android 性能優化—ANR

ANR產生原理

ANR(Application Not Responding)是 Android 對 “應用主線程卡死”系統級保護機制
輸入事件、廣播、服務 等在規定時間內未被處理完畢,SystemServer 會彈框并殺進程,防止整個系統跟著假死。

計時起點:事件離開 SystemServer 的瞬間

// InputDispatcher.cpp
void InputDispatcher::startDispatchCycleLocked(...) {// 主線程 5s 超時(默認)timeout = AMS::getInputDispatchingTimeout(timeout);mLooper->sendMessageDelayed(timeout, ...);
}
  • 按鍵/觸摸:5s

  • 前臺廣播:10s

  • 后臺廣播:60s

  • 前臺服務:20s

  • 后臺服務:200s

計時終點:App 主線程處理完事件并回執

App 主線程在 ViewRootImpl$WindowInputEventReceiver 里消費完事件后,會調用:

// ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {public void onInputEvent(InputEvent event) {... // 派發、measure、layout、drawfinishInputEvent(event, true); // ← 通知系統“我干完了”}
}

通過 Binder IPC 回到 InputDispatcher,取消第 1 步的超時任務。

超時未回執 → InputDispatcher 直接發SIGQUIT給 App

若第 2 步沒在規定時間內到達,native 層會:

// InputDispatcher.cpp
void InputDispatcher::onDispatchTimeoutLocked(...) {pid_t pid = connection->getPid();kill(pid, SIGQUIT);          // ① 發送 SIGQUIT(3)// 同時把超時信息寫入 dropbox
}
  • SIGQUIT 默認行為是終止進程(可被信號處理器捕獲)。

  • 系統利用這個信號觸發 Dalvik/ART dump trace,生成 /data/anr/traces.txt

SystemServer 收到“超時報告”→ 彈 ANR 對話框 → 殺進程

