<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # 線下 ## 流暢度 gfxinfo、開發者模式的GPU渲染、perfdog、LayoutInspect都提供了卡頓監控的能力,不過都是肉眼觀察數據判斷是否發生卡頓。 ## 慢函數 ### TraceView Traceview利用 Android Runtime 函數調用的 event 事件,將函數運行的耗時和調用關系寫入 trace 文件中。 由此可見,Traceview 屬于 instrument 類型,它可以用來查看整個過程有哪些函數調用,但是工具本身帶來的性能開銷過大,有時無法反映真實的情況。比如一個函數本身的耗時是 1 秒,開啟 Traceview 后可能會變成 5 秒,而且這些函數的耗時變化并不是成比例放大。 使用`Debug.startMethodTracing()`以及`Debug.stopMethodTracing()`可以在程序中動態開啟TraceView。 在 Android 5.0 之后,新增了`Debug.startMethodTracingSampling`方法,可以使用基于樣本的方式進行分析,以減少分析對運行時的性能影響。新增了 sample 類型后,就需要我們在開銷和信息豐富度之間做好權衡。 ### systrace [systrace](https://source.android.com/devices/tech/debug/systrace?hl=zh-cn)是 Android 4.1 新增的性能分析工具。我通常使用 systrace 跟蹤系統的 I/O 操作、CPU 負載、Surface 渲染、GC 等事件。 systrace 利用了 Linux 的[ftrace](https://source.android.com/devices/tech/debug/ftrace)調試工具,相當于在系統各個關鍵位置都添加了一些性能探針,也就是在代碼里加了一些性能監控的埋點。Android 在 ftrace 的基礎上封裝了[atrace](https://android.googlesource.com/platform/frameworks/native/+/master/cmds/atrace/atrace.cpp),并增加了更多特有的探針,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。 systrace 工具只能監控特定系統調用的耗時情況,所以它是屬于 sample 類型,而且性能開銷非常低。但是它不支持應用程序代碼的耗時分析,所以在使用時有一些局限性。 由于系統預留了`Trace.beginSection`接口來監聽應用程序的調用耗時,那我們有沒有辦法在 systrace 上面自動增加應用程序的耗時分析呢? 劃重點了,我們可以通過**編譯時給每個函數插樁**的方式來實現,也就是在重要函數的入口和出口分別增加`Trace.beginSection`和`Trace.endSection`。當然出于性能的考慮,我們會過濾大部分指令數比較少的函數,這樣就實現了在 systrace 基礎上增加應用程序耗時的監控。通過這樣方式的好處有: * 可以看到整個流程系統和應用程序的調用流程。包括系統關鍵線程的函數調用,例如渲染耗時、線程鎖,GC 耗時等。 * 性能損耗可以接受。由于過濾了大部分的短函數,而且沒有放大 I/O,所以整個運行耗時不到原來的兩倍,基本可以反映真實情況。 systrace 生成的也是 HTML 格式的結果,我們利用跟 Nanoscope 相似方式實現對反混淆的支持。 ![](https://img.kancloud.cn/87/d1/87d145a64562def1c6922379a47cd2d8_1920x490.png) # 線上 ## 卡頓監控 ### 主進程-Handle #### 替換 Looper 的 Printer Looper#loop 代碼片段 ~~~ public?static?void?loop() { ? ?... ? ?for?(;;) { ? ? ? ?... ? ? ? ?// This must be in a local variable, in case a UI event sets the logger ? ? ? ?Printer?logging?=?me.mLogging; ? ? ? ?if?(logging?!=?null) { ? ? ? ? ? ?logging.println(">>>>> Dispatching to "?+?msg.target?+?" "?+ ? ? ? ? ? ? ? ? ? ?msg.callback?+?": "?+?msg.what); ? ? ? ?} ? ? ? ?msg.target.dispatchMessage(msg); ? ? ? ?if?(logging?!=?null) { ? ? ? ? ? ?logging.println("<<<<< Finished to "?+?msg.target?+?" "?+?msg.callback); ? ? ? ?} ? ? ? ?... ? ?} } ~~~ 簡單實現 ~~~ class LooperMonitor implements Printer { @Override public void println(String x) { if (!mPrintingStarted) { mStartTimestamp = System.currentTimeMillis(); mStartThreadTimestamp = SystemClock.currentThreadTimeMillis(); mPrintingStarted = true; // 1:處理消息前 startDump(); } else { final long endTime = System.currentTimeMillis(); mPrintingStarted = false; if (isBlock(endTime)) { // 2:處理消息后,如果超時了就獲取堆棧并輸出 notifyBlockEvent(endTime); } stopDump(); } } private boolean isBlock(long endTime) { return endTime - mStartTimestamp > mBlockThresholdMillis; } private void startDump() { BlockCanaryInternals.getInstance().stackSampler.start(); } private void stopDump() { BlockCanaryInternals.getInstance().stackSampler.stop(); } } ~~~ 缺點 1. View的TouchEvent中的卡頓這種方案是無法監控的 2. IdleHandler的queueIdle()回調方法也是無法被監控的 3. SyncBarrier(同步屏障)的泄漏同樣無法被監控到 4. 需要使用idleHandler循環的檢測Looper.mLogging 5. 沒有開啟Looper的子線程,無法監控 優點 1. 真正有任務執行的時候才監控 #### 插入空消息到消息隊列 通過一個監控線程,每隔1秒向主線程消息隊列的頭部插入一條空消息。假設1秒后這個消息并沒有被主線程消費掉,說明阻塞消息運行的時間在0~1秒之間。換句話說,如果我們需要監控3秒卡頓,那在第4次輪詢中,頭部消息依然沒有被消費的話,就可以確定主線程出現了一次3秒以上的卡頓。 ![](https://img.kancloud.cn/3c/b1/3cb18fc80c74abe5bef2d74ba71cfe20_600x183.png) ### Choreographer#doFrame 間隔檢測 簡單代碼實現如下: ~~~ Choreographer.getInstance().postFrameCallback(new?Choreographer.FrameCallback() { ? ?@Override?? ? ? ?public?voiddoFrame(long?frameTimeNanos) { if(frameTimeNanos?\-?mLastFrameNanos?\>100) { ... } ? ? ? ?mLastFrameNanos?\=?frameTimeNanos; ? ? ? ?Choreographer.getInstance().postFrameCallback(this); } }); ~~~ ## 慢函數 ### 線程采樣 #### 高頻采樣 在事件進入時,開啟一個延時的定時任務,如果任務在規定時間內完成則取消掉,否則開始間隔52ms抓取堆棧對象,最多抓取3秒數據的堆棧。當事件執行結束時,如果總耗時超過卡頓閾值,則將抓取到的多個堆棧,進行合并,將合并后的堆棧樹進行上報,如下所示: ![](https://img.kancloud.cn/b4/dd/b4dd1052757b47ca927d3cca7ee36c1b_1176x642.png) 一個堆棧樹節點包含以下內容: 1. method:節點對應的方法,如 android.app.ActivityThread.performLaunchActivity 2. weight:節點方法的耗時權重,即該方法在整個消息執行過程中,堆棧數組中出現的個數 3. sliceIndex:節點方法所在的時間片集合,一個完整的堆棧為一個時間片(52ms) 4. children:節點方法下的子節點,即下一個執行的方法 ### 3.1.2 堆棧樹設計 ### 微信 ASM插樁 方案: 1. 為每個插樁的函數分配一個獨立 ID 2. 在方法前后插入了?MethodBeat.i/o 的方法 3. MethodBeat有個預先初始化好的數組 long\[\] 中 index 的位置(預先分配記錄數據的 buffer 長度為 100w,內存占用約 7.6M)。 4. 數組保存并當前執行的是 MethodBeat i或者o、mehtod id 及時間 offset 5. Choreographer 注冊監聽,在每一幀 doframe 回調時判斷距離上一幀的時間差是否超出閾值(卡頓),如果超出閾值,則獲取數組 index 前的所有數據(即兩幀之間的所有函數執行信息)進行分析上報。同時,我們在每一幀 doFrame 到來時,重置一個定時器,如果 5s 內沒有 cancel,則認為 ANR 發生,這時會主動取出當前記錄的 buffer 數據進行獨立分析上報, 優化點: 1. 掃描的函數是否只含有 PUT/READ FIELD 等簡單的指令,來過濾一些默認或匿名構造函數,以及 get/set 等簡單不耗時函數。 2. 時間不是實時獲取,而是每 5ms 去更新一個時間變量 缺點: Matrix的堆棧信息并不包含非系統堆棧信息,無法進行進行一些系統場景,比如Binder耗時,鎖耗時等問題,當其無法監聽系統堆棧時,其顯示效果如下 ### Facebook- Profilo 2018 年 3 月,Facebook 開源了一個叫[Profilo](https://github.com/facebookincubator/profilo)的庫,它收集了各大方案的優點,令我眼前一亮。具體來說有以下幾點: **第一,集成 atrace 功能**。ftrace 所有性能埋點數據都會通過 trace\_marker 文件寫入內核緩沖區,Profilo 通過 PLT Hook 攔截了寫入操作,選擇部分關心的事件做分析。這樣所有 systrace 的探針我們都可以拿到,例如四大組件生命周期、鎖等待時間、類校驗、GC 時間等。 **不過大部分的 atrace 事件都比較籠統,從事件“B|pid|activityStart”,我們并不知道具體是哪個 Activity 的創建**。同樣我們可以統計 GC 相關事件的耗時,但是也不知道為什么發生了這次 GC。 ![stuck_profilo](https://blog.yorek.xyz/assets/images/android/master/stuck_profilo.jpg) **第二,快速獲取 Java 堆棧。很多同學有一個誤區,覺得在某個線程不斷地獲取主線程堆棧是不耗時的。但是事實上獲取堆棧的代價是巨大的,它要暫停主線程的運行。** Profilo 的實現非常精妙,它實現類似 Native 崩潰捕捉的方式快速獲取 Java 堆棧,通過間隔發送 SIGPROF 信號,整個過程如下圖所示。 ![](https://img.kancloud.cn/60/11/60111d8fb3fa75a64a8a81ae86dc65f1_2480x2196.png) Signal Handler 捕獲到信號后,拿取到當前正在執行的 Thread,通過 Thread 對象可以獲取當前線程的 ManagedStack,ManagedStack 是一個單鏈表,它保存了當前的 ShadowFrame 或者 QuickFrame 棧指針,先依次遍歷 ManagedStack 鏈表,然后遍歷其內部的 ShadowFrame 或者 QuickFrame 還原一個可讀的調用棧,從而 unwind 出當前的 Java 堆棧。通過這種方式,可以實現線程一邊繼續跑步,我們還可以幫它做檢查,而且耗時基本忽略不計。代碼可以參照:[Profilo::unwind](https://github.com/facebookincubator/profilo/blob/master/cpp/profiler/unwindc/android_712/arm/unwinder.h)和[StackVisitor::WalkStack](http://androidxref.com/7.1.1_r6/xref/art/runtime/stack.cc#772)。 不用插樁、性能基本沒有影響、捕捉信息還全,那 Profilo 不就是完美的化身嗎?當然由于它利用了大量的黑科技,兼容性是需要注意的問題。它內部實現有大量函數的 Hook,unwind 也需要強依賴 Android Runtime 實現。Facebook 已經將 Profilo 投入到線上使用,但由于目前 Profilo 快速獲取堆棧功能依然不支持 Android 8.0 和 Android 9.0,鑒于穩定性問題,建議采取抽樣部分用戶的方式來開啟該功能。 ### 西瓜 通過偏移+校驗的方式讀取到 Thread List,使用 SusopendThraedByPeer 函數傳入目標THread 對象的 jobkect 完成對目標線程的掛起。 獲取到 ThreadList 和函數指針后,在棧回溯的前后調用 Suspend 和 Resume 即可完成一次跨線程棧回溯。 在 Suspend 和 Resume 之間,僅執行 WalkStack 的操作,堆棧記錄和其他操作則放在 Resume 后執行,保證線程被掛起的時間足夠短,這樣可以確保性能最優。線程掛起機制經過實踐驗證可以滿足現有需求,實際運行中也未發現無法攻克的問題。另外線程掛起相較于信號機制,可以將大部分操作放到 Resume 后執行,而信號機制記錄堆棧等操作需要在回調內做,額外增加耗時,所以最后決定選用線程掛起方案。 # 參考資料 [抖音 Android 性能優化系列:新一代全能型性能分析工具 Rhea](https://toutiao.io/posts/p99i0mc/preview) [西瓜視頻穩定性治理體系建設三:Sliver 原理及實踐](https://blog.csdn.net/ByteDanceTech/article/details/119621240) [Matrix TraceCanary -- 初戀·卡頓](https://mp.weixin.qq.com/s/W4-1tfepKg2XMYvVn62B-Q) [微信Android客戶端的卡頓監控方案](https://mp.weixin.qq.com/s/3dubi2GVW\_rVFZZztCpsKg) [ 面試官又來了:你的app卡頓過嗎?](https://juejin.cn/post/6844903949560971277)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看