[筆記]Android性能優(yōu)化 上
[筆記]Android性能優(yōu)化 中
[筆記]Android性能優(yōu)化 下
8.Android性能優(yōu)化典范-第5季
多線程大部分內(nèi)容源自凱哥的課程,個人覺得比優(yōu)化典范寫得清晰得多
1.線程
線程就是代碼線性執(zhí)行,執(zhí)行完畢就結(jié)束的一條線.UI線程不會結(jié)束是因為其初始化完畢后會執(zhí)行死循環(huán),所以永遠不會執(zhí)行完畢.
-
如何簡單創(chuàng)建新線程:
//1:直接創(chuàng)建Thread,執(zhí)行其start方法 Thread t1 = new Thread(){ @Override public void run() { System.out.println("Thread:run"); } }; t1.start(); //2:使用Runnable實例作為參數(shù)創(chuàng)建Thread,執(zhí)行start Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Runnable:run"); } }; Thread t2 = new Thread(runnable); t2.start();- 兩種方式創(chuàng)建新線程性能無差別,使用Runnable實例適用于希望Runnable復(fù)用的情形
- 常用的創(chuàng)建線程池2種方式
- Executors.newCachedThreadPool():一般情況下使用newCachedThreadPool即可.
- Executors.newFixedThreadPool(int number):短時批量處理/比如要并行處理多張圖片,可以直接創(chuàng)建包含圖片精確數(shù)量的線程的線程池并行處理.
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張圖片要同時處理 //創(chuàng)建包含40個線程的線程池,每個線程處理一張圖片,處理完畢后shutdown ExecutorService service = Executors.newFixedThreadPool(40); for(Bitmap item:bitmaps){ //比如runnable就是處理單張圖片的 service.execute(runnable); } service.shutdown(); - 《阿里巴巴Java開發(fā)手冊》規(guī)定:
- 線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式.這樣
的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險 - 看Android中Executors源碼.Executors.newCachedThreadPool/newScheduledThreadPool允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導致 OOM.而newFixedThreadPool,newSingleThreadExecutor不會存在這種風險.
- 線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式.這樣
- 如何正確創(chuàng)建ThreadPoolExecutor:有點麻煩,晚點詳述
- ExecutorService的shutdown和shutdownNow
- shutdown:在調(diào)用shutdown之前ExecutorService中已經(jīng)啟動的線程,在調(diào)用shutdown后,線程如果執(zhí)行未結(jié)束會繼續(xù)執(zhí)行完畢并結(jié)束,但不會再啟動新的線程執(zhí)行新任務(wù).
- shutdownNow:首先停止啟動新的線程執(zhí)行新任務(wù);并嘗試結(jié)束所有正在執(zhí)行的線程,正在執(zhí)行的線程可能被終止也可能會繼續(xù)執(zhí)行完成.
-
如何正確創(chuàng)建ThreadPoolExecutor
3.1:ThreadPoolExecutor構(gòu)造參數(shù)public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)- int corePoolSize:該線程池中核心線程最大數(shù)量.默認情況下,即使核心線程處于空閑狀態(tài)也不會被銷毀.除非通過allowCoreThreadTimeOut(true),則核心線程在空閑時間達到keepAliveTime時會被銷毀
- int maximumPoolSize:該線程池中線程最大數(shù)量
- long keepAliveTime:該線程池中非核心線程被銷毀前最大空閑時間,時間單位由unit決定.默認情況下核心線程即使空閑也不會被銷毀,在調(diào)用allowCoreThreadTimeOut(true)后,該銷毀時間設(shè)置也適用于核心線程
- TimeUnit unit:keepAliveTime/被銷毀前最大空閑時間的單位
- BlockingQueue<Runnable> workQueue:該線程池中的任務(wù)隊列.維護著等待被執(zhí)行的Runnable對象.BlockingQueue有幾種類型,下面會詳述
- ThreadFactory threadFactory:創(chuàng)建新線程的工廠.一般情況使用Executors.defaultThreadFactory()即可.當然也可以自定義.
- RejectedExecutionHandler handler:拒絕策略.當需要創(chuàng)建的線程數(shù)量達到maximumPoolSize并且等待執(zhí)行的Runnable數(shù)量超過了任務(wù)隊列的容量,該如何處理.
3.2:當1個任務(wù)被放進線程池,ThreadPoolExecutor具體執(zhí)行策略如下:
- 如果線程數(shù)量沒有達到corePoolSize,有核心線程空閑則核心線程直接執(zhí)行,沒有空閑則直接新建核心線程執(zhí)行任務(wù);
- 如果線程數(shù)量已經(jīng)達到corePoolSize,且核心線程無空閑,則將任務(wù)添加到等待隊列;
- 如果等待隊列已滿,則新建非核心線程執(zhí)行該任務(wù);
- 如果等待隊列已滿且總線程數(shù)量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.
3.3:阻塞隊列/BlockingQueue<Runnable> workQueue
- BlockingQueue有如下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
- SynchronousQueue:SynchronousQueue的容量是0,不存儲任何Runnable實例.新任務(wù)到來會直接嘗試交給線程執(zhí)行,如所有線程都在忙就創(chuàng)建新線程執(zhí)行該任務(wù).
- LinkedBlockingQueue:默認情況下沒有容量限制的隊列.
- ArrayBlockingQueue:一個有容量限制的隊列.
- DelayQueue:一個沒有容量限制的隊列.隊列中的元素必須實現(xiàn)了Delayed接口.元素在隊列中的排序按照當前時間的延遲值,延遲最小/最早要被執(zhí)行的任務(wù)排在隊列頭部,依次排序.延遲時間到達后執(zhí)行指定任務(wù).
- PriorityBlockingQueue:一個沒有容量限制的隊列.隊列中元素必須實現(xiàn)了Comparable接口.隊列中元素排序依賴元素的自然排序/compareTo的比較結(jié)果.
- 各種BlockingQueue的問題
1.SynchronousQueue缺點:因為不具備存儲元素的能力,因而當任務(wù)很頻繁時候,為了防止線程數(shù)量超標,我們往往設(shè)置maximumPoolSize是Integer.MAX_VALUE,創(chuàng)建過多線程會導致OOM.《阿里巴巴Java開發(fā)手冊》中強調(diào)不能使用Executors直接創(chuàng)建線程池,就是對應(yīng)Android源碼中newCachedThreadPool和newScheduledThreadPool,本質(zhì)上就是創(chuàng)建了maximumPoolSize為Integer.MAX_VALUE的ThreadPoolExecutor.
2.LinkedBlockingQueue因為沒有容量限制,所以我們使用LinkedBlockingQueue創(chuàng)建ThreadPoolExecutor,設(shè)置maximumPoolSize是無意義的,如果線程數(shù)量已經(jīng)達到corePoolSize,且核心線程都在忙,那么新來的任務(wù)會一直被添加到隊列中.只要核心線程無空閑則一直得不到被執(zhí)行機會.
3.DelayQueue和PriorityBlockingQueue也具有同樣的問題.所以corePoolSize必須設(shè)置合理,否則會導致超出核心線程數(shù)量的任務(wù)一直得不到機會被執(zhí)行.這兩類隊列分別適用于定時及優(yōu)先級明確的任務(wù).
3.4:RejectedExecutionHandler handler/拒絕策略有4種
1.hreadPoolExecutor.AbortPolicy:丟棄任務(wù),并拋出RejectedExecutionException異常.ThreadPoolExecutor默認就是使用AbortPolicy.
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務(wù),但不會拋出異常.
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄排在隊列頭部的任務(wù),不拋出異常,并嘗試重新執(zhí)行任務(wù).
4.ThreadPoolExecutor.CallerRunsPolicy:丟棄任務(wù),但不拋出異常,并將該任務(wù)交給調(diào)用此ThreadPoolExecutor的線程執(zhí)行. - int corePoolSize:該線程池中核心線程最大數(shù)量.默認情況下,即使核心線程處于空閑狀態(tài)也不會被銷毀.除非通過allowCoreThreadTimeOut(true),則核心線程在空閑時間達到keepAliveTime時會被銷毀
-
synchronized 的本質(zhì)
- 保證synchronized方法或者代碼塊內(nèi)部資源/數(shù)據(jù)的互斥訪問
- 即同一時間,由同一個Monitor監(jiān)視的代碼,最多只有1個線程在訪問
- 保證線程之間對監(jiān)視資源的數(shù)據(jù)同步.
- 任何線程在獲取Monitor后,會第一時間將共享內(nèi)存中的數(shù)據(jù)復(fù)制到自己的緩存中;
- 任何線程在釋放Monitor后,會第一時間將緩存中的數(shù)據(jù)復(fù)制到共享內(nèi)存中
- 保證synchronized方法或者代碼塊內(nèi)部資源/數(shù)據(jù)的互斥訪問
-
volatile
- 保證被volatile修飾的成員的操作具有原子性和同步性.相當于簡化版的synchronized
- 原子性就是線程間互斥訪問
- 同步性就是線程之間對監(jiān)視資源的數(shù)據(jù)同步
- volatile生效范圍:基本類型的直接復(fù)制賦值 + 引用類型的直接賦值
//引用類型的直接賦值操作有效 private volatile User u = U1; //修改引用類型的屬性,則不是原子性的,volatile無效 U1.name = "吊炸天" //對引用類型的直接賦值是原子性的 u = U2; private volatile int a = 0; private int b = 100; //volatile無法實現(xiàn)++/--的原子性 a++;- volatile型變量自增操作的隱患
- volatile類型變量每次在讀取的時候,會越過線程的工作內(nèi)存,直接從主存中讀取,也就不會產(chǎn)生臟讀
- ++自增操作,在Java對應(yīng)的匯編指令有三條
- 從主存讀取變量值到cpu寄存器
- 寄存器里的值+1
- 寄存器的值寫回主存
- 如果N個線程同時執(zhí)行到了第1步,那么最終變量會損失(N-1).第二步第三步只有一個線程是執(zhí)行成功.
- 對變量的寫操作不依賴于當前值,才能用volatile修飾.
- volatile型變量自增操作的隱患
- 保證被volatile修飾的成員的操作具有原子性和同步性.相當于簡化版的synchronized
-
針對num++這類復(fù)合類的操作,可以使用java并發(fā)包中的原子操作類原子操作類:AtomicInteger AtomicBoolean等來保證其原子性.
public static AtomicInteger num = new AtomicInteger(0); num.incrementAndGet();//原子性的num++,通過循環(huán)CAS方式
2.線程間交互
- 一個線程終結(jié)另一個線程
- Thread.stop不要用:
- 因為線程在運行過程中隨時有可能會被暫停切換到其他線程,stop的效果相當于切換到其他線程繼續(xù)執(zhí)行且以后再也不會切換回來.我們執(zhí)行A.stop的時候,完全無法預(yù)知A的run方法已經(jīng)執(zhí)行了多少,執(zhí)行百分比完全不可控.
下面的代碼,每次執(zhí)行最后打印的結(jié)果都不同,即我們完全不可預(yù)知調(diào)用stop時候當前線程執(zhí)行了百分之多少. 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:僅僅設(shè)置當前線程為被中斷狀態(tài).在運行的線程依然會繼續(xù)運行.
- Thread.isInterrupted:獲取當前線程是否被中斷
- Thread.interrupted():如果線程A調(diào)用了Thread.interrupted()
- 如果A之前已經(jīng)被中斷,調(diào)用Thread.interrupted()返回false,A已經(jīng)不是被中斷狀態(tài)
- 如果A之前不是被中斷狀態(tài),調(diào)用Thread.interrupted()返回true,A變成被中斷狀態(tài).
- 單純調(diào)用A.interrupt是無效果的,interrupt需要和isInterrupted聯(lián)合使用
- 用于我們希望線程處于被中斷狀態(tài)時結(jié)束運行的場景.
- interrupt和stop比較的優(yōu)點:stop后,線程直接結(jié)束,我們完全無法控制當前執(zhí)行到哪里;
interrupt后線程默認會繼續(xù)執(zhí)行,我們通過isInterrupted來獲取被中斷狀態(tài),只有被中斷且滿足我們指定條件才return,可以精確控制線程的執(zhí)行百分比.
private static void t2(){ Thread t = new Thread(){ @Override public void run() { for(int i=0;i<1000000;i++){ //檢查線程是否處于中斷狀態(tài),且檢查是否滿足指定條件 //如果不滿足指定條件,即使處于中斷狀態(tài)也繼續(xù)執(zhí)行. if(isInterrupted()&&i>800000){ //先做收尾工作 //return 結(jié)束 return; } System.out.println(""+i); } } }; t.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //調(diào)用了interrupt后,在run中監(jiān)查是否已經(jīng)被打斷,如果已經(jīng)被打斷,且滿足指定條件, //就return,線程就執(zhí)行完了 t.interrupt(); } ...... 799999 800000 Process finished with exit code 0 - InterruptedException:
- 如果線程A在sleep過程中被其他線程調(diào)用A.interrupt(),會觸發(fā)InterruptedException.
- 如果調(diào)用A.interrupt()時候,A并不在sleep狀態(tài),后面再調(diào)用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進行調(diào)用
- wait:
- 當1個線程A持有Monitor M.
- 此時調(diào)用M.wait,A會釋放M并處于等待狀態(tài).并記錄A在當前代碼執(zhí)行的位置Position.
- notify:
- 當調(diào)用M.notify(),就會喚醒1個因為調(diào)用M.wait()而處于等待狀態(tài)的線程
- 如果有A,B,C--多個線程都是因為調(diào)用M.wait()而處于等待狀態(tài),不一定哪個會被喚醒并嘗試獲取M
- notifyAll:
- 當調(diào)用M.notifyAll(),所有因為調(diào)用M.wait()而處于等待狀態(tài)的線程都被喚醒,一起競爭嘗試獲取M
- 調(diào)用notify/notifyAll被喚醒并獲取到M的線程A,會接著之前的代碼執(zhí)行位置Position繼續(xù)執(zhí)行下去
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("老子設(shè)置一下"); } }).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:老子設(shè)置一下 線程:Thread-2 printStr時間:1539247469944 str:老子設(shè)置一下
3.Executor、 AsyncTask、 HandlerThead、 IntentService 如何選擇
- HandlerThead就不要用,HandlerThead設(shè)計目的就是為了主界面死循環(huán)刷新界面,無其他應(yīng)用場景.
- 能用線程池就用線程池,因為最簡單.
- 涉及后臺線程推送任務(wù)到UI線程,可以使用Handler或AsyncTask
- Service:就是為了做后臺任務(wù),不要UI界面,需要持續(xù)存活.有復(fù)雜的需要長期存活/等待的場景使用Service.
- IntentService:屬于Service.當我們需要使用Service,且需要后臺代碼執(zhí)行完畢后該Service自動被銷毀,使用IntentService.
4.AsyncTask的內(nèi)存泄漏
- GC Roots:由堆外指向堆內(nèi)的引用,包括:
- Java方法棧幀中的局部變量
- 已加載類的靜態(tài)變量
- native代碼的引用
- 運行中的Java線程
- AsyncTask內(nèi)存泄漏本質(zhì):正在運行的線程/AsyncTask 在虛擬機中屬于GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的對象不能被回收.
- 所以AsyncTask和其他線程工具一樣,只要是使用線程,都有可能發(fā)生內(nèi)存泄漏,都要及時關(guān)閉,AsyncTask并不比其他工具更差.
- 如何避免AsyncTask內(nèi)存泄漏:使用弱引用解決AsyncTask在Activity銷毀后依然持有Activity引用的問題
5.RxJava.
講的太多了這里推薦1個專題RxJava2.x
下面記錄一下自己不太熟的幾點
- RxJava整體結(jié)構(gòu):
- 鏈的最上游:生產(chǎn)者Observable
- 鏈的最下游:觀察者Observer
- 鏈的中間多個節(jié)點:雙重角色.即是上一節(jié)點的觀察者Observer,也是下一節(jié)點的生產(chǎn)者Observable.
- Scheduler切換線程的原理:源碼跟蹤下去,實質(zhì)是通過Excutor實現(xiàn)了線程切換.
6.Android M對Profile GPU Rendering工具的更新
- Swap Buffers:CPU等待GPU處理的時間
- Command Issur:OpenGL渲染Display List所需要的時間
- Sync&Upload:通常表示的是準備當前界面上有待繪制的圖片所耗費的時間,為了減少該段區(qū)域的執(zhí)行時間,我們可以減少屏幕上的圖片數(shù)量或者是縮小圖片本身的大小
- Draw:測量繪制Display List的時間
- Measure & Layout:這里表示的是布局的onMeasure與onLayout所花費的時間.一旦時間過長,就需要仔細檢查自己的布局是不是存在嚴重的性能問題
- Animation:表示的是計算執(zhí)行動畫所需要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這里的執(zhí)行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執(zhí)行的過程中是不是觸發(fā)了讀寫操作等等
- Input Handling:表示的是系統(tǒng)處理輸入事件所耗費的時間,粗略等于對于的事件處理方法所執(zhí)行的時間.一旦執(zhí)行時間過長,意味著在處理用戶的輸入事件的地方執(zhí)行了復(fù)雜的操作
- Misc/Vsync Delay:如果稍加注意,我們可以在開發(fā)應(yīng)用的Log日志里面看到這樣一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。這意味著我們在主線程執(zhí)行了太多的任務(wù),導致UI渲染跟不上vSync的信號而出現(xiàn)掉幀的情況
9.Android性能優(yōu)化典范-第6季
1.啟動閃屏
- 當點擊桌面圖標啟動APP的時候,App會出現(xiàn)短暫的白屏,一直到第一個Activity的頁面的渲染加載完畢
- 為了消除白屏,我們可以為App入口Activity單獨設(shè)置theme.
- 在單獨設(shè)置的theme中設(shè)置android:background屬性為App的品牌宣傳圖片背景.
- 在代碼執(zhí)行到入口Activity的onCreate的時候設(shè)置為程序正常的主題.
styles.xml <!-- Base application theme. --> //Activity默認主題 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> //默認主題窗口背景設(shè)置為白色 <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單獨設(shè)置 <style name="ThemeSplash" parent="Theme.AppCompat.Light.NoActionBar"> //入口Activity初始窗口背景設(shè)置為品牌宣傳圖片 <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) { //在代碼執(zhí)行到入口Activity時候設(shè)置入口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提供對應(yīng)分辨率下的圖片,系統(tǒng)會自動匹配最合適分辨率的圖片執(zhí)行拉伸或壓縮的處理.
- 如果是只有1張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
- 所有的大背景圖片,統(tǒng)一放在mipmap-nodpi目錄,用一套1080P素材可以解決大部分手機適配問題,不用每個資源目錄下放一套素材
- 經(jīng)過試驗,不論ImageView寬高是否是wrap_content,只要圖片所在文件夾和當前設(shè)備分辨率不匹配,都會涉及到放大或壓縮,占用的內(nèi)存都會相應(yīng)的變化.尤其對于大圖,放在低分辨率文件夾下直接OOM.
具體原因:
郭霖:Android drawable微技巧,你所不知道的drawable的那些細節(jié)
當我們使用資源id來去引用一張圖片時,Android會使用一些規(guī)則來去幫我們匹配最適合的圖片。什么叫最適合的圖片?比如我的手機屏幕密度是xxhdpi,那么drawable-xxhdpi文件夾下的圖片就是最適合的圖片。因此,當我引用android_logo這張圖時,如果drawable-xxhdpi文件夾下有這張圖就會優(yōu)先被使用,在這種情況下,圖片是不會被縮放的。但是,如果drawable-xxhdpi文件夾下沒有這張圖時, 系統(tǒng)就會自動去其它文件夾下找這張圖了,優(yōu)先會去更高密度的文件夾下找這張圖片,我們當前的場景就是drawable-xxxhdpi文件夾,然后發(fā)現(xiàn)這里也沒有android_logo這張圖,接下來會嘗試再找更高密度的文件夾,發(fā)現(xiàn)沒有更高密度的了,這個時候會去drawable-nodpi文件夾找這張圖,發(fā)現(xiàn)也沒有,那么就會去更低密度的文件夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
總體匹配規(guī)則就是這樣,那么比如說現(xiàn)在終于在drawable-mdpi文件夾下面找到android_logo這張圖了,但是系統(tǒng)會認為你這張圖是專門為低密度的設(shè)備所設(shè)計的,如果直接將這張圖在當前的高密度設(shè)備上使用就有可能會出現(xiàn)像素過低的情況,于是系統(tǒng)自動幫我們做了這樣一個放大操作。
那么同樣的道理,如果系統(tǒng)是在drawable-xxxhdpi文件夾下面找到這張圖的話,它會認為這張圖是為更高密度的設(shè)備所設(shè)計的,如果直接將這張圖在當前設(shè)備上使用就有可能會出現(xiàn)像素過高的情況,于是會自動幫我們做一個縮小的操作
3.盡量復(fù)用已經(jīng)存在的圖片.
比如一張圖片O已經(jīng)存在,如果有View的背景就是O旋轉(zhuǎn)過后的樣子,可以直接用O創(chuàng)建RotateDrawable.然后將設(shè)置給View使用.
注意:RotateDrawable已經(jīng)重寫了其onLevelChange方法,所以一定要設(shè)置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.首先創(chuàng)建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對應(yīng)的Drawable實例,并設(shè)置level為10000
Drawable drawable = getResources().getDrawable(R.drawable.rotate_close);
drawable.setLevel(10000);
3.將Drawable設(shè)置為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.對于簡單/規(guī)則紋理的圖片,使用VectorDrawable來替代多個分辨率圖片.VectorDrawable 有很多注意事項,后面單獨一篇文章總結(jié)
10.網(wǎng)絡(luò)優(yōu)化
網(wǎng)絡(luò)優(yōu)化主要有幾個方面:降低網(wǎng)絡(luò)請求數(shù)量,降低單次請求響應(yīng)的數(shù)據(jù)量,在弱網(wǎng)環(huán)境下將非必要網(wǎng)絡(luò)請求延緩至網(wǎng)絡(luò)環(huán)境好的時候.
1.降低網(wǎng)絡(luò)請求數(shù)量:獲取同樣的數(shù)據(jù),多次網(wǎng)絡(luò)請求會增加電量消耗,且多次請求總體上將消耗服務(wù)端更多的時間及資源
- 接口Api設(shè)計要合理.可以將多個接口合并,多次請求顯示1個界面,改造后1個接口即可提供完整數(shù)據(jù).
- 根據(jù)具體場景實時性需求,在App中加入網(wǎng)絡(luò)緩存,在實時性有效區(qū)間避免重復(fù)請求:主要包括網(wǎng)絡(luò)框架和圖片加載框架的緩存.
2.降低單次請求的數(shù)據(jù)量
- 網(wǎng)絡(luò)接口Api在設(shè)計時候,去除多余的請求參數(shù)及響應(yīng)數(shù)據(jù).
- 網(wǎng)絡(luò)請求及響應(yīng)數(shù)據(jù)的傳輸開啟GZIP壓縮,降低傳輸數(shù)據(jù)量.
- okHttp對gzip的支持前面已記錄
- Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執(zhí)行序列化.
- Protocal Buffers網(wǎng)上有使用的方法,相對GSON有點繁瑣.如果對網(wǎng)絡(luò)傳輸量很敏感,可以考慮使用.其他幾種方案的文章不多.
- 網(wǎng)絡(luò)請求圖片,添加圖片寬高參數(shù),避免下載過大圖片增加流量消耗.
3.弱網(wǎng)環(huán)境優(yōu)化這塊沒有經(jīng)驗,直接看anly_jun的文章
App優(yōu)化之網(wǎng)絡(luò)優(yōu)化
文章中提到:用戶點贊操作, 可以直接給出界面的點贊成功的反饋, 使用JobScheduler在網(wǎng)絡(luò)情況較好的時候打包請求.
11.電量優(yōu)化
12.JobScheduler,AlarmManager和WakeLock
JobScheduler在網(wǎng)絡(luò)優(yōu)化中出現(xiàn)過,WakeLock涉及電量優(yōu)化,AlarmManager和WakeLock有相似,但側(cè)重點不同.
- WakeLock:比如一段關(guān)鍵邏輯T已經(jīng)在執(zhí)行,執(zhí)行未完成Android系統(tǒng)就進入休眠,會導致T執(zhí)行中斷.WakeLock目的就在于阻止Android系統(tǒng)進入休眠狀態(tài),保證T得以繼續(xù)執(zhí)行.
- 休眠過程中自定義的Timer、Handler、Thread、Service等都會暫停
- AlarmManager:Android系統(tǒng)自帶的定時器,可以將處于休眠狀態(tài)的Android系統(tǒng)喚醒
- 保證Android系統(tǒng)在休眠狀態(tài)下被及時喚醒,執(zhí)行 定時/延時/輪詢?nèi)蝿?wù)
- JobScheduler:JobScheduler目的在于將當下不緊急的任務(wù)延遲到后面更合適的某個時間來執(zhí)行.我們可以控制這些任務(wù)在什么條件下被執(zhí)行.
- JobScheduler可以節(jié)約Android設(shè)備當下網(wǎng)絡(luò),電量,CPU等資源.在指定資源充裕情況下再執(zhí)行"不緊要"的任務(wù).
JobScheduler:
Android Jobscheduler使用
Android開發(fā)筆記(一百四十三)任務(wù)調(diào)度JobScheduler
WakeLock:
Android WakeLock詳解
Android PowerManager.WakeLock使用小結(jié)
Android的PowerManager和PowerManager.WakeLock用法簡析
AlarmManager和WakeLock使用:
后臺任務(wù) - 保持設(shè)備喚醒狀態(tài)
13.性能檢測工具
1.Android Studio 3.2之后,Android Device Monitor已經(jīng)被移除.Android Device Monitor原先包含的工具由新的方案替代.Android Device Monitor
- DDMS:由Android Profiler代替.可以進行CPU,內(nèi)存,網(wǎng)絡(luò)分析.
- TraceView:可以通過Debug類在代碼中調(diào)用Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄兩者之間所有線程及線程中方法的耗時,生成.trace文件.通過abd命令可以將trace文件導出到電腦,通過CPU profiler分析.
- Systrace:可以通過命令行生成html文件,通過Chrome瀏覽器進行分析.
- 生成html文件已實現(xiàn).但文件怎么分析暫未掌握,看了網(wǎng)上一些文章說實話還是沒搞懂
- Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能查看每個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.我們可使用系統(tǒng)提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
- Network Traffic tool:由Network Profiler代替.
2.其中Android Profiler如何使用,直接看官網(wǎng)即可.Profile your app performance.
3.TraceView
-
TraceView可以直接通過CPU profiler中點擊Record按鈕后,任意時間后點擊Stop按鈕.即可生成trace文件.并可將.trace文件導出.
image
image - TraceView也可以通過Debug類在代碼中精確控制要統(tǒng)計哪個區(qū)間代碼/線程的CPU耗時.
這種用法是 anly_jun大神文章里學到的
代碼執(zhí)行完畢,會在Android設(shè)備中生成JetApp.trace文件.通過Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.tracepublic 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(); }
在JetApp.trace上點擊右鍵->Copy Path,將trace文件路徑復(fù)制下來.
Windows下cmd打開命令行,執(zhí)行 adb pull 路徑,即可trace文件導出到電腦.
image - trace文件分析很簡單.我們可以看到每個線程及線程中每個方法調(diào)用消耗的時間.
4.Layout Inspector很簡單,在App運行后,點擊Tools->Layout Inspector即可.
下面只看Window.OnFrameMetricsAvailableListener怎么用.
從Android 7.0 (API level 24)開始,Android引入Window.OnFrameMetricsAvailableList接口用于提供每一幀繪制各階段的耗時,數(shù)據(jù)源與GPU Profile相同.
public interface OnFrameMetricsAvailableListener {
void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,int dropCountSinceLastInvocation);
}
/**
* 包含1幀的周期內(nèi),渲染系統(tǒng)各個方法的耗時數(shù)據(jù).
*/
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:
- 通過調(diào)用this.getWindow().addOnFrameMetricsAvailableListener(@NonNull OnFrameMetricsAvailableListener listener,Handler handler)來添加監(jiān)聽.
- 通過調(diào)用this.getWindow().removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener)取消監(jiān)聽.
-
解析ConstraintLayout的性能優(yōu)勢中引用了Google使用OnFrameMetricsAvailableListener的例子android-constraint-layout-performance.其中Activity是Kotlin寫的,嘗試將代碼轉(zhuǎn)為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,則可以統(tǒng)一設(shè)置,不需要每個Activity單獨設(shè)置,推薦使用開源項目ActivityFrameMetrics
- 在Application的onCreate中設(shè)置單幀渲染總時間超過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設(shè)置完成運行App,出現(xiàn)單幀渲染總耗時超過指定時間,即可看到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中設(shè)置單幀渲染總時間超過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版本,在這個路徑下執(zhí)行python systrace.py *** 命令會報錯,提示你應(yīng)該安裝2.7
- Python安裝時候,要記得勾選"Add python.exe to Path".
- 這時候直接執(zhí)行python systrace.py ***命令還是會報錯:ImportError: No module named win32com
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 生成html及如何分析