// ActivityManagerService.java
void inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {synchronized (this) {ProcessRecord proc = findProcessLocked(pid);if (proc == null) return;// ④ 彈出系統對話框mUiHandler.post(() -> showAnrDialog(proc, reason));// ⑤ 10s 后若用戶未選擇“等待”則殺進程mHandler.sendMessageDelayed(MSG_KILL_ANR, proc, 10_000);}
}
  • 用戶點 “關閉應用” → 立即 killProcessQuiet(pid)

  • 用戶點 “等待” → 取消殺進程,但計時器重新啟動(再卡一次就再來 5s)

場景超時信號最終動作
主線程死循環5sSIGQUIT彈框 + 殺進程
onCreate() 超 10s10s廣播超時同上
后臺服務 200s200s服務超時同上

一句話背板

事件離開系統時開始倒計時,主線程處理完回執取消;超時未回 → InputDispatcher 發 SIGQUIT,AMS 彈框并 10s 后殺進程;主線程別做長活,用子線程/Handler 異步。

ANR Log分析-binder

ANR 日志

----- pid 1887 at 2010-06-23 00:20:50.043623522+0800 -----
Cmd line: com.android.systemui
Build fingerprint: 'chery/cheryidcpro/cheryidcpro:14/UQ1A.240205.002/eng.autoli.20250416.022302:userdebug/dev-keys'
ABI: 'arm64'
Build type: optimized
suspend all histogram:	Sum: 41.458ms 99% C.I. 1us-1736.960us Avg: 73.768us Max: 5367us
DALVIK THREADS (133):
"main" prio=5 tid=1 Runnable| group="main" sCount=0 ucsCount=0 flags=0 obj=0x72c5fd28 self=0xb400007c496897b0| sysTid=1887 nice=-10 cgrp=top-app sched=0/0 handle=0x7e022f84f8| state=R schedstat=( 723564350007 288749055735 4147215 ) utm=44375 stm=27981 core=3 HZ=100| stack=0x7fd2ed3000-0x7fd2ed5000 stackSize=8188KB| held mutexes= "mutator lock"(shared held)native: #00 pc 0055be4c  /apex/com.android.art/lib64/libart.so (art::DumpNativeStack+172) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #01 pc 006773a8  /apex/com.android.art/lib64/libart.so (art::Thread::DumpStack const+340) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #02 pc 00694f38  /apex/com.android.art/lib64/libart.so (art::DumpCheckpoint::Run+916) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #03 pc 006782d4  /apex/com.android.art/lib64/libart.so (art::Thread::RunCheckpointFunction+184) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #04 pc 00729b28  /apex/com.android.art/lib64/libart.so (artJniMethodStart+88) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #05 pc 0020f8f8  /apex/com.android.art/lib64/libart.so (art_jni_method_start+40) (BuildId: 55b9536b61b755b7bae82e95aa498dab)at android.os.Parcel.nativeFreeBuffer(Native method)at android.os.Parcel.freeBuffer(Parcel.java:5249)at android.os.Parcel.recycle(Parcel.java:577)at android.bluetooth.IBluetooth$Stub$Proxy.getRemoteName(IBluetooth.java:2624)at android.bluetooth.BluetoothDevice.getName(BluetoothDevice.java:1751)at android.bluetooth.BluetoothDevice.getAlias(BluetoothDevice.java:1818)at com.android.settingslib.bluetooth.CachedBluetoothDevice.getName(CachedBluetoothDevice.java:599)at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1811)at android.app.LoadedApk$ReceiverDispatcher$Args.$r8$lambda$gDuJqgxY6Zb-ifyeubKeivTLAwk(unavailable:0)at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(unavailable:2)at android.os.Handler.handleCallback(Handler.java:958)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loopOnce(Looper.java:205)at android.os.Looper.loop(Looper.java:294)at android.app.ActivityThread.main(ActivityThread.java:8248)at java.lang.reflect.Method.invoke(Native method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:981)

上面這段 ANR trace 堪稱「教科書級」的 主線程-Binder 被拖死 現場,把整條證據鏈完整地擺在了我們面前

進程快照

----- pid 1887 at 2010-06-23 00:20:50+0800 -----
Cmd line: com.android.systemui
  • 發生 ANR 的正是 SystemUI(狀態欄、導航欄、藍牙彈窗都由它負責)。

  • 時間戳雖然是 2010,這是編譯時寫死的 mock 時間,不影響分析。

主線程狀態快照

"main" prio=5 tid=1 Runnable
state=R schedstat=( 723564350007 288749055735 4147215 )
held mutexes= "mutator lock"(shared held)
  • state=R 表示 正在 CPU 上跑,不是傳統「阻塞」。

  • utm+stm=72 s 已吃掉 72 s CPU 時間,說明 死循環 / 長時間原地打轉

  • 還抱著 mutator lock,GC 想停都停不下來,整個進程世界都被它拖住

調用棧快照(關鍵幀)

at android.os.Parcel.nativeFreeBuffer(Native method)
at android.os.Parcel.recycle(Parcel.java:577)
at android.bluetooth.IBluetooth$Stub$Proxy.getRemoteName(IBluetooth.java:2624)
at android.bluetooth.BluetoothDevice.getName(BluetoothDevice.java:1751)
at android.bluetooth.BluetoothDevice.getAlias(BluetoothDevice.java:1818)
at com.android.settingslib.bluetooth.CachedBluetoothDevice.getName(CachedBluetoothDevice.java:599)
at com.autolink.systemui.plugin.status.view.BlueToothPopView.getData(BlueToothPopView.java:316)
  • 最頂層 正在 JNI 釋放 Parcel 內存 (nativeFreeBuffer)。

  • 該 Parcel 是 剛走完 Binder 調用 getRemoteName() 的返回包。

  • 意味著:
    BluetoothDevice.getName() → Binder → bluetooth@service → 等待回復 → 一直等到現在

  • 主線程 同步等結果,結果 remote 端不返回,于是 卡在釋放返回包 這一步。

業務入口快照

  • 觸發點在 藍牙狀態廣播 (BluetoothAdapter.ACTION_STATE_CHANGEDACL_CONNECTED 之類)。

  • 收到廣播后 直接在主線程 遍歷已配對設備并 依次 getName()/getAlias()

  • 如果 遠端設備無響應(耳機/車機斷電、固件 bug、驅動卡死),Binder 就會阻塞,主線程同步被拖住。

ANR Log分析-input

Subject: Input dispatching timed out (9b06661 NavigationBar0 (server) is not responding. Waited 5001ms for MotionEvent(deviceId=-1, eventTime=105593089000000, source=TOUCHSCREEN, displayId=0, action=DOWN, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (1748.0, 1152.0)]), policyFlags=0x6b000000).
RssHwmKb: 578736
RssKb: 442240
RssAnonKb: 248084
RssShmemKb: 9312
VmSwapKb: 102988--- CriticalEventLog ---
capacity: 20
timestamp_ms: 1746257995372
window_ms: 300000----- dumping pid: 17251 at 105598116----- pid 17251 at 2025-05-03 15:39:55.140104249+0800 -----
Cmd line: com.android.systemui
Build fingerprint: 'chery/cheryidcpro/cheryidcpro:14/UQ1A.240205.002/eng.autoli.20250430.025459:userdebug/dev-keys'
ABI: 'arm64'
Build type: optimized
suspend all histogram:	Sum: 92.523ms 99% C.I. 2.660us-10708.479us Avg: 249.388us Max: 22989us
DALVIK THREADS (134):
"main" prio=5 tid=1 Runnable| group="main" sCount=0 ucsCount=0 flags=0 obj=0x722c2f08 self=0xb400006ecd557380| sysTid=17251 nice=0 cgrp=foreground sched=0/0 handle=0x70cfae14f8| state=R schedstat=( 473063282543 2713319119711 2741705 ) utm=33543 stm=13763 core=0 HZ=100| stack=0x7fc4435000-0x7fc4437000 stackSize=8188KB| held mutexes= "mutator lock"(shared held)native: #00 pc 0055be4c  /apex/com.android.art/lib64/libart.so (art::DumpNativeStack+172) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #01 pc 006773a8  /apex/com.android.art/lib64/libart.so (art::Thread::DumpStack const+340) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #02 pc 00694f38  /apex/com.android.art/lib64/libart.so (art::DumpCheckpoint::Run+916) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #03 pc 006782d4  /apex/com.android.art/lib64/libart.so (art::Thread::RunCheckpointFunction+184) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #04 pc 0072a710  /apex/com.android.art/lib64/libart.so (artTestSuspendFromCode+52) (BuildId: 55b9536b61b755b7bae82e95aa498dab)native: #05 pc 00226e1c  /apex/com.android.art/lib64/libart.so (art_quick_test_suspend+156) (BuildId: 55b9536b61b755b7bae82e95aa498dab)at java.lang.reflect.Executable.getMethodNameInternal(Native method)at java.lang.reflect.Method.getName(Method.java:115)at java.lang.reflect.Method$1.compare(Method.java:72)at java.lang.reflect.Method$1.compare(Method.java:67)at java.util.TimSort.gallopRight(TimSort.java:617)at java.util.TimSort.mergeHi(TimSort.java:863)at java.util.TimSort.mergeAt(TimSort.java:520)at java.util.TimSort.mergeForceCollapse(TimSort.java:461)at java.util.TimSort.sort(TimSort.java:254)at java.util.Arrays.sort(Arrays.java:1344)at java.util.ArrayList.sort(ArrayList.java:1738)at java.util.Collections.sort(Collections.java:210)at libcore.util.CollectionUtils.removeDuplicates(CollectionUtils.java:86)at java.lang.Class.getMethods(Class.java:2226)at android.os.Handler.handleCallback(Handler.java:958)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loopOnce(Looper.java:205)at android.os.Looper.loop(Looper.java:294)at android.app.ActivityThread.main(ActivityThread.java:8248)at java.lang.reflect.Method.invoke(Native method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:981)

