丝袜人妻一区二区三区_少妇福利无码视频_亚洲理论片在线观看_一级毛片国产A级片

當前位置:首頁 > 攻略

【applicationisinterrupted】Android開發(fā)月收入10k和月收入20k的差異——性能優(yōu)化學習的Karton優(yōu)化

前言:

APPCarton對用戶體驗影響很大,如何成為Android開發(fā)的性能優(yōu)化高手,今天我們來看一下性能優(yōu)化—— Carton優(yōu)化。打開優(yōu)化大師的道路。學習資料地址請發(fā)送“核心筆記”或“手冊”直接收到!

卡頓現(xiàn)象:

如何定義Carton現(xiàn)象: APP的FPS平均值小于30,最小值小于24,表示應用程序發(fā)生Carton。

復制代碼行很難再現(xiàn),與發(fā)生場景密切相關。(所以我們要進行卡頓監(jiān)控,收集現(xiàn)場信息。)需要CPU的知識。(阿爾伯特愛因斯坦)。

最新的主流機型使用多級高能效CPU體系結構(多核分層體系結構)(從CPU到GPU再到AI芯片NPU),移動CPU的整體性能得到了飛躍的提高,使您能夠充分利用移動計算功能,降低高服務器成本。要評估CPU的性能,需要查看時鐘速度、內核數(shù)、高速緩存等參數(shù)。這是每秒執(zhí)行的浮點計算數(shù)和每秒執(zhí)行的命令數(shù)。Carton的原因(包括代碼、內存、繪圖、IO、CPU等)反映在CPU時間中,CPU時間可以分為用戶時間和系統(tǒng)時間。用戶時間:執(zhí)行基于用戶的應用程序代碼所用的時間。系統(tǒng)時間:執(zhí)行內核狀態(tài)系統(tǒng)調用所需的時間,包括I/|O、鎖定、中斷和其他系統(tǒng)調用時間。常用命令:ADB shell

//獲取CPU核心數(shù)

cat/sys/devices/system/CPU/possible

//獲取第一個CPU的最大頻率

cat/sys/devices/system/CPU/CPU 0/CPU freq/CPU info _ max _ freq

//獲取第二個CPU的最小頻率

cat/sys/devices/system/CPU/CPU 1/CPU freq/CPU info _ min _ freq

//整個系統(tǒng)的CPU使用率

貓/proc/[PID]/狀態(tài)

Top命令有助于確定CPU消耗較大的進程。

Vmstat命令可實時、動態(tài)地監(jiān)控操作系統(tǒng)的虛擬內存和CPU活動。

Strace命令跟蹤進程中的所有系統(tǒng)調用

可以通過Vmstat命令或/proc/[pid]/schedstat文件查看CPU上下文切換的次數(shù)

/proc/[pid]/stat //進程CPU使用率

/proc/[pid]/task/[tid]/stat //進程下每個線程的CPU使用率

/proc/[pid]/sched //與進程CPU時間表相關

/proc/loadavg //系統(tǒng)平均負載,uptime命令對應的文件

復制代碼否存在優(yōu)先級較低的線程,如優(yōu)先級較高的線程null。例如,主要線程等待后臺線程的鎖定CPU的三個問題:CPU資源冗余使用:算法效率低下。不使用緩存計算時使用的默認類型不正確(例如,int足夠,但long,計算壓力增加4倍)。爭用CPU資源:通過搶占主要線程的CPU資源,搶占音頻視頻的CPU資源。優(yōu)化有兩種方法。換句話說,盡量排除非核心業(yè)務的消費。優(yōu)化自身性能消耗,并將CPU負載轉換為GPU負載(例如,使用renderscript處理視頻中的圖像信息)。都是平等的,互相搶(三和尚不喝水)CPU資源利用率低的:磁盤和網(wǎng)絡I/O、鎖操作、睡眠等,為了鎖的優(yōu)化,往往會盡量減少鎖的范圍??D故障診斷工具

Traceview和systrace都是調查我們比較熟悉的卡頓的工具,在實現(xiàn)中可以除以兩個流派instrument:得到一段時間內所有函數(shù)的調用過程,在此期間可以分析函數(shù)調用過程,進一步分析要優(yōu)化的點。Sample:選擇性地采樣或觀察一些函數(shù)調用過程,在繼續(xù)分析之前,可以推斷過程的可疑之處。Traceview類型:instrument原則:使用Android Runtime函數(shù)調用的event事件,將執(zhí)行函數(shù)所需的時間和調用關系寫入trace文件。特征:可以確定整個過程中有哪些函數(shù)調用,但有時工具本身的性能開銷太大,無法反映真實情況。Android 5.0之后添加了sta

