[筆記]Android性能優(yōu)化 下

[筆記]Android性能優(yōu)化 上
[筆記]Android性能優(yōu)化 中
[筆記]Android性能優(yōu)化 下

8.Android性能優(yōu)化典范-第5季

多線程大部分內(nèi)容源自凱哥的課程,個人覺得比優(yōu)化典范寫得清晰得多

1.線程

  1. 線程就是代碼線性執(zhí)行,執(zhí)行完畢就結(jié)束的一條線.UI線程不會結(jié)束是因為其初始化完畢后會執(zhí)行死循環(huán),所以永遠不會執(zhí)行完畢.

  2. 如何簡單創(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種方式
      1. Executors.newCachedThreadPool():一般情況下使用newCachedThreadPool即可.
      2. 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();
        
      3. 《阿里巴巴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不會存在這種風險.
      4. 如何正確創(chuàng)建ThreadPoolExecutor:有點麻煩,晚點詳述
      5. ExecutorService的shutdown和shutdownNow
        1. shutdown:在調(diào)用shutdown之前ExecutorService中已經(jīng)啟動的線程,在調(diào)用shutdown后,線程如果執(zhí)行未結(jié)束會繼續(xù)執(zhí)行完畢并結(jié)束,但不會再啟動新的線程執(zhí)行新任務(wù).
        2. shutdownNow:首先停止啟動新的線程執(zhí)行新任務(wù);并嘗試結(jié)束所有正在執(zhí)行的線程,正在執(zhí)行的線程可能被終止也可能會繼續(xù)執(zhí)行完成.
  3. 如何正確創(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)
    
    1. int corePoolSize:該線程池中核心線程最大數(shù)量.默認情況下,即使核心線程處于空閑狀態(tài)也不會被銷毀.除非通過allowCoreThreadTimeOut(true),則核心線程在空閑時間達到keepAliveTime時會被銷毀
    2. int maximumPoolSize:該線程池中線程最大數(shù)量
    3. long keepAliveTime:該線程池中非核心線程被銷毀前最大空閑時間,時間單位由unit決定.默認情況下核心線程即使空閑也不會被銷毀,在調(diào)用allowCoreThreadTimeOut(true)后,該銷毀時間設(shè)置也適用于核心線程
    4. TimeUnit unit:keepAliveTime/被銷毀前最大空閑時間的單位
    5. BlockingQueue<Runnable> workQueue:該線程池中的任務(wù)隊列.維護著等待被執(zhí)行的Runnable對象.BlockingQueue有幾種類型,下面會詳述
    6. ThreadFactory threadFactory:創(chuàng)建新線程的工廠.一般情況使用Executors.defaultThreadFactory()即可.當然也可以自定義.
    7. RejectedExecutionHandler handler:拒絕策略.當需要創(chuàng)建的線程數(shù)量達到maximumPoolSize并且等待執(zhí)行的Runnable數(shù)量超過了任務(wù)隊列的容量,該如何處理.

    3.2:當1個任務(wù)被放進線程池,ThreadPoolExecutor具體執(zhí)行策略如下:

    1. 如果線程數(shù)量沒有達到corePoolSize,有核心線程空閑則核心線程直接執(zhí)行,沒有空閑則直接新建核心線程執(zhí)行任務(wù);
    2. 如果線程數(shù)量已經(jīng)達到corePoolSize,且核心線程無空閑,則將任務(wù)添加到等待隊列;
    3. 如果等待隊列已滿,則新建非核心線程執(zhí)行該任務(wù);
    4. 如果等待隊列已滿且總線程數(shù)量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.

    3.3:阻塞隊列/BlockingQueue<Runnable> workQueue

    1. BlockingQueue有如下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
    2. SynchronousQueue:SynchronousQueue的容量是0,不存儲任何Runnable實例.新任務(wù)到來會直接嘗試交給線程執(zhí)行,如所有線程都在忙就創(chuàng)建新線程執(zhí)行該任務(wù).
    3. LinkedBlockingQueue:默認情況下沒有容量限制的隊列.
    4. ArrayBlockingQueue:一個有容量限制的隊列.
    5. DelayQueue:一個沒有容量限制的隊列.隊列中的元素必須實現(xiàn)了Delayed接口.元素在隊列中的排序按照當前時間的延遲值,延遲最小/最早要被執(zhí)行的任務(wù)排在隊列頭部,依次排序.延遲時間到達后執(zhí)行指定任務(wù).
    6. PriorityBlockingQueue:一個沒有容量限制的隊列.隊列中元素必須實現(xiàn)了Comparable接口.隊列中元素排序依賴元素的自然排序/compareTo的比較結(jié)果.
    7. 各種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í)行.

  4. synchronized 的本質(zhì)

    1. 保證synchronized方法或者代碼塊內(nèi)部資源/數(shù)據(jù)的互斥訪問
      • 即同一時間,由同一個Monitor監(jiān)視的代碼,最多只有1個線程在訪問
    2. 保證線程之間對監(jiān)視資源的數(shù)據(jù)同步.
      • 任何線程在獲取Monitor后,會第一時間將共享內(nèi)存中的數(shù)據(jù)復(fù)制到自己的緩存中;
      • 任何線程在釋放Monitor后,會第一時間將緩存中的數(shù)據(jù)復(fù)制到共享內(nèi)存中
  5. volatile

    1. 保證被volatile修飾的成員的操作具有原子性和同步性.相當于簡化版的synchronized
      • 原子性就是線程間互斥訪問
      • 同步性就是線程之間對監(jiān)視資源的數(shù)據(jù)同步
    2. 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++;
      
      1. volatile型變量自增操作的隱患
        • volatile類型變量每次在讀取的時候,會越過線程的工作內(nèi)存,直接從主存中讀取,也就不會產(chǎn)生臟讀
        • ++自增操作,在Java對應(yīng)的匯編指令有三條
          1. 從主存讀取變量值到cpu寄存器
          2. 寄存器里的值+1
          3. 寄存器的值寫回主存
        • 如果N個線程同時執(zhí)行到了第1步,那么最終變量會損失(N-1).第二步第三步只有一個線程是執(zhí)行成功.
      2. 對變量的寫操作不依賴于當前值,才能用volatile修飾.
  6. 針對num++這類復(fù)合類的操作,可以使用java并發(fā)包中的原子操作類原子操作類:AtomicInteger AtomicBoolean等來保證其原子性.

    public static AtomicInteger num = new AtomicInteger(0);
    num.incrementAndGet();//原子性的num++,通過循環(huán)CAS方式
    