這段日志是 SystemUI 進程(com.android.systemui) 被系統判定為 輸入ANR(Input Dispatching Timed Out) 的完整現場。

核心結論一句話:

主線程并沒有死鎖、也沒有 Binder 阻塞,而是陷在「Class.getMethods() → 反射排序」里連續耗 CPU,導致 InputDispatcher 的 MotionEvent 5 秒沒人消費,被系統報 ANR。

關鍵信息速覽

項目日志原文解讀
ANR 類型Input dispatching timed out ... NavigationBar0 ... Waited 5001ms輸入事件(手指按下)在 5 s 內未被 SystemUI 主線程消費
進程 & 線程pid=17251 tid=1 main就是 SystemUI 的主線程
線程狀態state=R schedstat=(473063282543 ...)一直處于 RunnableCPU 在跑(不是 Block)
內存RssKb: 442240 VmSwapKb: 102988物理內存 442 MB,Swap 占用 100 MB輕度內存壓力,但 不是 GC 停滯
棧頂TimSort.gallopRight/mergeHi → Arrays.sort → Class.getMethods正在對 Method[] 做排序去重反射耗時

為什么會卡在這里

調用鏈

主線程消息 → Handler.handleCallback → ...
→ ReflectUtils.findMethod() → Class.getMethods()
→ CollectionUtils.removeDuplicates() → Collections.sort() → Arrays.sort()
→ TimSort.mergeHi/gallopRight

