Android效能優化 下
8.Android效能優化典範-第5季
ofollow,noindex">多執行緒大部分內容源自凱哥的課程,個人覺得比優化典範寫得清晰得多
1.執行緒
-
執行緒就是程式碼線性執行,執行完畢就結束的一條線.UI執行緒不會結束是因為其初始化完畢後會執行死迴圈,所以永遠不會執行完畢.
-
如何簡單建立新執行緒:
//1:直接建立Thread,執行其start方法 Thread t1 = new Thread(){ @Override public void run() { System.out.println("Thread:run"); } }; t1.start(); //2:使用Runnable例項作為引數建立Thread,執行start Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Runnable:run"); } }; Thread t2 = new Thread(runnable); t2.start();
- 兩種方式建立新執行緒效能無差別,使用Runnable例項適用於希望Runnable複用的情形
- 常用的建立執行緒池2種方式
- Executors.newCachedThreadPool():一般情況下使用newCachedThreadPool即可.
- Executors.newFixedThreadPool(int number):短時批量處理/比如要並行處理多張圖片,可以直接建立包含圖片精確數量的執行緒的執行緒池並行處理.
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("runnable:run()"); } }; ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(runnable); executorService.execute(runnable); executorService.execute(runnable); executorService.shutdown(); //比如有40張圖片要同時處理 //建立包含40個執行緒的執行緒池,每個執行緒處理一張圖片,處理完畢後shutdown ExecutorService service = Executors.newFixedThreadPool(40); for(Bitmap item:bitmaps){ //比如runnable就是處理單張圖片的 service.execute(runnable); } service.shutdown();
- 《阿里巴巴Java開發手冊》規定:
- 執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式.這樣
的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險 - 看Android中Executors原始碼.Executors.newCachedThreadPool/newScheduledThreadPool允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM.而newFixedThreadPool,newSingleThreadExecutor不會存在這種風險.
- 執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式.這樣
- 如何正確建立ThreadPoolExecutor:有點麻煩,晚點詳述
- ExecutorService的shutdown和shutdownNow
- shutdown:在呼叫shutdown之前ExecutorService中已經啟動的執行緒,在呼叫shutdown後,執行緒如果執行未結束會繼續執行完畢並結束,但不會再啟動新的執行緒執行新任務.
- shutdownNow:首先停止啟動新的執行緒執行新任務;並嘗試結束所有正在執行的執行緒,正在執行的執行緒可能被終止也可能會繼續執行完成.
-
如何正確建立ThreadPoolExecutor
3.1:ThreadPoolExecutor構造引數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- int corePoolSize:該執行緒池中核心執行緒最大數量.預設情況下,即使核心執行緒處於空閒狀態也不會被銷燬.除非通過allowCoreThreadTimeOut(true),則核心執行緒在空閒時間達到keepAliveTime時會被銷燬
- int maximumPoolSize:該執行緒池中執行緒最大數量
- long keepAliveTime:該執行緒池中非核心執行緒被銷燬前最大空閒時間,時間單位由unit決定.預設情況下核心執行緒即使空閒也不會被銷燬,在呼叫allowCoreThreadTimeOut(true)後,該銷燬時間設定也適用於核心執行緒
- TimeUnit unit:keepAliveTime/被銷燬前最大空閒時間的單位
- BlockingQueue<Runnable> workQueue:該執行緒池中的任務佇列.維護著等待被執行的Runnable物件.BlockingQueue有幾種型別,下面會詳述
- ThreadFactory threadFactory:建立新執行緒的工廠.一般情況使用Executors.defaultThreadFactory()即可.當然也可以自定義.
- RejectedExecutionHandler handler:拒絕策略.當需要建立的執行緒數量達到maximumPoolSize並且等待執行的Runnable數量超過了任務佇列的容量,該如何處理.
3.2:當1個任務被放進執行緒池,ThreadPoolExecutor具體執行策略如下:
- 如果執行緒數量沒有達到corePoolSize,有核心執行緒空閒則核心執行緒直接執行,沒有空閒則直接新建核心執行緒執行任務;
- 如果執行緒數量已經達到corePoolSize,且核心執行緒無空閒,則將任務新增到等待佇列;
- 如果等待佇列已滿,則新建非核心執行緒執行該任務;
- 如果等待佇列已滿且匯流排程數量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.
3.3:阻塞佇列/BlockingQueue<Runnable> workQueue
- BlockingQueue有如下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
- SynchronousQueue:SynchronousQueue的容量是0,不儲存任何Runnable例項.新任務到來會直接嘗試交給執行緒執行,如所有執行緒都在忙就建立新執行緒執行該任務.
- LinkedBlockingQueue:預設情況下沒有容量限制的佇列.
- ArrayBlockingQueue:一個有容量限制的佇列.
- DelayQueue:一個沒有容量限制的佇列.佇列中的元素必須實現了Delayed介面.元素在佇列中的排序按照當前時間的延遲值,延遲最小/最早要被執行的任務排在佇列頭部,依次排序.延遲時間到達後執行指定任務.
- PriorityBlockingQueue:一個沒有容量限制的佇列.佇列中元素必須實現了Comparable介面.佇列中元素排序依賴元素的自然排序/compareTo的比較結果.
- 各種BlockingQueue的問題
1.SynchronousQueue缺點:因為不具備儲存元素的能力,因而當任務很頻繁時候,為了防止執行緒數量超標,我們往往設定maximumPoolSize是Integer.MAX_VALUE,建立過多執行緒會導致OOM.《阿里巴巴Java開發手冊》中強調不能使用Executors直接建立執行緒池,就是對應Android原始碼中newCachedThreadPool和newScheduledThreadPool,本質上就是建立了maximumPoolSize為Integer.MAX_VALUE的ThreadPoolExecutor.
2.LinkedBlockingQueue因為沒有容量限制,所以我們使用LinkedBlockingQueue建立ThreadPoolExecutor,設定maximumPoolSize是無意義的,如果執行緒數量已經達到corePoolSize,且核心執行緒都在忙,那麼新來的任務會一直被新增到佇列中.只要核心執行緒無空閒則一直得不到被執行機會.
3.DelayQueue和PriorityBlockingQueue也具有同樣的問題.所以corePoolSize必須設定合理,否則會導致超出核心執行緒數量的任務一直得不到機會被執行.這兩類佇列分別適用於定時及優先順序明確的任務.
3.4:RejectedExecutionHandler handler/拒絕策略有4種
1.hreadPoolExecutor.AbortPolicy:丟棄任務,並丟擲RejectedExecutionException異常.ThreadPoolExecutor預設就是使用AbortPolicy.
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不會丟擲異常.
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄排在佇列頭部的任務,不丟擲異常,並嘗試重新執行任務.
4.ThreadPoolExecutor.CallerRunsPolicy:丟棄任務,但不丟擲異常,並將該任務交給呼叫此ThreadPoolExecutor的執行緒執行.
-
synchronized 的本質
- 保證synchronized方法或者程式碼塊內部資源/資料的互斥訪問
- 即同一時間,由同一個Monitor監視的程式碼,最多隻有1個執行緒在訪問
- 保證執行緒之間對監視資源的資料同步.
- 任何執行緒在獲取Monitor後,會第一時間將共享記憶體中的資料複製到自己的快取中;
- 任何執行緒在釋放Monitor後,會第一時間將快取中的資料複製到共享記憶體中
- 保證synchronized方法或者程式碼塊內部資源/資料的互斥訪問
-
volatile
- 保證被volatile修飾的成員的操作具有原子性和同步性.相當於簡化版的synchronized
- 原子性就是執行緒間互斥訪問
- 同步性就是執行緒之間對監視資源的資料同步
- volatile生效範圍:基本型別的直接複製賦值 + 引用型別的直接賦值
//引用型別的直接賦值操作有效 private volatile User u = U1; //修改引用型別的屬性,則不是原子性的,volatile無效 U1.name = "吊炸天" //對引用型別的直接賦值是原子性的 u = U2; private volatile int a = 0; private int b = 100; //volatile無法實現++/--的原子性 a++;
- volatile型變數自增操作的隱患
- volatile型別變數每次在讀取的時候,會越過執行緒的工作記憶體,直接從主存中讀取,也就不會產生髒讀
- ++自增操作,在Java對應的彙編指令有三條
- 從主存讀取變數值到cpu暫存器
- 暫存器裡的值+1
- 暫存器的值寫回主存
- 如果N個執行緒同時執行到了第1步,那麼最終變數會損失(N-1).第二步第三步只有一個執行緒是執行成功.
- 對變數的寫操作不依賴於當前值,才能用volatile修飾.
- volatile型變數自增操作的隱患
- 保證被volatile修飾的成員的操作具有原子性和同步性.相當於簡化版的synchronized
-
針對num++這類複合類的操作,可以使用java併發包中的原子操作類原子操作類:AtomicInteger AtomicBoolean等來保證其原子性.
public static AtomicInteger num = new AtomicInteger(0); num.incrementAndGet();//原子性的num++,通過迴圈CAS方式
2.執行緒間互動
- 一個執行緒終結另一個執行緒
- Thread.stop不要用:
- 因為執行緒在執行過程中隨時有可能會被暫停切換到其他執行緒,stop的效果相當於切換到其他執行緒繼續執行且以後再也不會切換回來.我們執行A.stop的時候,完全無法預知A的run方法已經執行了多少,執行百分比完全不可控.
下面的程式碼,每次執行最後列印的結果都不同,即我們完全不可預知呼叫stop時候當前執行緒執行了百分之多少. private static void t2(){ Thread t = new Thread(){ @Override public void run() { for(int i=0;i<1000000;i++){ System.out.println(""+i); } } }; t.start(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } t.stop(); }
- Thread.interrupt:僅僅設定當前執行緒為被中斷狀態.在執行的執行緒依然會繼續執行.
- Thread.isInterrupted:獲取當前執行緒是否被中斷
- Thread.interrupted():如果執行緒A呼叫了Thread.interrupted()
- 如果A之前已經被中斷,呼叫Thread.interrupted()返回false,A已經不是被中斷狀態
- 如果A之前不是被中斷狀態,呼叫Thread.interrupted()返回true,A變成被中斷狀態.
- 單純呼叫A.interrupt是無效果的,interrupt需要和isInterrupted聯合使用
- 用於我們希望執行緒處於被中斷狀態時結束執行的場景.
- interrupt和stop比較的優點:stop後,執行緒直接結束,我們完全無法控制當前執行到哪裡;
interrupt後執行緒預設會繼續執行,我們通過isInterrupted來獲取被中斷狀態,只有被中斷且滿足我們指定條件才return,可以精確控制執行緒的執行百分比.
private static void t2(){ Thread t = new Thread(){ @Override public void run() { for(int i=0;i<1000000;i++){ //檢查執行緒是否處於中斷狀態,且檢查是否滿足指定條件 //如果不滿足指定條件,即使處於中斷狀態也繼續執行. if(isInterrupted()&&i>800000){ //先做收尾工作 //return 結束 return; } System.out.println(""+i); } } }; t.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //呼叫了interrupt後,在run中監查是否已經被打斷,如果已經被打斷,且滿足指定條件, //就return,執行緒就執行完了 t.interrupt(); } ...... 799999 800000 Process finished with exit code 0
- InterruptedException:
- 如果執行緒A在sleep過程中被其他執行緒呼叫A.interrupt(),會觸發InterruptedException.
- 如果呼叫A.interrupt()時候,A並不在sleep狀態,後面再呼叫A.sleep,也會立即丟擲InterruptedException.
private static void t3(){ Thread thread = new Thread(){ @Override public void run() { long t1 = System.currentTimeMillis(); try { Thread.sleep(3000); } catch (InterruptedException e) { long t2 = System.currentTimeMillis(); System.out.println("老子被叫醒了:睡了"+(t2-t1)+"ms"); //用於做執行緒收尾工作,然後return return; } System.out.println("AAAAAAAA"); } }; thread.start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); } 老子被叫醒了:睡了493ms Process finished with exit code 0
- Thread.stop不要用:
- 執行緒等待:wait,notifyAll,notify
- wait,notifyAll,notify是屬於Object的方法.用於執行緒等待的場景,需用Monitor進行呼叫
- wait:
- 當1個執行緒A持有Monitor M.
- 此時呼叫M.wait,A會釋放M並處於等待狀態.並記錄A在當前程式碼執行的位置Position.
- notify:
- 當呼叫M.notify(),就會喚醒1個因為呼叫M.wait()而處於等待狀態的執行緒
- 如果有A,B,C--多個執行緒都是因為呼叫M.wait()而處於等待狀態,不一定哪個會被喚醒並嘗試獲取M
- notifyAll:
- 當呼叫M.notifyAll(),所有因為呼叫M.wait()而處於等待狀態的執行緒都被喚醒,一起競爭嘗試獲取M
- 呼叫notify/notifyAll被喚醒並獲取到M的執行緒A,會接著之前的程式碼執行位置Position繼續執行下去
private String str = null; private synchronized void setStr(String str){ System.out.println("setStr時間:"+System.currentTimeMillis()); this.str = str; notifyAll(); } private synchronized void printStr(){ while (str==null){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("執行緒:"+Thread.currentThread().getName()+ " printStr時間:"+System.currentTimeMillis()); System.out.println("str:"+str); } private void t4(){ (new Thread(){ @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } setStr("老子設定一下"); } }).start(); (new Thread(){ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執行緒:"+Thread.currentThread().getName()+ " 嘗試printStr時間:"+System.currentTimeMillis()); printStr(); } }).start(); (new Thread(){ @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執行緒:"+Thread.currentThread().getName()+ " 嘗試printStr時間:"+System.currentTimeMillis()); printStr(); } }).start(); } 執行緒:Thread-2 嘗試printStr時間:1539247468146 執行緒:Thread-1 嘗試printStr時間:1539247468944 setStr時間:1539247469944 執行緒:Thread-1 printStr時間:1539247469944 str:老子設定一下 執行緒:Thread-2 printStr時間:1539247469944 str:老子設定一下
3.Executor、 AsyncTask、 HandlerThead、 IntentService 如何選擇
- HandlerThead就不要用,HandlerThead設計目的就是為了主介面死迴圈重新整理介面,無其他應用場景.
- 能用執行緒池就用執行緒池,因為最簡單.
- 涉及後臺執行緒推送任務到UI執行緒,可以使用Handler或AsyncTask
- Service:就是為了做後臺任務,不要UI介面,需要持續存活.有複雜的需要長期存活/等待的場景使用Service.
- IntentService:屬於Service.當我們需要使用Service,且需要後臺程式碼執行完畢後該Service自動被銷燬,使用IntentService.
4.AsyncTask的記憶體洩漏
- GC Roots:由堆外指向堆內的引用,包括:
- Java方法棧幀中的區域性變數
- 已載入類的靜態變數
- native程式碼的引用
- 執行中的Java執行緒
- AsyncTask記憶體洩漏本質:正在執行的執行緒/AsyncTask 在虛擬機器中屬於GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的物件不能被回收.
- 所以AsyncTask和其他執行緒工具一樣,只要是使用執行緒,都有可能發生記憶體洩漏,都要及時關閉,AsyncTask並不比其他工具更差.
- 如何避免AsyncTask記憶體洩漏:使用弱引用解決AsyncTask在Activity銷燬後依然持有Activity引用的問題
5.RxJava.
講的太多了這裡推薦1個專題RxJava2.x
下面記錄一下自己不太熟的幾點
- RxJava整體結構:
- 鏈的最上游:生產者Observable
- 鏈的最下游:觀察者Observer
- 鏈的中間多個節點:雙重角色.即是上一節點的觀察者Observer,也是下一節點的生產者Observable.
- Scheduler切換執行緒的原理:原始碼跟蹤下去,實質是通過Excutor實現了執行緒切換.
6.Android M對Profile GPU Rendering工具的更新

image
- Swap Buffers:CPU等待GPU處理的時間
- Command Issur:OpenGL渲染Display List所需要的時間
- Sync&Upload:通常表示的是準備當前介面上有待繪製的圖片所耗費的時間,為了減少該段區域的執行時間,我們可以減少螢幕上的圖片數量或者是縮小圖片本身的大小
- Draw:測量繪製Display List的時間
- Measure & Layout:這裡表示的是佈局的onMeasure與onLayout所花費的時間.一旦時間過長,就需要仔細檢查自己的佈局是不是存在嚴重的效能問題
- Animation:表示的是計算執行動畫所需要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這裡的執行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執行的過程中是不是觸發了讀寫操作等等
- Input Handling:表示的是系統處理輸入事件所耗費的時間,粗略等於對於的事件處理方法所執行的時間.一旦執行時間過長,意味著在處理使用者的輸入事件的地方執行了複雜的操作
- Misc/Vsync Delay:如果稍加註意,我們可以在開發應用的Log日誌裡面看到這樣一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。這意味著我們在主執行緒執行了太多的任務,導致UI渲染跟不上vSync的訊號而出現掉幀的情況
9.Android效能優化典範-第6季
1.啟動閃屏
- 當點選桌面圖示啟動APP的時候,App會出現短暫的白屏,一直到第一個Activity的頁面的渲染載入完畢
- 為了消除白屏,我們可以為App入口Activity單獨設定theme.
- 在單獨設定的theme中設定android:background屬性為App的品牌宣傳圖片背景.
- 在程式碼執行到入口Activity的onCreate的時候設定為程式正常的主題.
styles.xml <!-- Base application theme. --> //Activity預設主題 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> //預設主題視窗背景設定為白色 <item name="android:background">@android:color/white</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> //入口Activity的theme單獨設定 <style name="ThemeSplash" parent="Theme.AppCompat.Light.NoActionBar"> //入口Activity初始視窗背景設定為品牌宣傳圖片 <item name="android:background">@mipmap/startbg</item> <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> AndroidManifest.xml <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:theme="@style/ThemeSplash">//為入口Activity單獨指定theme <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </manifest> public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { //在程式碼執行到入口Activity時候設定入口Activity為預設主題 setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_all = findViewById(R.id.tv_all); tv_local = findViewById(R.id.tv_local); //註冊全域性廣播 registerReceiver(globalReceiver,new IntentFilter("global")); //註冊本地廣播 LocalBroadcastManager.getInstance(this).registerReceiver(localBroadReceiver,new IntentFilter("localBroadCast")); } }
2.為App提供對應解析度下的圖片,系統會自動匹配最合適解析度的圖片執行拉伸或壓縮的處理.
- 如果是隻有1張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
- 所有的大背景圖片,統一放在mipmap-nodpi目錄,用一套1080P素材可以解決大部分手機適配問題,不用每個資源目錄下放一套素材
- 經過試驗,不論ImageView寬高是否是wrap_content,只要圖片所在資料夾和當前裝置解析度不匹配,都會涉及到放大或壓縮,佔用的記憶體都會相應的變化.尤其對於大圖,放在低解析度資料夾下直接OOM.
具體原因:
郭霖:Android drawable微技巧,你所不知道的drawable的那些細節
當我們使用資源id來去引用一張圖片時,Android會使用一些規則來去幫我們匹配最適合的圖片。什麼叫最適合的圖片?比如我的手機螢幕密度是xxhdpi,那麼drawable-xxhdpi資料夾下的圖片就是最適合的圖片。因此,當我引用android_logo這張圖時,如果drawable-xxhdpi資料夾下有這張圖就會優先被使用,在這種情況下,圖片是不會被縮放的。但是,如果drawable-xxhdpi資料夾下沒有這張圖時, 系統就會自動去其它資料夾下找這張圖了,優先會去更高密度的資料夾下找這張圖片,我們當前的場景就是drawable-xxxhdpi資料夾,然後發現這裡也沒有android_logo這張圖,接下來會嘗試再找更高密度的資料夾,發現沒有更高密度的了,這個時候會去drawable-nodpi資料夾找這張圖,發現也沒有,那麼就會去更低密度的資料夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
總體匹配規則就是這樣,那麼比如說現在終於在drawable-mdpi資料夾下面找到android_logo這張圖了,但是系統會認為你這張圖是專門為低密度的裝置所設計的,如果直接將這張圖在當前的高密度裝置上使用就有可能會出現畫素過低的情況,於是系統自動幫我們做了這樣一個放大操作。
那麼同樣的道理,如果系統是在drawable-xxxhdpi資料夾下面找到這張圖的話,它會認為這張圖是為更高密度的裝置所設計的,如果直接將這張圖在當前裝置上使用就有可能會出現畫素過高的情況,於是會自動幫我們做一個縮小的操作
3.儘量複用已經存在的圖片.
比如一張圖片O已經存在,如果有View的背景就是O旋轉過後的樣子,可以直接用O建立RotateDrawable.然後將設定給View使用.
注意:RotateDrawable已經重寫了其onLevelChange方法,所以一定要設定level才會生效
@Override protected boolean onLevelChange(int level) { super.onLevelChange(level); final float value = level / (float) MAX_LEVEL; final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value); mState.mCurrentDegrees = degrees; invalidateSelf(); return true; }
例項:
1.首先建立xml檔案 <?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@mipmap/close10" android:fromDegrees="90" android:toDegrees="120" android:pivotX="50%" android:pivotY="50%" > </rotate> 2.在Java程式碼中獲取該xml對應的Drawable例項,並設定level為10000 Drawable drawable = getResources().getDrawable(R.drawable.rotate_close); drawable.setLevel(10000); 3.將Drawable設定為View的背景 findViewById(R.id.v).setBackgroundDrawable(drawable);
4.開啟混淆和資源壓縮:在app模組下的的build.gradle中
buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
5.對於簡單/規則紋理的圖片,使用VectorDrawable來替代多個解析度圖片.VectorDrawable 有很多注意事項,後面單獨一篇文章總結
10.網路優化
網路優化主要有幾個方面:降低網路請求數量,降低單次請求響應的資料量,在弱網環境下將非必要網路請求延緩至網路環境好的時候.
1.降低網路請求數量:獲取同樣的資料,多次網路請求會增加電量消耗,且多次請求總體上將消耗服務端更多的時間及資源
- 介面Api設計要合理.可以將多個介面合併,多次請求顯示1個介面,改造後1個介面即可提供完整資料.
- 根據具體場景實時性需求,在App中加入網路快取,在實時性有效區間避免重複請求:主要包括網路框架和圖片載入框架的快取.
2.降低單次請求的資料量
- 網路介面Api在設計時候,去除多餘的請求引數及響應資料.
- 網路請求及響應資料的傳輸開啟GZIP壓縮,降低傳輸資料量.
- okHttp對gzip的支援前面已記錄
- Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執行序列化.
- Protocal Buffers網上有使用的方法,相對GSON有點繁瑣.如果對網路傳輸量很敏感,可以考慮使用.其他幾種方案的文章不多.
- 網路請求圖片,新增圖片寬高參數,避免下載過大圖片增加流量消耗.
3.弱網環境優化這塊沒有經驗,直接看anly_jun的文章
文章中提到:使用者點贊操作, 可以直接給出介面的點贊成功的反饋, 使用JobScheduler在網路情況較好的時候打包請求.
11.電量優化
12.JobScheduler,AlarmManager和WakeLock
JobScheduler在網路優化中出現過,WakeLock涉及電量優化,AlarmManager和WakeLock有相似,但側重點不同.
- WakeLock:比如一段關鍵邏輯T已經在執行,執行未完成Android系統就進入休眠,會導致T執行中斷.WakeLock目的就在於阻止Android系統進入休眠狀態,保證T得以繼續執行.
- 休眠過程中自定義的Timer、Handler、Thread、Service等都會暫停
- AlarmManager:Android系統自帶的定時器,可以將處於休眠狀態的Android系統喚醒
- 保證Android系統在休眠狀態下被及時喚醒,執行 定時/延時/輪詢任務
- JobScheduler:JobScheduler目的在於將當下不緊急的任務延遲到後面更合適的某個時間來執行.我們可以控制這些任務在什麼條件下被執行.
- JobScheduler可以節約Android裝置當下網路,電量,CPU等資源.在指定資源充裕情況下再執行"不緊要"的任務.
JobScheduler:
WakeLock:
Android PowerManager.WakeLock使用小結
Android的PowerManager和PowerManager.WakeLock用法簡析
AlarmManager和WakeLock使用:
後臺任務 - 保持裝置喚醒狀態13.效能檢測工具
1.Android Studio 3.2之後,Android Device Monitor已經被移除.Android Device Monitor原先包含的工具由新的方案替代. Android Device Monitor

image
- DDMS:由Android Profiler代替.可以進行CPU,記憶體,網路分析.
- TraceView:可以通過Debug類在程式碼中呼叫Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄兩者之間所有執行緒及執行緒中方法的耗時,生成.trace檔案.通過abd命令可以將trace檔案匯出到電腦,通過CPU profiler分析.
- Systrace:可以通過命令列生成html檔案,通過Chrome瀏覽器進行分析.
- 生成html檔案已實現.但檔案怎麼分析暫未掌握,看了網上一些文章說實話還是沒搞懂
- Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能檢視每個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.我們可使用系統提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
- Network Traffic tool:由Network Profiler代替.
2.其中Android Profiler如何使用,直接看官網即可. Profile your app performance .
3.TraceView
-
TraceView可以直接通過CPU profiler中點選Record按鈕後,任意時間後點擊Stop按鈕.即可生成trace檔案.並可將.trace檔案匯出.
image
image
- TraceView也可以通過Debug類在程式碼中精確控制要統計哪個區間程式碼/執行緒的CPU耗時.
這種用法是 anly_jun大神文章裡學到的public class SampleApplication extends Application { @Override public void onCreate() { Debug.startMethodTracing("JetApp"); super.onCreate(); LeakCanary.install(this); // init logger. AppLog.init(); // init crash helper CrashHelper.init(this); // init Push PushPlatform.init(this); // init Feedback FeedbackPlatform.init(this); Debug.stopMethodTracing(); }
程式碼執行完畢,會在Android裝置中生成JetApp.trace檔案.通過Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.trace
在JetApp.trace上點選右鍵->Copy Path,將trace檔案路徑複製下來.
Windows下cmd開啟命令列,執行 adb pull 路徑,即可trace檔案匯出到電腦.
image
- trace檔案分析很簡單.我們可以看到每個執行緒及執行緒中每個方法呼叫消耗的時間.
4.Layout Inspector很簡單,在App執行後,點選Tools->Layout Inspector即可.
下面只看Window.OnFrameMetricsAvailableListener怎麼用.
從Android 7.0 (API level 24)開始,Android引入Window.OnFrameMetricsAvailableList介面用於提供每一幀繪製各階段的耗時,資料來源與GPU Profile相同.
public interface OnFrameMetricsAvailableListener { void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,int dropCountSinceLastInvocation); } /** * 包含1幀的週期內,渲染系統各個方法的耗時資料. */ public final class FrameMetrics { **** //通過getMetric獲取layout/measure耗時所用的id public static final int LAYOUT_MEASURE_DURATION = 3; public static final int DRAW_DURATION = 4; /** * 獲取當前幀指定id代表的方法/過程的耗時,單位是納秒:1納秒(ns)=10的負6次方毫秒(ms) */ public long getMetric(@Metric int id) { **** } }
- 在Activity中使用OnFrameMetricsAvailableListener:
- 通過呼叫this.getWindow().addOnFrameMetricsAvailableListener(@NonNull OnFrameMetricsAvailableListener listener,Handler handler)來新增監聽.
- 通過呼叫this.getWindow().removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener)取消監聽.
- QQ?" target="_blank" rel="nofollow,noindex">解析ConstraintLayout的效能優勢 中引用了Google使用OnFrameMetricsAvailableListener的例子 android-constraint-layout-performance .其中Activity是Kotlin寫的,嘗試將程式碼轉為java,解決掉報錯後執行.
package p1.com.p1; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.annotation.RequiresApi; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.FrameMetrics; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.view.Window.OnFrameMetricsAvailableListener; import android.widget.Button; import android.widget.TextView; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.WeakReference; import java.util.Arrays; import kotlin.TypeCastException; import kotlin.jvm.internal.Intrinsics; public final class KtMainActivity extends AppCompatActivity { private final Handler frameMetricsHandler = new Handler(); @RequiresApi(24) private final OnFrameMetricsAvailableListener frameMetricsAvailableListener = new OnFrameMetricsAvailableListener() { @Override public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) { long costDuration = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION); Log.d("Jet", "layoutMeasureDurationNs: " + costDuration); } }; private static final String TAG = "KtMainActivity"; private static final int TOTAL = 100; private static final int WIDTH = 1920; private static final int HEIGHT = 1080; @RequiresApi(3) protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_for_test); final Button traditionalCalcButton = (Button) this.findViewById(R.id.button_start_calc_traditional); final Button constraintCalcButton = (Button) this.findViewById(R.id.button_start_calc_constraint); final TextView textViewFinish = (TextView) this.findViewById(R.id.textview_finish); traditionalCalcButton.setOnClickListener((OnClickListener) (new OnClickListener() { public final void onClick(View it) { Button var10000 = constraintCalcButton; Intrinsics.checkExpressionValueIsNotNull(constraintCalcButton, "constraintCalcButton"); var10000.setVisibility(View.INVISIBLE); View var4 = KtMainActivity.this.getLayoutInflater().inflate(R.layout.activity_traditional, (ViewGroup) null); if (var4 == null) { throw new TypeCastException("null cannot be cast to non-null type android.view.ViewGroup"); } else { ViewGroup container = (ViewGroup) var4; String var10002 = KtMainActivity.this.getString(R.string.executing_nth_iteration); Intrinsics.checkExpressionValueIsNotNull(var10002, "getString(R.string.executing_nth_iteration)"); KtMainActivity.MeasureLayoutAsyncTask asyncTask = new KtMainActivity.MeasureLayoutAsyncTask(var10002, new WeakReference(traditionalCalcButton), new WeakReference(textViewFinish), new WeakReference(container)); asyncTask.execute(new Void[0]); } } })); constraintCalcButton.setOnClickListener((OnClickListener) (new OnClickListener() { public final void onClick(View it) { Button var10000 = traditionalCalcButton; Intrinsics.checkExpressionValueIsNotNull(traditionalCalcButton, "traditionalCalcButton"); var10000.setVisibility(View.INVISIBLE); View var4 = KtMainActivity.this.getLayoutInflater().inflate(R.layout.activity_constraintlayout, (ViewGroup) null); if (var4 == null) { throw new TypeCastException("null cannot be cast to non-null type android.view.ViewGroup"); } else { ViewGroup container = (ViewGroup) var4; String var10002 = KtMainActivity.this.getString(R.string.executing_nth_iteration); Intrinsics.checkExpressionValueIsNotNull(var10002, "getString(R.string.executing_nth_iteration)"); KtMainActivity.MeasureLayoutAsyncTask asyncTask = new KtMainActivity.MeasureLayoutAsyncTask(var10002, new WeakReference(constraintCalcButton), new WeakReference(textViewFinish), new WeakReference(container)); asyncTask.execute(new Void[0]); } } })); } @RequiresApi(24) protected void onResume() { super.onResume(); this.getWindow().addOnFrameMetricsAvailableListener(this.frameMetricsAvailableListener, this.frameMetricsHandler); } @RequiresApi(24) protected void onPause() { super.onPause(); this.getWindow().removeOnFrameMetricsAvailableListener(this.frameMetricsAvailableListener); } @RequiresApi(3) private static final class MeasureLayoutAsyncTask extends AsyncTask { @NotNull private final String executingNthIteration; @NotNull private final WeakReference startButtonRef; @NotNull private final WeakReference finishTextViewRef; @NotNull private final WeakReference containerRef; @Nullable protected Void doInBackground(@NotNull Void... voids) { Intrinsics.checkParameterIsNotNull(voids, "voids"); int i = 0; for (int var3 = KtMainActivity.TOTAL; i < var3; ++i) { this.publishProgress(new Integer[]{i}); try { Thread.sleep(100L); } catch (InterruptedException var5) { ; } } return null; } // $FF: synthetic method // $FF: bridge method public Object doInBackground(Object[] var1) { return this.doInBackground((Void[]) var1); } protected void onProgressUpdate(@NotNull Integer... values) { Intrinsics.checkParameterIsNotNull(values, "values"); Button var10000 = (Button) this.startButtonRef.get(); if (var10000 != null) { Button startButton = var10000; Intrinsics.checkExpressionValueIsNotNull(startButton, "startButton"); //StringCompanionObject var3 = StringCompanionObject.INSTANCE; String var4 = this.executingNthIteration; Object[] var5 = new Object[]{values[0], KtMainActivity.TOTAL}; String var9 = String.format(var4, Arrays.copyOf(var5, var5.length)); Intrinsics.checkExpressionValueIsNotNull(var9, "java.lang.String.format(format, *args)"); String var7 = var9; startButton.setText((CharSequence) var7); ViewGroup var10 = (ViewGroup) this.containerRef.get(); if (var10 != null) { ViewGroup container = var10; Intrinsics.checkExpressionValueIsNotNull(container, "container"); this.measureAndLayoutExactLength(container); this.measureAndLayoutWrapLength(container); } } } // $FF: synthetic method // $FF: bridge method public void onProgressUpdate(Object[] var1) { this.onProgressUpdate((Integer[]) var1); } protected void onPostExecute(@Nullable Void aVoid) { TextView var10000 = (TextView) this.finishTextViewRef.get(); if (var10000 != null) { TextView finishTextView = var10000; Intrinsics.checkExpressionValueIsNotNull(finishTextView, "finishTextView"); finishTextView.setVisibility(View.VISIBLE); Button var4 = (Button) this.startButtonRef.get(); if (var4 != null) { Button startButton = var4; Intrinsics.checkExpressionValueIsNotNull(startButton, "startButton"); startButton.setVisibility(View.GONE); } } } // $FF: synthetic method // $FF: bridge method public void onPostExecute(Object var1) { this.onPostExecute((Void) var1); } private final void measureAndLayoutWrapLength(ViewGroup container) { int widthMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.WIDTH, View.MeasureSpec.AT_MOST); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.HEIGHT, View.MeasureSpec.AT_MOST); container.measure(widthMeasureSpec, heightMeasureSpec); container.layout(0, 0, container.getMeasuredWidth(), container.getMeasuredHeight()); } private final void measureAndLayoutExactLength(ViewGroup container) { int widthMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.WIDTH, View.MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.HEIGHT, View.MeasureSpec.EXACTLY); container.measure(widthMeasureSpec, heightMeasureSpec); container.layout(0, 0, container.getMeasuredWidth(), container.getMeasuredHeight()); } @NotNull public final String getExecutingNthIteration() { return this.executingNthIteration; } @NotNull public final WeakReference getStartButtonRef() { return this.startButtonRef; } @NotNull public final WeakReference getFinishTextViewRef() { return this.finishTextViewRef; } @NotNull public final WeakReference getContainerRef() { return this.containerRef; } public MeasureLayoutAsyncTask(@NotNull String executingNthIteration, @NotNull WeakReference startButtonRef, @NotNull WeakReference finishTextViewRef, @NotNull WeakReference containerRef) { super(); Intrinsics.checkParameterIsNotNull(executingNthIteration, "executingNthIteration"); Intrinsics.checkParameterIsNotNull(startButtonRef, "startButtonRef"); Intrinsics.checkParameterIsNotNull(finishTextViewRef, "finishTextViewRef"); Intrinsics.checkParameterIsNotNull(containerRef, "containerRef"); this.executingNthIteration = executingNthIteration; this.startButtonRef = startButtonRef; this.finishTextViewRef = finishTextViewRef; this.containerRef = containerRef; } } } D/Jet: layoutMeasureDurationNs: 267344 D/Jet: layoutMeasureDurationNs: 47708 D/Jet: layoutMeasureDurationNs: 647240 D/Jet: layoutMeasureDurationNs: 59636 D/Jet: layoutMeasureDurationNs: 50052 D/Jet: layoutMeasureDurationNs: 49739 D/Jet: layoutMeasureDurationNs: 75990 D/Jet: layoutMeasureDurationNs: 296198 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 894375 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 1248021 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 0 D/Jet: layoutMeasureDurationNs: 1290677 D/Jet: layoutMeasureDurationNs: 2936563 D/Jet: layoutMeasureDurationNs: 1387188 D/Jet: layoutMeasureDurationNs: 2325521 D/Jet: layoutMeasureDurationNs: 1940052 D/Jet: layoutMeasureDurationNs: 1539271 D/Jet: layoutMeasureDurationNs: 803750 D/Jet: layoutMeasureDurationNs: 1405000 D/Jet: layoutMeasureDurationNs: 1188437 D/Jet: layoutMeasureDurationNs: 1748802 D/Jet: layoutMeasureDurationNs: 3422240 D/Jet: layoutMeasureDurationNs: 1400677 D/Jet: layoutMeasureDurationNs: 2416094 D/Jet: layoutMeasureDurationNs: 1532864 D/Jet: layoutMeasureDurationNs: 1684063 D/Jet: layoutMeasureDurationNs: 1092865 D/Jet: layoutMeasureDurationNs: 1363177 D/Jet: layoutMeasureDurationNs: 1067188 D/Jet: layoutMeasureDurationNs: 1358333 D/Jet: layoutMeasureDurationNs: 2999895 D/Jet: layoutMeasureDurationNs: 2113021 D/Jet: layoutMeasureDurationNs: 1957395 D/Jet: layoutMeasureDurationNs: 1319740 D/Jet: layoutMeasureDurationNs: 2207239 D/Jet: layoutMeasureDurationNs: 1514167 D/Jet: layoutMeasureDurationNs: 949114 D/Jet: layoutMeasureDurationNs: 1691250 D/Jet: layoutMeasureDurationNs: 1387448 D/Jet: layoutMeasureDurationNs: 932552 D/Jet: layoutMeasureDurationNs: 1223802 D/Jet: layoutMeasureDurationNs: 2024740 D/Jet: layoutMeasureDurationNs: 1242292 D/Jet: layoutMeasureDurationNs: 2228230 D/Jet: layoutMeasureDurationNs: 1382083 D/Jet: layoutMeasureDurationNs: 2233282 D/Jet: layoutMeasureDurationNs: 1907187 D/Jet: layoutMeasureDurationNs: 2287552 D/Jet: layoutMeasureDurationNs: 776354 D/Jet: layoutMeasureDurationNs: 1225000 D/Jet: layoutMeasureDurationNs: 875417 D/Jet: layoutMeasureDurationNs: 1271302 D/Jet: layoutMeasureDurationNs: 1211614 D/Jet: layoutMeasureDurationNs: 1346459 D/Jet: layoutMeasureDurationNs: 1978854 D/Jet: layoutMeasureDurationNs: 2915677 D/Jet: layoutMeasureDurationNs: 1330573 D/Jet: layoutMeasureDurationNs: 2195364 D/Jet: layoutMeasureDurationNs: 775208 D/Jet: layoutMeasureDurationNs: 2492292 D/Jet: layoutMeasureDurationNs: 400104 D/Jet: layoutMeasureDurationNs: 2844375 D/Jet: layoutMeasureDurationNs: 1563750 D/Jet: layoutMeasureDurationNs: 3689531 D/Jet: layoutMeasureDurationNs: 2019323 D/Jet: layoutMeasureDurationNs: 1663906 D/Jet: layoutMeasureDurationNs: 1004531 D/Jet: layoutMeasureDurationNs: 738125 D/Jet: layoutMeasureDurationNs: 1299166 D/Jet: layoutMeasureDurationNs: 1223854 D/Jet: layoutMeasureDurationNs: 1942240 D/Jet: layoutMeasureDurationNs: 1392396 D/Jet: layoutMeasureDurationNs: 1906458 D/Jet: layoutMeasureDurationNs: 691198 D/Jet: layoutMeasureDurationNs: 2620468 D/Jet: layoutMeasureDurationNs: 1953229 D/Jet: layoutMeasureDurationNs: 1120365 D/Jet: layoutMeasureDurationNs: 3165417 D/Jet: layoutMeasureDurationNs: 537709 D/Jet: layoutMeasureDurationNs: 3019531 D/Jet: layoutMeasureDurationNs: 706250 D/Jet: layoutMeasureDurationNs: 1129115 D/Jet: layoutMeasureDurationNs: 539427 D/Jet: layoutMeasureDurationNs: 1633438 D/Jet: layoutMeasureDurationNs: 1784479 D/Jet: layoutMeasureDurationNs: 743229 D/Jet: layoutMeasureDurationNs: 1851615 D/Jet: layoutMeasureDurationNs: 851927 D/Jet: layoutMeasureDurationNs: 1847916 D/Jet: layoutMeasureDurationNs: 836718 D/Jet: layoutMeasureDurationNs: 2892552 D/Jet: layoutMeasureDurationNs: 1230573 D/Jet: layoutMeasureDurationNs: 3886563 D/Jet: layoutMeasureDurationNs: 2138281 D/Jet: layoutMeasureDurationNs: 2198021 D/Jet: layoutMeasureDurationNs: 1805885 D/Jet: layoutMeasureDurationNs: 2316927 D/Jet: layoutMeasureDurationNs: 1990937 D/Jet: layoutMeasureDurationNs: 2261041 D/Jet: layoutMeasureDurationNs: 2159010 D/Jet: layoutMeasureDurationNs: 666562 D/Jet: layoutMeasureDurationNs: 2332031 D/Jet: layoutMeasureDurationNs: 1061875 D/Jet: layoutMeasureDurationNs: 1879062 D/Jet: layoutMeasureDurationNs: 1411459 D/Jet: layoutMeasureDurationNs: 154635
- 在Application中使用OnFrameMetricsAvailableListener,則可以統一設定,不需要每個Activity單獨設定,推薦使用開源專案 ActivityFrameMetrics
- 在Application的onCreate中設定單幀渲染總時間超過W毫秒和E毫秒,會在Logcat中列印警告和錯誤的Log資訊.
public class SampleApplication extends Application { @Override public void onCreate() { registerActivityLifecycleCallbacks(new ActivityFrameMetrics.Builder() .warningLevelMs(10)//default: 17ms .errorLevelMs(10)//default: 34ms .showWarnings(true)//default: true .showErrors(true)//default: true .build()); } }
- Application設定完成執行App,出現單幀渲染總耗時超過指定時間,即可看到Logcat中的資訊.
E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 16.91ms Layout/measure: 1.66ms, draw:2.51ms, gpuCommand:3.13ms others:9.61ms Janky frames: 72/107(67.28972%) E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 15.47ms Layout/measure: 1.00ms, draw:2.05ms, gpuCommand:3.44ms others:8.98ms Janky frames: 73/108(67.59259%) E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 15.09ms Layout/measure: 1.30ms, draw:1.44ms, gpuCommand:2.91ms others:9.44ms Janky frames: 74/110(67.27273%) ****
- 在Application的onCreate中設定單幀渲染總時間超過W毫秒和E毫秒,會在Logcat中列印警告和錯誤的Log資訊.
5.Systrace:通過命令列生成html檔案,通過Chrome瀏覽器進行分析
- 首先電腦要安裝python,這裡有幾個坑:
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 比如自己電腦中systrace資料夾路徑是:C:\Users\你的使用者名稱\AppData\Local\Android\Sdk\platform-tools\systrace,如果我們安裝的是3.x版本,在這個路徑下執行python systrace.py *** 命令會報錯,提示你應該安裝2.7
- Python安裝時候,要記得勾選"Add python.exe to Path".
- 這時候直接執行python systrace.py ***命令還是會報錯:ImportError: No module named win32com
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 生成html及如何分析