2.線程間交互

  1. 一個線程終結(jié)另一個線程
    1. 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();
      }
      
    2. Thread.interrupt:僅僅設(shè)置當前線程為被中斷狀態(tài).在運行的線程依然會繼續(xù)運行.
      1. Thread.isInterrupted:獲取當前線程是否被中斷
      2. Thread.interrupted():如果線程A調(diào)用了Thread.interrupted()
        1. 如果A之前已經(jīng)被中斷,調(diào)用Thread.interrupted()返回false,A已經(jīng)不是被中斷狀態(tài)
        2. 如果A之前不是被中斷狀態(tài),調(diào)用Thread.interrupted()返回true,A變成被中斷狀態(tài).
      3. 單純調(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
        
      4. InterruptedException:
        1. 如果線程A在sleep過程中被其他線程調(diào)用A.interrupt(),會觸發(fā)InterruptedException.
        2. 如果調(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
        
  2. 線程等待:wait,notifyAll,notify
    1. wait,notifyAll,notify是屬于Object的方法.用于線程等待的場景,需用Monitor進行調(diào)用
    2. wait:
      • 當1個線程A持有Monitor M.
      • 此時調(diào)用M.wait,A會釋放M并處于等待狀態(tài).并記錄A在當前代碼執(zhí)行的位置Position.
    3. notify:
      • 當調(diào)用M.notify(),就會喚醒1個因為調(diào)用M.wait()而處于等待狀態(tài)的線程
      • 如果有A,B,C--多個線程都是因為調(diào)用M.wait()而處于等待狀態(tài),不一定哪個會被喚醒并嘗試獲取M
    4. notifyAll:
      • 當調(diào)用M.notifyAll(),所有因為調(diào)用M.wait()而處于等待狀態(tài)的線程都被喚醒,一起競爭嘗試獲取M
    5. 調(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 如何選擇

  1. HandlerThead就不要用,HandlerThead設(shè)計目的就是為了主界面死循環(huán)刷新界面,無其他應(yīng)用場景.
  2. 能用線程池就用線程池,因為最簡單.
  3. 涉及后臺線程推送任務(wù)到UI線程,可以使用Handler或AsyncTask
  4. Service:就是為了做后臺任務(wù),不要UI界面,需要持續(xù)存活.有復(fù)雜的需要長期存活/等待的場景使用Service.
  5. IntentService:屬于Service.當我們需要使用Service,且需要后臺代碼執(zhí)行完畢后該Service自動被銷毀,使用IntentService.

4.AsyncTask的內(nèi)存泄漏

  1. GC Roots:由堆外指向堆內(nèi)的引用,包括:
    1. Java方法棧幀中的局部變量
    2. 已加載類的靜態(tài)變量
    3. native代碼的引用
    4. 運行中的Java線程
  2. AsyncTask內(nèi)存泄漏本質(zhì):正在運行的線程/AsyncTask 在虛擬機中屬于GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的對象不能被回收.
  3. 所以AsyncTask和其他線程工具一樣,只要是使用線程,都有可能發(fā)生內(nèi)存泄漏,都要及時關(guān)閉,AsyncTask并不比其他工具更差.
  4. 如何避免AsyncTask內(nèi)存泄漏:使用弱引用解決AsyncTask在Activity銷毀后依然持有Activity引用的問題

5.RxJava.

講的太多了這里推薦1個專題RxJava2.x

下面記錄一下自己不太熟的幾點

  1. RxJava整體結(jié)構(gòu):
    1. 鏈的最上游:生產(chǎn)者Observable
    2. 鏈的最下游:觀察者Observer
    3. 鏈的中間多個節(jié)點:雙重角色.即是上一節(jié)點的觀察者Observer,也是下一節(jié)點的生產(chǎn)者Observable.
  2. Scheduler切換線程的原理:源碼跟蹤下去,實質(zhì)是通過Excutor實現(xiàn)了線程切換.

6.Android M對Profile GPU Rendering工具的更新

image
  1. Swap Buffers:CPU等待GPU處理的時間
  2. Command Issur:OpenGL渲染Display List所需要的時間
  3. Sync&Upload:通常表示的是準備當前界面上有待繪制的圖片所耗費的時間,為了減少該段區(qū)域的執(zhí)行時間,我們可以減少屏幕上的圖片數(shù)量或者是縮小圖片本身的大小
  4. Draw:測量繪制Display List的時間
  5. Measure & Layout:這里表示的是布局的onMeasure與onLayout所花費的時間.一旦時間過長,就需要仔細檢查自己的布局是不是存在嚴重的性能問題
  6. Animation:表示的是計算執(zhí)行動畫所需要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這里的執(zhí)行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執(zhí)行的過程中是不是觸發(fā)了讀寫操作等等
  7. Input Handling:表示的是系統(tǒng)處理輸入事件所耗費的時間,粗略等于對于的事件處理方法所執(zhí)行的時間.一旦執(zhí)行時間過長,意味著在處理用戶的輸入事件的地方執(zhí)行了復(fù)雜的操作
  8. 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.啟動閃屏

  1. 當點擊桌面圖標啟動APP的時候,App會出現(xiàn)短暫的白屏,一直到第一個Activity的頁面的渲染加載完畢
  2. 為了消除白屏,我們可以為App入口Activity單獨設(shè)置theme.
    1. 在單獨設(shè)置的theme中設(shè)置android:background屬性為App的品牌宣傳圖片背景.
    2. 在代碼執(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. 如果是只有1張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
  2. 所有的大背景圖片,統(tǒng)一放在mipmap-nodpi目錄,用一套1080P素材可以解決大部分手機適配問題,不用每個資源目錄下放一套素材
  3. 經(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ù)端更多的時間及資源

  1. 接口Api設(shè)計要合理.可以將多個接口合并,多次請求顯示1個界面,改造后1個接口即可提供完整數(shù)據(jù).
  2. 根據(jù)具體場景實時性需求,在App中加入網(wǎng)絡(luò)緩存,在實時性有效區(qū)間避免重復(fù)請求:主要包括網(wǎng)絡(luò)框架和圖片加載框架的緩存.

2.降低單次請求的數(shù)據(jù)量

  1. 網(wǎng)絡(luò)接口Api在設(shè)計時候,去除多余的請求參數(shù)及響應(yīng)數(shù)據(jù).
  2. 網(wǎng)絡(luò)請求及響應(yīng)數(shù)據(jù)的傳輸開啟GZIP壓縮,降低傳輸數(shù)據(jù)量.
    • okHttp對gzip的支持前面已記錄
  3. Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執(zhí)行序列化.
    • Protocal Buffers網(wǎng)上有使用的方法,相對GSON有點繁瑣.如果對網(wǎng)絡(luò)傳輸量很敏感,可以考慮使用.其他幾種方案的文章不多.
  4. 網(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)化

anly_jun大神的:App優(yōu)化之電池省著用

12.JobScheduler,AlarmManager和WakeLock

JobScheduler在網(wǎng)絡(luò)優(yōu)化中出現(xiàn)過,WakeLock涉及電量優(yōu)化,AlarmManager和WakeLock有相似,但側(cè)重點不同.

  1. 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等都會暫停
  2. AlarmManager:Android系統(tǒng)自帶的定時器,可以將處于休眠狀態(tài)的Android系統(tǒng)喚醒
    • 保證Android系統(tǒng)在休眠狀態(tài)下被及時喚醒,執(zhí)行 定時/延時/輪詢?nèi)蝿?wù)
  3. 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

image
  1. DDMS:由Android Profiler代替.可以進行CPU,內(nèi)存,網(wǎng)絡(luò)分析.
  2. TraceView:可以通過Debug類在代碼中調(diào)用Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄兩者之間所有線程及線程中方法的耗時,生成.trace文件.通過abd命令可以將trace文件導出到電腦,通過CPU profiler分析.
  3. Systrace:可以通過命令行生成html文件,通過Chrome瀏覽器進行分析.
    • 生成html文件已實現(xiàn).但文件怎么分析暫未掌握,看了網(wǎng)上一些文章說實話還是沒搞懂
  4. Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能查看每個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.我們可使用系統(tǒng)提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
  5. Network Traffic tool:由Network Profiler代替.

2.其中Android Profiler如何使用,直接看官網(wǎng)即可.Profile your app performance.

3.TraceView

  1. TraceView可以直接通過CPU profiler中點擊Record按鈕后,任意時間后點擊Stop按鈕.即可生成trace文件.并可將.trace文件導出.


    image

    image
  2. TraceView也可以通過Debug類在代碼中精確控制要統(tǒng)計哪個區(qū)間代碼/線程的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();
        }
    
    代碼執(zhí)行完畢,會在Android設(shè)備中生成JetApp.trace文件.通過Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.trace

    在JetApp.trace上點擊右鍵->Copy Path,將trace文件路徑復(fù)制下來.

    Windows下cmd打開命令行,執(zhí)行 adb pull 路徑,即可trace文件導出到電腦.
    image
  3. 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) {
        ****
    }
}
  1. 在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
      
  2. 在Application中使用OnFrameMetricsAvailableListener,則可以統(tǒng)一設(shè)置,不需要每個Activity單獨設(shè)置,推薦使用開源項目ActivityFrameMetrics
    1. 在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());
          }
      }
      
    2. 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%)
      ****
      

5.Systrace:通過命令行生成html文件,通過Chrome瀏覽器進行分析

  1. 首先電腦要安裝python,這里有幾個坑:
    1. 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
    2. Python安裝時候,要記得勾選"Add python.exe to Path".
    3. 這時候直接執(zhí)行python systrace.py ***命令還是會報錯:ImportError: No module named win32com
  2. 生成html及如何分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容