rtMethodTracingSampling方法,可以使用基于樣本的方式進行分析,以減少分析對運行時的性能影響。
  • 新增了 sample 類型后,就需要我們在開銷和信息豐富度之間做好權衡。
  • 無論是哪種的 Traceview 對 release 包支持的都不太好,例如無法反混淆
  • Nanoscope
  • 類型:instrument
  • 原理:直接修改 Android 虛擬機源碼,在ArtMethod執(zhí)行入口和執(zhí)行結束位置增加埋點代碼,將所有的信息先寫到內存,等到 trace 結束后才統(tǒng)一生成結果文件;
  • 特點:性能損耗較小,適合做啟動耗時的自動化分析,但是 trace 結束生成結果文件這一步需要的時間比較長。另一方面它可以支持分析任意一個應用,可用于做競品分析。但是它也有不少限制:
  • 需要自己刷 ROM,并且當前只支持 Nexus 6P,或者采用其提供的 x86 架構的模擬器;
  • 默認只支持主線程采集,其他線程需要[代碼手動設置]
  • 考慮到內存大小的限制,每個線程的內存數(shù)組只能支持大約 20 秒左右的時間段。
  • 我們可以每天定期去跑自動化啟動測試,查看是否存在新增的耗時點
  • systrace
  • 類型:sample
  • Android 4.1 新增的性能分析工具。我通常使用 systrace 跟蹤系統(tǒng)的 I/O 操作、CPU 負載、Surface 渲染、GC 等事件。
  • 特點:只能監(jiān)控特定系統(tǒng)調用的耗時情況,性能開銷低,但不支持應用程序代碼的耗時分析;但是系統(tǒng)預留了Trace.beginSection接口來監(jiān)聽應用程序的調用耗時,我們可以通過編譯時給每個函數(shù)插樁的方式來實現(xiàn)在 systrace 基礎上增加應用程序耗時的監(jiān)控
  • Simpleperf
  • 類型:sample
  • 如果我們想分析 Native 函數(shù)的調用,上面的三個工具都不能滿足這個需求,Android 5.0 新增了Simpleperf性能分析工具
  • 利用 CPU 的性能監(jiān)控單元(PMU)提供的硬件 perf 事件,可以看到所有的 Native 代碼的耗時,同時封裝了 systrace 的監(jiān)控功能
  • Android Studio 3.2 也在 Profiler 中直接支持 Simpleperf
    • 匯總一下
      • 如果需要分析 Native 代碼的耗時,可以選擇 Simpleperf;
      • 如果想分析系統(tǒng)調用,可以選擇 systrace;
      • 如果想分析整個程序執(zhí)行流程的耗時,可以選擇 Traceview 或者插樁版本的 systrace。
    1. 可視化方法
    • Android Studio 3.2 的 Profiler 中直接集成了幾種性能分析工具:
      • Sample Java Methods 的功能類似于 Traceview 的 sample 類型
      • Trace Java Methods 的功能類似于 Traceview 的 instrument 類型
      • Trace System Calls 的功能類似于 systrace
      • SampleNative (API Level 26+) 的功能類似于 Simpleperf
    • 雖然不夠全面和強大,但大大降低了開發(fā)者的使用門檻
    • 分析結果的展示方式:這些分析工具都支持了 Call Chart 和 Flame Chart 兩種展示方式
      • Call Chart 是 Traceview 和 systrace 默認使用的展示方式,按照應用程序的函數(shù)執(zhí)行順序來展示,適合用于分析整個流程的調用
      • Flame Chart 也就是大名鼎鼎的火焰圖,以一個全局的視野來看待一段時間的調用分布,時間和空間兩個維度上的信息融合在一張圖上
    1. StrictMode
    2. 是Android 2.3引入的一個工具類,它被稱為嚴苛模式,是Android提供的一種運行時檢測機制,可以用來幫助開發(fā)人員用來檢測代碼中一些不規(guī)范的問題。
    3. 主要用來檢測兩大問題:
      1. 線程策略: 檢測內容是一些自定義的耗時調用、磁盤讀取操作以及網(wǎng)絡請求等;
      2. 虛擬機策略: 檢測內容包括Activity泄漏,SqLite對象泄漏,檢測實例數(shù)量;
    4. 使用:在Application的onCreate方法中對StrictMode進行統(tǒng)一配置,在日志輸出欄中注意使用“StrictMode”關鍵字過濾出對應的log即可
    5. private void initStrictMode() {
      // 1、設置Debug標志位,僅僅在線下環(huán)境才使用StrictMode
      if ) {
      // 2、設置線程策略
      S(new S()
      .detectCustomSlowCalls() //API等級11,使用S
      .detectDiskReads()
      .detectDiskWrites()
      .detectNetwork() // or .detectAll() for all detectable problems
      .penaltyLog() //在Logcat 中打印違規(guī)異常信息
      //.penaltyDialog() //也可以直接跳出警報dialog
      //.penaltyDeath() //或者直接崩潰
      .build());
      // 3、設置虛擬機策略
      S(new S()
      .detectLeakedSqlLiteObjects()
      // 給Person對象的實例數(shù)量限制為1
      .setClassInstanceLimi, 1)
      .detectLeakedClosableObjects() //API等級11
      .penaltyLog()
      .build());
      }
      }
      復制代碼

    卡頓監(jiān)控

    1. 消息隊列
    2. 方式1:通過替換 Looper 的 Printer 實現(xiàn);
      1. 首先,我們需要使用Loo().setMessageLogging()去設置我們自己的Printer 實現(xiàn)類去打印輸出logging。這樣,在每個message執(zhí)行的之前和之后都會調用我們設置的這個Printer實現(xiàn)類。
      2. 如果我們匹配到">>>>> Dispatching to "之后,我們就可以執(zhí)行一行代碼:也就是在指定的時間閾值之后, 我們在子線程去執(zhí)行一個任務,這個任務就是去獲取當前主線程的堆棧信息以及當前的一些場景信息,比如:內存大小、電腦、網(wǎng)絡狀態(tài)等。
      3. 如果在指定的閾值之內匹配到了"<<<<< Finished to ",那么說明message就被執(zhí)行完成了, 則表明此時沒有產生我們認為的卡頓效果,那我們就可以將這個子線程任務取消掉
    3. 方式2:通過一個監(jiān)控線程,每隔 1 秒向主線程消息隊列的頭部插入一條空消息; 如果我們需要監(jiān)控 3 秒卡頓,那在第 4 次輪詢中頭部消息依然沒有被消費的話,就可以確定主線程出現(xiàn)了一次 3 秒以上的卡頓;
    4. 插裝
    5. 基于消息隊列的卡頓監(jiān)控并不準確,正在運行的函數(shù)有可能并不是真正耗時的函數(shù);
    6. 假設一個消息循環(huán)里面順序執(zhí)行了 A、B、C 三個函數(shù),當整個消息執(zhí)行超過 3 秒時,因為函數(shù) A 和 B 已經(jīng)執(zhí)行完畢,
      我們只能得到的正在執(zhí)行的函數(shù) C 的堆棧,事實上它可能并不耗時,不過對于線上大數(shù)據(jù)來說,因為函數(shù) A 和 B 相對
      比較耗時,所以抓取到它們的概率會更大一些,通過后臺聚合后捕獲到函數(shù) A 和 B 的卡頓日志會更多一些;
      如果跟 Traceview 一樣,可以拿到整個卡頓過程所有運行函數(shù)的耗時,就可以明確知道其實函數(shù) A 和 B 才是造成卡頓的主要原因;
      那能否利用 Android Runtime 函數(shù)調用的回調事件,做一個自定義的 Traceview++ 呢?
      復制代碼
    7. 需要使用 Inline Hook 技術。我們可以實現(xiàn)類似 Nanoscope 先寫內存的方案;需要注意兩點:
    8. 避免方法數(shù)暴增
    9. 過濾簡單的函數(shù)
    10. 實現(xiàn)參考:微信的Matrix
    11. 雖然插樁方案對性能的影響總體還可以接受,但只會在灰度包使用;
    12. 短板:只能監(jiān)控應用內自身的函數(shù)耗時,無法監(jiān)控系統(tǒng)的函數(shù)調用,整個堆??雌饋砗孟瘛叭笔Я恕币徊糠?/li>
    13. Profilo
    14. 參考了JVM的 AsyncGetCallTrace 思路,然后適配 Android Runtime 的實現(xiàn)
    15. Facebook 開源庫,它收集了各大方案的優(yōu)點
      1. 集成 atrace 功能
      2. ftrace 所有性能埋點數(shù)據(jù)都會通過 trace_marker 文件寫入內核緩沖區(qū),Profilo 通過
        PLT Hook 攔截了寫入操作,選擇部分關心的事件做分析。這樣所有 systrace 的探針我們
        都可以拿到,例如四大組件生命周期、鎖等待時間、類校驗、GC 時間等。
        復制代碼
      3. 快速獲取 Java 堆棧
      4. 獲取堆棧的代價是巨大的,它要暫停主線程的運行,Profilo 的實現(xiàn)非常精妙,它實現(xiàn)類似
      5. Native 崩潰捕捉的方式快速獲取 Java 堆棧,通過間隔發(fā)送 SIGPROF 信號;
    16. AndroidPerformanceMonitor
    17. 一個非侵入式的性能監(jiān)控組件,可以通過通知的形式彈出卡頓信息。
    18. 優(yōu)勢:非侵入式,方便精準,能夠定位到代碼的某一行代碼。
    19. 使用:
    20. //1. build.gradle下配置它的依賴
      api 'com.gi;
      // 僅在debug包啟用BlockCanary進行卡頓監(jiān)控和提示的話,可以這么用
      debugApi 'com.gi;
      //2. Application的onCreate方法中開啟卡頓監(jiān)控
      BlockCanary.install(this, new AppBlockCanaryContext()).start();
      //3.繼承BlockCanaryContext類去實現(xiàn)自己的監(jiān)控配置上下文類
      /**
      * @Author: LiuJinYang
      * @CreateDate: 2020/12/9
      */
      public class AppBlockCanaryContext extends BlockCanaryContext {
      // 實現(xiàn)各種上下文,包括應用標識符,用戶uid,網(wǎng)絡類型,卡頓判斷闕值,Log保存位置等等

      /**
      * 提供應用的標識符
      *
      * @return 標識符能夠在安裝的時候被指定,建議為 version + flavor.
      */
      @Override
      public String provideQualifier() {
      return "unknown";
      }

      /**
      * 提供用戶uid,以便在上報時能夠將對應的
      * 用戶信息上報至服務器
      *
      * @return user id
      */
      @Override
      public String provideUid() {
      return "uid";
      }

      /**
      * 提供當前的網(wǎng)絡類型
      *
      * @return {@link String} like 2G, 3G, 4G, wifi, etc.
      */
      @Override
      public String provideNetworkType() {
      return "unknown";
      }

      /**
      * 配置監(jiān)控的時間區(qū)間,超過這個時間區(qū)間 ,BlockCanary將會停止, use
      * with {@code BlockCanary}'s isMonitorDurationEnd
      *
      * @return monitor last duration (in hour)
      */
      @Override
      public int provideMonitorDuration() {
      return -1;
      }

      /**
      * 指定判定為卡頓的閾值threshold (in millis),
      * 你可以根據(jù)不同設備的性能去指定不同的閾值
      *
      * @return threshold in mills
      */
      @Override
      public int provideBlockThreshold() {
      return 1000;
      }

      /**
      * 設置線程堆棧dump的間隔, 當阻塞發(fā)生的時候使用, BlockCanary 將會根據(jù)
      * 當前的循環(huán)周期在主線程去dump堆棧信息
      * <p>
      * 由于依賴于Looper的實現(xiàn)機制, 真實的dump周期
      * 將會比設定的dump間隔要長(尤其是當CPU很繁忙的時候).
      * </p>
      *
      * @return dump interval (in millis)
      */
      @Override
      public int provideDumpInterval() {
      return provideBlockThreshold();
      }

      /**
      * 保存log的路徑, 比如 "/blockcanary/", 如果權限允許的話,
      * 會保存在本地sd卡中
      *
      * @return path of log files
      */
      @Override
      public String providePath() {
      return "/blockcanary/";
      }

      /**
      * 是否需要通知去通知用戶發(fā)生阻塞
      *
      * @return true if need, else if not need.
      */
      @Override
      public boolean displayNotification() {
      return true;
      }

      /**
      * 用于將多個文件壓縮為一個.zip文件
      *
      * @param src files before compress
      * @param dest files compressed
      * @return true if compression is successful
      */
      @Override
      public boolean zip(File[] src, File dest) {
      return false;
      }

      /**
      * 用于將已經(jīng)被壓縮好的.zip log文件上傳至
      * APM后臺
      *
      * @param zippedFile zipped file
      */
      @Override
      public void upload(File zippedFile) {
      throw new UnsupportedOperationException();
      }

      /**
      * 用于設定包名, 默認使用進程名,
      *
      * @return null if simply concern only package with process name.
      */
      @Override
      public List<String> concernPackages() {
      return null;
      }

      /**
      * 使用 @{code concernPackages}方法指定過濾的堆棧信息
      *
      * @return true if filter, false it not.
      */
      @Override
      public boolean filterNonConcernStack() {
      return false;
      }

      /**
      * 指定一個白名單, 在白名單的條目將不會出現(xiàn)在展示阻塞信息的UI中
      *
      * @return return null if you don't need white-list filter.
      */
      @Override
      public List<String> provideWhiteList() {
      LinkedList<String> whiteList = new LinkedList<>();
      w("org.chromium");
      return whiteList;
      }

      /**
      * 使用白名單的時候,是否去刪除堆棧在白名單中的文件
      *
      * @return true if delete, false it not.
      */
      @Override
      public boolean deleteFilesInWhiteList() {
      return true;
      }

      /**
      * 阻塞攔截器, 我們可以指定發(fā)生阻塞時應該做的工作
      */
      @Override
      public void onBlock(Context context, BlockInfo blockInfo) {

      }
      }
      復制代碼

    其他監(jiān)控

    • 除了主線程的耗時過長之外,我們還有哪些卡頓問題需要關注呢?
    • Android Vitals 是 Google Play 官方的性能監(jiān)控服務,涉及卡頓相關的監(jiān)控有 ANR、啟動、幀率三個
    1. 幀率
    2. 業(yè)界都使用 Choreographer 來監(jiān)控應用的幀率;
    3. 需要排除掉頁面沒有操作的情況,應該只在界面存在繪制的時候才做統(tǒng)計;
    4. // 監(jiān)聽界面是否存在繪制行為
      getWindow().getDecorView().getViewTreeObserver().addOnDrawListener
      復制代碼
    5. 平均幀率: 衡量界面流暢度;
    6. 凍幀率:計算發(fā)生凍幀時間在所有時間的占比;
    7. 凍幀:Android Vitals 將連續(xù)丟幀超過 700 毫秒定義為凍幀,也就是連續(xù)丟幀 42 幀以上;
    8. 出現(xiàn)丟幀的時候,我們可以獲取當前的頁面信息、View 信息和操作路徑上報后臺,降低二次排查的難度
    9. 生命周期監(jiān)控
    10. Activity、Service、Receiver 組件生命周期的耗時和調用次數(shù)也是我們重點關注的性能問題;
    11. 如:Activity 的 onCreate() 不應該超過 1 秒,不然會影響用戶看到頁面的時間
    12. 對于組件生命周期應采用更嚴格地監(jiān)控,全量上報,在后臺查看各個組件各個生命周期的啟動時間和啟動次數(shù);
    13. 除了四大組件的生命周期,我們還需要監(jiān)控各個進程生命周期的啟動次數(shù)和耗時;
    14. 生命周期監(jiān)控推薦使用編譯時插樁的方式,如 Aspect、ASM 和 ReDex 三種插樁技術;
    15. 線程監(jiān)控
    16. Java 線程管理是很多應用非常頭痛的事情,應用啟動過程就已經(jīng)創(chuàng)建了幾十上百個線程。而且大部分的線程都沒有經(jīng)過線程池管理,都在自由自在地狂奔著;
    17. 另外一方面某些線程優(yōu)先級或者活躍度比較高,占用了過多的 CPU。這會降低主線程 UI 響應能力,我們需要特別針對這些線程做重點的優(yōu)化。
    18. 對于線程需要監(jiān)控兩點
      1. 線程數(shù)量,以及創(chuàng)建線程的方式:可以通過 got hook 線程的 nativeCreate() 函數(shù),主要用于進行線程收斂,也就是減少線程數(shù)量。
      2. 監(jiān)控線程的用戶時間 utime、系統(tǒng)時間 stime 和優(yōu)先級
    • 導致卡頓的原因會有很多,比如函數(shù)非常耗時、I/O 非常慢、線程間的競爭或者鎖等。其實很多時候卡頓問題并不難解決,相較解決來說,更困難的是如何快速發(fā)現(xiàn)這些卡頓點,以及通過更多的輔助信息找到真正的卡頓原因。

    卡頓現(xiàn)場

    • 以A函數(shù)耗時為例進行分析
    1. 方案一: java實現(xiàn):
    2. 通過源碼可以發(fā)現(xiàn),AssetManager 內部有大量的 synchronized 鎖;
    3. 步驟1: 獲得 Java 線程狀態(tài):通過T獲取 , 證實當時主線程是 BLOCKED 狀態(tài);
    4. //WAITING、TIME_WAITING 和 BLOCKED 都是需要特別注意的狀態(tài);
      //BLOCKED 是指線程正在等待獲取鎖,對應的是下面代碼中的情況一;
      //WAITING 是指線程正在等待其他線程的“喚醒動作”,對應的是代碼中的情況二;
      synchronized (object) { // 情況一:在這里卡住 --> BLOCKED
      doSomething();
      object.wait(); // 情況二:在這里卡住 --> WAITING
      }
      //不過當一個線程進入 WAITING 狀態(tài)時,它不僅會釋放 CPU 資源,還會將持有的 object 鎖也同時釋放。
      復制代碼
    5. 步驟2:獲得所有線程堆棧:通過T()獲得
    6. 注意:在 Android 7.0,getAllStackTraces 是不會返回主線程的堆棧的
    7. 通過分析收集上來的卡頓日志,發(fā)現(xiàn)跟 AssetManager 相關的線程是BackgroundHandler
    8. "BackgroundHandler" RUNNABLE
      at android.con
      at com.
      //通過查看A的確發(fā)現(xiàn)是使用了同一個 synchronized 鎖,而 list 函數(shù)需要遍歷整個目錄,耗時會比較久
      public String[] list(String path) throws IOException {
      synchronized (this) {
      ensureValidLocked();
      return nativeList(mObject, path);
      }
      }
      //另外一方面,“BackgroundHandler”線程屬于低優(yōu)先級后臺線程,這也是我們前面文章提到的不良現(xiàn)象,也就是主線程等待低優(yōu)先級的后臺線程
      復制代碼
    9. 方案2:ANR日志實現(xiàn)(SIGQUIT信號)
    10. 上面java實現(xiàn)方案還不錯,不過貌似ANR 日志的信息更加豐富,如果直接用 ANR 日志呢?
    11. // 線程名稱; 優(yōu)先級; 線程id; 線程狀態(tài)
      "main" prio=5 tid=1 Suspended
      // 線程組; 線程suspend計數(shù); 線程debug suspend計數(shù);
      | group="main" sCount=1 dsCount=0 obj=0x74746000 self=0xf4827400
      // 線程native id; 進程優(yōu)先級; 調度者優(yōu)先級;
      | sysTid=28661 nice=-4 cgrp=default sched=0/0 handle=0xf72cbbec
      // native線程狀態(tài); 調度者狀態(tài); 用戶時間utime; 系統(tǒng)時間stime; 調度的CPU
      | state=D schedstat=( 3137222937 94427228 5819 ) utm=218 stm=95 core=2 HZ=100
      // stack相關信息
      | stack=0xff717000-0xff719000 stackSize=8MB

      復制代碼
    12. Native 線程狀態(tài)
    13. 上面的 ANR 日志中“main”線程的狀態(tài)是 Suspended,Java 線程中的 6 種狀態(tài)中并不存在 Suspended 狀態(tài)啊?
      事實上,Suspended 代表的是 Native 線程狀態(tài)。怎么理解呢?在 Android 里面 Java 線程的運行都委托于一個
      Linux 標準線程 pthread 來運行,而 Android 里運行的線程可以分成兩種,一種是 Attach 到虛擬機的,一種是
      沒有 Attach 到虛擬機的,在虛擬機管理的線程都是托管的線程,所以本質上 Java 線程的狀態(tài)其實是 Native 線程
      的一種映射。不同的 Android 版本 Native 線程的狀態(tài)不太一樣,例如 Android 9.0 就定義了 27 種線程狀態(tài),
      它能更加明確地區(qū)分線程當前所處的情況。
      復制代碼
    14. 如何拿到卡頓時的 ANR 日志?
    15. 第一步:當監(jiān)控到主線程卡頓時,主動向系統(tǒng)發(fā)送 SIGQUIT 信號。
    16. 第二步:等待 /data/anr 文件生成。
    17. 第三步:文件生成以后進行上報
    18. 通過 ANR 日志,我們可以直接看到主線程的鎖是由“BackgroundHandler”線程持有。相比之下通過 getAllStackTraces 方法,我們只能通過一個一個線程進行猜測。
    19. // 堆棧相關信息
      at android.con(AssetManager.java:311)
      - waiting to lock <0x41ddc798> ) held by tid=66 (BackgroundHandler)
      at android.con(AssetManager.java:289)
      復制代碼
    20. 存在的問題:
    21. 可行性:很多高版本系統(tǒng)已經(jīng)沒有權限讀取 /data/anr 文件,需要刷ROM;
    22. 性能:獲取所有線程堆棧以及各種信息非常耗時,對于卡頓場景不一定合適,它可能會進一步加劇用戶的卡頓;
    23. 方案3:Hook實現(xiàn)
    24. 通過 Hook 方式我們實現(xiàn)了一套“無損”獲取所有 Java 線程堆棧與詳細信息的方法:
      1. 通過 fork 子進程方式實現(xiàn),這樣即使子進程崩潰了也不會影響我們主進程的運行,而且獲取所有線程堆棧這個過程可以做到完全不卡我們主進程;
      2. 通過libart.so、dlsym調用ThreadList::ForEach方法,拿到所有的 Native 線程對象。
      3. 遍歷線程對象列表,調用Thread::DumpState方法;
    25. 線上ANR監(jiān)控方式:
    26. ANR的幾種常見的類型:
      1. KeyDispatchTimeout:按鍵事件在5s的時間內沒有處理完成;
      2. BroadcastTimeout:廣播接收器在前臺10s,后臺60s的時間內沒有響應完成;
      3. ServiceTimeout:服務在前臺20s,后臺200s的時間內沒有處理完成;
    27. 之前的崩潰優(yōu)化中說了“怎么去發(fā)現(xiàn)應用中的 ANR 異常”,那么,有沒有更好的實現(xiàn)方式呢?
    28. ANR-WatchDog:一種非侵入式的ANR監(jiān)控組件,可以用于線上ANR的監(jiān)控
    29. //1. build.gradle下配置它的依賴
      implementation 'com.gi;
      //2. Application的onCreate方法中初始化ANR-WatchDog
      new ANRWatchDog().start();
      //3.源碼:ANRWatchDog實際上是繼承了Thread類,也就是它是一個線程,對于線程來說,最重要的就是其run方法
      private static final int DEFAULT_ANR_TIMEOUT = 5000;
      private volatile long _tick = 0;
      private volatile boolean _reported = false;

      private final Runnable _ticker = new Runnable() {
      @Override public void run() {
      _tick = 0;
      _reported = false;
      }
      };

      @Override
      public void run() {
      // 1、首先,將線程命名為|ANR-WatchDog|。
      setName("|ANR-WatchDog|");
      // 2、接著,聲明了一個默認的超時間隔時間,默認的值為5000ms。
      long interval = _timeoutInterval;
      // 3、然后,在while循環(huán)中通過_uiHandler去post一個_ticker Runnable。
      while (!isInterrupted()) {
      // 3.1 這里的_tick默認是0,所以needPost即為true。
      boolean needPost = _tick == 0;
      // 這里的_tick加上了默認的5000ms
      _tick += interval;
      if (needPost) {
      _uiHandler.post(_ticker);
      }
      // 接下來,線程會sleep一段時間,默認值為5000ms。
      try {
      T(interval);
      } catch (InterruptedException e) {
      _in(e);
      return ;
      }
      // 4、如果主線程沒有處理Runnable,即_tick的值沒有被賦值為0,則說明發(fā)生了ANR,第二個_reported標志位是為了避免重復報道已經(jīng)處理過的ANR。
      if (_tick != 0 && !_reported) {
      //noinspection ConstantConditions
      if (!_ignoreDebugger && () || Debug.waitingForDebugger())) {
      Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
      _reported = true;
      continue ;
      }
      interval = _anrIn(_tick);
      if (interval > 0) {
      continue;
      }
      final ANRError error;
      if (_namePrefix != null) {
      error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
      } else {
      // 5、如果沒有主動給ANR_Watchdog設置線程名,則會默認會使用ANRError的NewMainOnly方法去處理ANR。
      error = ANRError.NewMainOnly(_tick);
      }
      // 6、最后會通過ANRListener調用它的onAppNotResponding方法,其默認的處理會直接拋出當前的ANRError,導致程序崩潰。 _anrLi(error);
      interval = _timeoutInterval;
      _reported = true;
      }
      }
      }
      //但是在Java層去獲取所有線程堆棧以及各種信息非常耗時,對于卡頓場景不一定合適,它可能會進一步加劇用戶的卡頓。
      如果是對性能要求比較高的應用,可以通過Hook Native層的方式去獲得所有線程的堆棧信息,參考上面“方案3:Hook實現(xiàn)”
      復制代碼
    • 現(xiàn)場信息:
      • 能不能進一步讓卡頓的“現(xiàn)場信息”的比系統(tǒng) ANR 日志更加豐富?我們可以進一步增加這些信息:
        • CPU 使用率和調度信息:參考下面的課后作業(yè)1;
        • 內存相關信息:可以添加系統(tǒng)總內存、可用內存以及應用各個進程的內存等信息。如果開啟了 Debug.startAllocCounting 或者 atrace,還可以增加 GC 相關的信息。
        • I/O 和網(wǎng)絡相關: 還可以把卡頓期間所有的 I/O 和網(wǎng)絡操作的詳細信息也一并收集
    • Android 8.0 后,Android 虛擬機終于支持了 JVM 的JVMTI機制。Profiler 中內存采集等很多模塊也切換到這個機制中實現(xiàn),

    卡頓單點問題檢測方案

    • 常見的單點問題有主線程IPC(進程間通信)、DB操作等等
    • IPC單點問題檢測方案:
    • 在IPC的前后加上埋點。但是,這種方式不夠優(yōu)雅
    • 線下可以通過adb命令監(jiān)測
    • // 1、對IPC操作開始監(jiān)控
      adb shell am trace-ipc start
      // 2、結束IPC操作的監(jiān)控,同時,將監(jiān)控到的信息存放到指定的文件
      adb shell am trace-ipc stop -dump-file /data/local/tm
      // 3、將監(jiān)控到的ipc-trace導出到電腦查看
      adb pull /data/local/tm
      復制代碼
    • ARTHook
      • AspectJ只能針對于那些非系統(tǒng)方法,也就是我們App自己的源碼,或者是我們所引用到的一些jar、aar包;
      • ARTHook可以用來Hook系統(tǒng)的一些方法,因為對于系統(tǒng)代碼來說,我們無法對它進行更改,但是我們可以Hook住它的一個方法,在它的方法體里面去加上自己的一些代碼;
    • //通過PackageManager去拿到我們應用的一些信息,或者去拿到設備的DeviceId這樣的信息以及AMS相關的信息等,最終會調用到android.os.BinderProxy
      //在項目中的Application的onCreate方法中使用ARTHook對android.os.BinderProxy類的transact方法進行Hook
      try {
      Dex("android.os.BinderProxy"), "transact",
      int.class, Parcel.class, Parcel.class, int.class, new XC_MethodHook() {
      @Override
      protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
      LogHel( "BinderProxy beforeHookedMethod " + ().getSimpleName()
      + "\n" + Log.getStackTraceString(new Throwable()));
      (param);
      }
      });
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      }
      復制代碼
      • 除了IPC調用的問題之外,還有IO、DB、View繪制等一系列單點問題需要去建立與之對應的檢測方案
      • 對于卡頓問題檢測方案的建設,主要是利用ARTHook去完善線下的檢測工具,盡可能地去Hook相對應的操作,以暴露、分析問題。

    使用Lancet統(tǒng)計界面耗時

    • Lancet 是一個輕量級Android AOP框架,編譯速度快, 并且支持增量編譯.
    • 使用Demo如下
    //1. 在根目錄的 build.gradle 添加: dependencies { classpath 'me.ele:lancet-plugin:1.0.6' } //2. 在 app 目錄的'build.gradle' 添加 apply plugin: 'me.ele.lancet' dependencies { provided 'me.ele:lancet-base:1.0.6' } //3. 基礎API使用 /** * @Author: LiuJinYang * @CreateDate: 2020/12/10 */ public class LancetUtil { //@Proxy 指定了將要被織入代碼目標方法i, 織入方式為Proxy(將使用新的方法替換代碼里存在的原有的目標方法) @Proxy("i") //TargetClass指定了將要被織入代碼目標類 android.u @TargetClass("android.u") public static int anyName(String tag, String msg){ msg = "LJY_LOG: "+msg ; () 代表了 Log.i() 這個目標方法 return (int) Origin.call(); } } //4. 統(tǒng)計界面耗時 /** * @Author: LiuJinYang * @CreateDate: 2020/12/10 */ public class LancetUtil { public static ActivityRecord sActivityRecord; static { sActivityRecord = new ActivityRecord(); } @Insert(value = "onCreate",mayCreateSuper = true) @TargetClass(value = "android.;,scope = Sco) protected void onCreate(Bundle savedInstanceState) { = Sy(); // 調用當前Hook類方法中原先的邏輯 Origin.callVoid(); } @Insert(value = "onWindowFocusChanged",mayCreateSuper = true) @TargetClass(value = "android.;,scope = Sco) public void onWindowFocusChanged(boolean hasFocus) { = Sy(); LjyLogU(getClass().getCanonicalName() + " onWindowFocusChanged cost "+( - )); Origin.callVoid(); } public static class ActivityRecord { /** * 避免沒有僅執(zhí)行onResume就去統(tǒng)計界面打開速度的情況,如息屏、亮屏等等 */ public boolean isNewCreate; public long mOnCreateTime; public long mOnWindowsFocusChangedTime; } } 復制代碼

    卡頓分析

    • 在客戶端捕獲卡頓之后,最后數(shù)據(jù)需要上傳到后臺統(tǒng)一分析
    • 卡頓率
      • 評估卡頓的影響面:UV 卡頓率 = 發(fā)生過卡頓 UV / 開啟卡頓采集 UV,一個用戶如果命中采集,那么在一天內都會持續(xù)的采集數(shù)據(jù)
      • 評估卡頓的嚴重度:PV 卡頓率 = 發(fā)生過卡頓 PV / 啟動采集 PV,對于命中采集 PV 卡頓率的用戶,每次啟動都需要上報作為分母

    結語:

    卡頓優(yōu)化是Android開發(fā)高手之路,這篇文章可能有些地方講的不夠詳細的,或關于更多的性能優(yōu)化(啟動優(yōu)化、崩潰優(yōu)化、卡頓優(yōu)化、弱網(wǎng)優(yōu)化、內存優(yōu)化)等一系列優(yōu)化。資料獲?。赫埶叫盼野l(fā)送暗號“核心筆記”或“手冊”直接領取喲!

    希望共同進步,能幫助到各位Android的小伙伴。

    1.《【applicationisinterrupted】Android開發(fā)月收入10k和月收入20k的差異——性能優(yōu)化學習的Karton優(yōu)化》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡信息知識,僅代表作者本人觀點,與本網(wǎng)站無關,侵刪請聯(lián)系頁腳下方聯(lián)系方式。

    2.《【applicationisinterrupted】Android開發(fā)月收入10k和月收入20k的差異——性能優(yōu)化學習的Karton優(yōu)化》僅供讀者參考,本網(wǎng)站未對該內容進行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。

    3.文章轉載時請保留本站內容來源地址,http://f99ss.com/gl/2516146.html

    上一篇

    【GRF代表正式辭職】誰說男人不會玩“宮心計”?英雄聯(lián)盟GRF真的是罰1億的事嗎?

    下一篇

    【怪物獵人ol大劍】《奇怪的狩獵冰原》PC全套武器組裝系列:大刀騎士

    【applicationisinterrupted】《Alibaba微服務組件Nacos配置中心用戶指南》

    • 【applicationisinterrupted】《Alibaba微服務組件Nacos配置中心用戶指南》
    • 【applicationisinterrupted】《Alibaba微服務組件Nacos配置中心用戶指南》
    • 【applicationisinterrupted】《Alibaba微服務組件Nacos配置中心用戶指南》
    【applicationisinterrupted】Mybatis框架深入分析

    【applicationisinterrupted】Mybatis框架深入分析

    applicationisinterrupted相關介紹,Mybatis框架深入分析 學習Spring后,您學習了如何使用Bean在IoC容器中管理類。換句話說,現(xiàn)在可以以更方便的方式使用Mybatis框架??梢詫qlSessionFactory、Mapp...

    【applicationisinterrupted】不要小看“彈簧過濾器”。這些知識點必須掌握。

    • 【applicationisinterrupted】不要小看“彈簧過濾器”。這些知識點必須掌握。
    • 【applicationisinterrupted】不要小看“彈簧過濾器”。這些知識點必須掌握。
    • 【applicationisinterrupted】不要小看“彈簧過濾器”。這些知識點必須掌握。

    【applicationisinterrupted】G1 GC官方文件|最佳實踐(翻譯)

    • 【applicationisinterrupted】G1 GC官方文件|最佳實踐(翻譯)
    • 【applicationisinterrupted】G1 GC官方文件|最佳實踐(翻譯)
    • 【applicationisinterrupted】G1 GC官方文件|最佳實踐(翻譯)

    【applicationisinterrupted】異常日志,不能解決問題嗎?

    • 【applicationisinterrupted】異常日志,不能解決問題嗎?
    • 【applicationisinterrupted】異常日志,不能解決問題嗎?
    • 【applicationisinterrupted】異常日志,不能解決問題嗎?
    【applicationisinterrupted】高考題目作文一般使用詞匯記憶和范文4。

    【applicationisinterrupted】高考題目作文一般使用詞匯記憶和范文4。

    applicationisinterrupted相關介紹,31.主題:人際關系 常用詞匯: harmonious和諧的friendly友好的civilized文明的honest真誠的credible誠實的be public-spirited有公德心的be i...

    【applicationisinterrupted】服務首次參與

    • 【applicationisinterrupted】服務首次參與
    • 【applicationisinterrupted】服務首次參與
    • 【applicationisinterrupted】服務首次參與