Class.getMethods() 會把 本類 + 所有父類的所有 public 方法 一次性取出,數量可達數百個
然后 CollectionUtils.removeDuplicates()方法名 + 參數類型 排序去重,最壞情況下 TimSort 比較器要執行上萬次反射字符串比較

主線程跑滿 CPU

schedstatutm=33543 stm=13763(單位 10 ms),
僅本次采樣就已經 累計 335 s 用戶態 + 137 s 內核態
說明主線程 一直在跑,但 就是沒干完,InputDispatcher 的事件隊列因此得不到空檔期處理。

不是 GC、也不是 Binder

  • 沒有 SuspendAll 幾百毫秒的記錄(GC 阻塞典型特征)。

  • 棧里無 IPCThreadState::waitForResponse 等 Binder 阻塞符號。

  • 內存 Swap 雖有 100 MB,但 anon 部分只有 248 MB,遠未到頻繁 swap-in/out 的程度。

根因修復動作
主線程做重型反射 + 排序① 把 Class.getMethods() 與排序 移到工作線程
② 結果 (),皮膚切換只讀緩存;
③ 若框架不可改,:Application 起就異步解析完常用 View 的方法;
緩存 key 建議(類, 方法名, 參數類型數組) 做 key,避免每次拼接字符串比較;
避免反射長期用 接口/策略模式 替代「運行時找方法」;

一句話總結

這不是死鎖,也不是內存抖動,而是 SystemUI 主線程在「給反射方法排序」時 CPU 被吃光,輸入事件 5 秒得不到處理,被系統判為輸入 ANR;把反射+排序移出主線程并加緩存即可根治

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98601.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98601.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98601.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

stm32——單總線,DHT11

目錄 一、單總線協議的原理和應用 單總線協議指的是只采用一根信道來進行數據傳輸,通信指的是雙方(MCU與傳感器)通過一根信道進行數據交互,所以按照數據的傳輸方向,只能采用半雙工通信方式,比較典型的傳感器…

css3之grid布局

容器:gird container開啟grid布局的元素 項目:grid items容器里面的子元素,不包括后代元素 顯式網格(單元格):通過grid-template-columns和grid-template-rows指定的網格,注意項目不等于單元格,…

C++容器:list

一、list的介紹及使用 list是可以在常數范圍內在任意位置進行插入和刪除的序列式容器,并且該容器可以前后雙向迭代。list的底層是雙向鏈表結構,雙向鏈表中每個元素存儲在互不相關的獨立節點中,在節點中通過指針指向其前一個元素和后一個元素…

STL庫——map/set(類函數學習)

? ? ? ? ? づ?ど 🎉 歡迎點贊支持🎉 個人主頁:勵志不掉頭發的內向程序員; 專欄主頁:C語言; 文章目錄 前言 一、序列式容器和關聯式容器 二、set 系列的使用 2.1、set 和 multiset 參考文檔 2.2、set…

計算機網絡IP協議

1.TCP協議1.1 確認應答1.2 超時重傳1.3 連接管理1.4 滑動窗口1.5 流量控制1.6 擁塞控制 1.7 延時應答1.8 稍帶應答1.9 粘包問題1.10 異常情況2.IP協議 網絡層2.1 NAT機制下的幾種情況:同一個局域網中,內網ip訪問 內網 ip,可以的不同局域網中,內網IP訪問 內網IP,不行~~外網IP訪…

Windows電腦如何查看wifi連接記錄及連接時間

查詢WIFI 連接的記錄 echo netsh wlan show profiles netsh wlan show wlanreport POWERSHELL 腳本 Get-WinEvent -LogName Microsoft-Windows-WLAN-AutoConfig/Operational | Where-Object { $_.Id -in (8001,8002) } | Select-Object TimeCreated, Id, {Name"Action…

【golang學習筆記 gin 】1.2 redis 的使用

安裝redis go get -u github.com/gin-gonic/gin go get -u github.com/go-redis/redis/v8創建相關目錄 gotest->conifg->database.go->redis.go->controller ->index.go->model->user.go->router->router.gomain.go 封裝Redis package config impor…

Java學習之——“IO流“的進階流之序列化流的學習

一、核心概念:什么是序列化與反序列化?序列化 (Serialization): 將一個對象(在內存中的狀態)轉換成一個字節序列的過程。這個字節序列包含了對象的數據、對象的類型以及對象中存儲的屬性等信息。反序列化 (Deserializa…

機器學習04——決策樹(信息增益、信息增益率、ID3、C4.5、CART、剪枝、連續值缺失值處理)

上一章:機器學習03——線性模型 下一章:機器學習05——多分類學習與類別不平衡 機器學習實戰項目:【從 0 到 1 落地】機器學習實操項目目錄:覆蓋入門到進階,大學生就業 / 競賽必備 文章目錄一、決策樹的基本流程&#…

(論文速讀)從語言模型到通用智能體

論文題目:From Multimodal LLMs to Generalist Embodied Agents: Methods and Lessons(從多模式大型語言模型到多面手具身代理:方法和教訓)會議:CVPR2025摘要:我們研究了多模態大型語言模型(Multimodal Large Language…

【Epiq Solutions】Matchstiq? G20 和 Matchstiq? G40 AI SDR

Matchstiq? G20 和 Matchstiq? G40 產品簡介 Matchstiq? G20 和 Matchstiq? G40 是 Epiq Solutions 推出的 緊湊型、高性能軟件定義無線電(SDR)平臺,專為滿足 嚴苛 SWaP-C(體積、重量、功耗受限)場景下的戰術與移動…

基于Echarts+HTML5可視化數據大屏展示-旅游智慧中心

效果展示&#xff1a; 代碼結構&#xff1a;主要代碼實現 index.html布局 <!DOCTYPE html> <html lang"en" style"font-size: 97.5px;"> <head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"…

Docker 鏡像的使用

1.鏡像的基本信息[roothost1 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 802541663949 2 weeks ago 78.1MB hello-world latest 1b44b5a3e06a 4 weeks ago 10.1kB執行 docker images 命令時加上 --no…

網絡編程;套接字;TCP通訊;UDP通訊;0909

思維導圖TCP服務器端和客戶端通訊服務器端 代碼#include<myhead.h> #define SER_IP "192.168.109.12"//我的虛擬機的ip #define SER_PORT 8888 int main() {//1.創建一個用于連接的套接字文件描述符int sfd socket(AF_INET,SOCK_STREAM,0);if(sfd-1){perror(&…

貪心算法應用:柔性制造系統(FMS)刀具分配問題詳解

Java中的貪心算法應用&#xff1a;柔性制造系統(FMS)刀具分配問題詳解 1. 問題背景與定義 柔性制造系統(Flexible Manufacturing System, FMS)是現代智能制造中的關鍵組成部分&#xff0c;它能夠靈活地適應不同產品的生產需求。在FMS中&#xff0c;刀具分配是一個核心優化問題&…

不止是DELETE:MySQL多表關聯刪除的JOIN語法實戰詳解

MySQL 的 ??DELETE?? 語句用于從數據庫表中刪除記錄。這是一項非常強大且危險的操作&#xff0c;因為一旦執行&#xff0c;數據通常無法恢復。理解其語法和安全實踐至關重要。以下是 MySQL 刪除語句的詳細指南。一、 核心語法&#xff1a;DELETE??DELETE?? 語句用于刪除…

ubuntu 系統使用過程中黑屏問題分析

背景&#xff1a; 工欲善其事&#xff0c;必先利其器。作為程序員&#xff0c;想要得到更好的發展&#xff0c;遇到問題直接baidu, google 雖然可以得到一些參考或者答案&#xff0c;但是也會降低自己的思考能力&#xff0c;本文以ubuntu 使用過程中黑屏這一問題為背景&#x…

Redis(45)哨兵模式與集群模式有何區別?

Redis 提供了兩種高可用性解決方案&#xff1a;哨兵模式和集群模式。它們各自有不同的特點和適用場景。以下是詳細的對比和結合代碼的示例&#xff1a; 哨兵模式&#xff08;Sentinel&#xff09; 特點高可用性&#xff1a; Sentinel 通過監控、通知、故障轉移等功能&#xff0…

微信小程序如何進行分包處理?

目錄 分包是什么&#xff1f; 為什么要分包&#xff1f; 分包前后結構對比 具體操作步驟 第 1 步&#xff1a;規劃分包結構 第 2 步&#xff1a;修改 app.json 進行配置 第 3 步&#xff1a;創建分包目錄并移動文件 第 4 步&#xff1a;處理組件和工具函數的引用 第 5…

Go語言極速入門與精要指南從零到精通的系統化學習路徑

&#x1f49d;&#x1f49d;&#x1f49d;歡迎蒞臨我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 持續學習&#xff0c;不斷…