1. AndroidRuntime關鍵字(跟整個系統代碼相關)
一、AndroidRuntime
的核心作用
AndroidRuntime
是Android系統負責啟動和運行應用程序的核心組件,當應用因未處理的異常(如空指針、數組越界等)導致崩潰時,AndroidRuntime會捕獲這些異常,并在log中輸出詳細信息,幫助開發者定位問題。
二、AndroidRuntime
日志的典型格式
AndroidRuntime
日志通常包含以下關鍵信息,格式大致如下:
E/AndroidRuntime: FATAL EXCEPTION: mainProcess: com.example.myapp, PID: 12345java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object referenceat com.example.myapp.MainActivity.updateText(MainActivity.java:42)at com.example.myapp.MainActivity.onClick(MainActivity.java:28)at android.view.View.performClick(View.java:7448)...(系統調用棧)
關鍵信息解析:
- 日志級別(E/):
E
表示Error(錯誤),是最高級別之一,說明發生了嚴重問題。 - 關鍵字(AndroidRuntime):明確標識該日志由AndroidRuntime組件輸出。
- 錯誤類型(FATAL EXCEPTION):表示發生了致命異常,導致應用強制終止。
- 進程信息(Process: … PID: …):顯示崩潰的應用包名和進程ID,方便定位具體應用。
- 異常詳情:
- 異常類型(如
NullPointerException
、IndexOutOfBoundsException
等)。 - 異常描述(如“Attempt to invoke virtual method on a null object reference”,說明對空對象調用了方法)。
- 異常類型(如
- 調用棧(Stack Trace):從
at ...
開始,顯示異常發生的代碼位置(類名、方法名、文件名、行號),是排查問題的核心依據(例如上述MainActivity.java:42
表示錯誤發生在該文件的第42行)。
總結
AndroidRuntime
是Android日志中標識應用崩潰的核心關鍵字,其日志包含的異常類型、調用棧等信息是解決應用崩潰問題的“關鍵線索”。開發者在調試時,可通過Android Studio的Logcat
工具搜索該關鍵字,快速定位并修復錯誤。
2. ANR關鍵字(跟主線程相關)
在Android開發中,ANR(Application Not Responding) 是指應用程序無響應,是影響用戶體驗的嚴重問題。當應用在主線程(UI線程)執行耗時操作,導致無法及時響應用戶輸入或系統請求時,就會觸發ANR。下面從多個角度詳細解析ANR:
一、ANR的觸發條件(系統默認超時時間)
- 輸入事件(如點擊、觸摸):5秒內未處理完成。(
定義在ActivityManagerService的KEY_DISPATCHING_TIMEOUT
) - BroadcastReceiver:前臺廣播10秒內、后臺廣播60秒內未處理完成。
- Service:啟動超時20秒、綁定超時10秒。
- ContentProvider:publish超時10秒。
二、ANR的常見原因
- 主線程執行耗時操作(最常見):
- 網絡請求(如HTTP請求、數據庫查詢)。
- 大量IO操作(如文件讀寫、大圖片解碼)。
- 復雜計算(如加密、算法處理)。
- 死鎖:兩個或多個線程互相等待對方釋放鎖。
- Binder通信超時:跨進程通信(IPC)時,目標進程響應過慢。
- 系統資源耗盡:CPU、內存不足,導致應用無法正常執行。
三、ANR日志分析步驟
當ANR發生時,系統會生成日志并彈窗提示用戶(如“應用無響應,是否關閉?”)。日志位置通常在:
- /data/anr/traces.txt(需要root權限)
- Android Studio Logcat:搜索關鍵字
ANR
或ActivityManager
。
第一步:查看Logcat日志
查看是否是受CPU影響
ANR in com.example.myapp
PID: 12345
Reason: Input dispatching timed out (Waiting because the touched window has not finished processing the input events that were previously delivered to it.)
Load: 1.2 / 0.8 / 0.4
CPU usage from 30000ms to 0ms ago (2025-07-25 10:14:30 to 10:15:30):98% 8765/com.example.myapp: 92% user + 6% kernel / faults: 2340 minor 12 major1.2% 8770/com.example.myapp: 1% user + 0.2% kernel / faults: 156 minor0.8% 8792/com.example.myapp: 0.7% user + 0.1% kernel / faults: 98 minor0.5% 8766/com.example.myapp: 0.3% user + 0.2% kernel / faults: 45 minor...(系統進程CPU占用省略)0.1% TOTAL: 0% user + 0.1% kernel
第二步:查看traces.txt日志分析原因并找到產生ANR的部分,然后對代碼進行修改
=====================================================================
以下針對 iowait過高、線程阻塞(Block)、內存泄漏(Memory Leak) 三種場景,分別給出對應的 traces.txt
日志示例,并分析問題根源與解決方案。
一、iowait過高(I/O等待導致的性能問題)
1. traces.txt 關鍵片段
----- pid 8765 at 2025-07-25 14:30:00 -----
Cmd line: com.example.myappDALVIK THREADS:
"main" prio=5 tid=1 TIMED_WAITING| group="main" sCount=1 dsCount=0 flags=1 obj=0x72f4a3c0 self=0x7a0c2d0000| sysTid=8765 nice=0 cgrp=default sched=0/0 handle=0x7a0c39e1d0| state=S schedstat=( 543210000 456780000 2345 ) utm=50 stm=4 core=3 HZ=100| stack=0x7ff5d3e000-0x7ff5d40000 stackSize=8MB| held mutexes=at java.io.FileInputStream.readBytes(Native method)- waiting on <0x0f2a1b40> (a java.io.FileInputStream)at java.io.FileInputStream.read(FileInputStream.java:255)at java.io.BufferedInputStream.fill(BufferedInputStream.java:248)at java.io.BufferedInputStream.read(BufferedInputStream.java:267)at libcore.io.IoBridge.read(IoBridge.java:496)at java.io.FileInputStream.read(FileInputStream.java:232)at com.example.myapp.io.LargeFileParser.parse(LargeFileParser.java:42)at com.example.myapp.ui.MainActivity.loadData(MainActivity.java:65)at com.example.myapp.ui.MainActivity.onCreate(MainActivity.java:32)at android.app.Activity.performCreate(Activity.java:8000)..."FinalizerDaemon" daemon prio=5 tid=3 WAITING| group="system" sCount=1 dsCount=0 flags=1 obj=0x72f4e0d0 self=0x7a0c320000| sysTid=8768 nice=8 cgrp=default sched=0/0 handle=0x7a0c28b1d0| state=S schedstat=( 123456789 12345678 123 ) utm=10 stm=2 core=0 HZ=100| stack=0x7a0c18c000-0x7a0c18e000 stackSize=1037KB| held mutexes=at java.lang.Object.wait(Native method)- waiting on <0x0f2a1c80> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)...CPU usage from 30000ms to 0ms ago (2025-07-25 14:29:30 to 14:30:00):85% 8765/com.example.myapp: 5% user + 80% kernel / faults: 1200 minor 5 major0.5% 8768/com.example.myapp: 0% user + 0.5% kernel / faults: 15 minor0.2% 8769/com.example.myapp: 0% user + 0.2% kernel / faults: 10 minor...0.1% TOTAL: 0% user + 0.1% kernel
2. 問題分析
-
關鍵線索:
- 主線程狀態為
TIMED_WAITING
,卡在java.io.FileInputStream.readBytes()
本地方法,說明正在進行磁盤讀取。 - CPU 使用率中 kernel 占比極高(80%),表明大量時間消耗在內核態的 I/O 操作。
- 調用鏈顯示
MainActivity.onCreate()
→loadData()
→LargeFileParser.parse()
,說明在 Activity 創建時同步讀取大文件。
- 主線程狀態為
-
問題根源:
在主線程執行耗時 I/O 操作(如讀取 100MB+ 的文件),導致 CPU 長時間等待磁盤響應,iowait 升高,應用卡頓甚至 ANR。
3. 解決方案
- 異步化:將 I/O 操作移至子線程(如
Executors
、Coroutine
):// 主線程 Executors.newSingleThreadExecutor().execute(() -> {// 子線程執行文件解析LargeFileParser.parse(filePath);// 解析完成后通過 Handler 切回主線程更新 UI });
- 優化 I/O:使用
BufferedInputStream
減少磁盤訪問次數,或分塊讀取大文件。
=====================================================================
二、線程阻塞(Block)
1. traces.txt 關鍵片段
----- pid 8765 at 2025-07-25 14:35:00 -----
Cmd line: com.example.myappDALVIK THREADS:
"main" prio=5 tid=1 BLOCKED| group="main" sCount=1 dsCount=0 flags=1 obj=0x72f4a3c0 self=0x7a0c2d0000| sysTid=8765 nice=0 cgrp=default sched=0/0 handle=0x7a0c39e1d0| state=S schedstat=( 23456000 1234000 56 ) utm=2 stm=0 core=3 HZ=100| stack=0x7ff5d3e000-0x7ff5d40000 stackSize=8MB| held mutexes=at com.example.myapp.data.UserManager.getUser(UserManager.java:55)- waiting to lock <0x0f2a1b30> (a com.example.myapp.data.UserManager) held by thread 6at com.example.myapp.ui.HomeActivity.refreshUserInfo(HomeActivity.java:120)at com.example.myapp.ui.HomeActivity.onResume(HomeActivity.java:80)at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1456)..."NetworkThread" prio=5 tid=6 RUNNABLE| group="main" sCount=0 dsCount=0 flags=1 obj=0x72f5c6a0 self=0x7a0c310000| sysTid=8771 nice=5 cgrp=default sched=0/0 handle=0x7a0c29d1d0| state=R schedstat=( 187654000 15623000 876 ) utm=17 stm=1 core=1 HZ=100| stack=0x7a0c19e000-0x7a0c1a0000 stackSize=1037KB| held mutexes=at com.example.myapp.data.UserManager.updateUser(UserManager.java:90)- locked <0x0f2a1b30> (a com.example.myapp.data.UserManager)at com.example.myapp.network.ApiService$1.onResponse(ApiService.java:45)at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89)at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1$$ExternalSyntheticLambda0.run(Unknown Source:6)at android.os.Handler.handleCallback(Handler.java:942)...CPU usage from 30000ms to 0ms ago (2025-07-25 14:34:30 to 14:35:00):30% 8765/com.example.myapp: 25% user + 5% kernel / faults: 345 minor 2 major25% 8771/com.example.myapp: 23% user + 2% kernel / faults: 210 minor0.5% 8768/com.example.myapp: 0% user + 0.5% kernel / faults: 15 minor...0.1% TOTAL: 0% user + 0.1% kernel
2. 問題分析
-
關鍵線索:
- 主線程狀態為
BLOCKED
,卡在UserManager.getUser()
,等待鎖<0x0f2a1b30>
。 NetworkThread
狀態為RUNNABLE
,持有鎖<0x0f2a1b30>
,正在執行UserManager.updateUser()
。- 調用鏈顯示:主線程在
onResume()
時調用getUser()
,而子線程在網絡請求回調中調用updateUser()
,兩者爭奪同一把鎖。
- 主線程狀態為
-
問題根源:
- 鎖競爭:
UserManager
中的getUser()
和updateUser()
使用同一把對象鎖,導致線程互相等待。 - 主線程風險:主線程參與鎖競爭,若鎖被長時間持有(如網絡請求期間),會直接導致 ANR。
- 鎖競爭:
3. 解決方案
- 減小鎖粒度:僅在必要代碼塊加鎖,避免整個方法同步:
public class UserManager {private final Object lock = new Object();public User getUser() {User result;synchronized (lock) {// 僅在讀取共享資源時加鎖result = cache.getUser();}return result;}public void updateUser(User user) {synchronized (lock) {// 更新操作加鎖cache.saveUser(user);}// 鎖外執行其他耗時操作(如網絡請求)} }
- 主線程異步化:將
getUser()
改為異步調用,避免主線程等待鎖:// 主線程 Executors.newSingleThreadExecutor().execute(() -> {User user = userManager.getUser();// 通過 Handler 切回主線程更新 UI });
=====================================================================
三、內存泄漏(Memory Leak)
1. traces.txt 關鍵片段
----- pid 8765 at 2025-07-25 14:40:00 -----
Cmd line: com.example.myappDALVIK THREADS:
"main" prio=5 tid=1 RUNNABLE| group="main" sCount=0 dsCount=0 flags=1 obj=0x72f4a3c0 self=0x7a0c2d0000| sysTid=8765 nice=0 cgrp=default sched=0/0 handle=0x7a0c39e1d0| state=R schedstat=( 345678900 234567800 1234 ) utm=32 stm=2 core=3 HZ=100| stack=0x7ff5d3e000-0x7ff5d40000 stackSize=8MB| held mutexes=at com.example.myapp.ui.NewActivity.onCreate(NewActivity.java:42)at android.app.Activity.performCreate(Activity.java:8000)at android.app.Activity.performCreate(Activity.java:7989)..."ReferenceQueueDaemon" daemon prio=5 tid=4 WAITING| group="system" sCount=1 dsCount=0 flags=1 obj=0x72f5e0e0 self=0x7a0c330000| sysTid=8769 nice=8 cgrp=default sched=0/0 handle=0x7a0c27a1d0| state=S schedstat=( 123456789 12345678 123 ) utm=10 stm=2 core=0 HZ=100| stack=0x7a0c17b000-0x7a0c17d000 stackSize=1037KB| held mutexes=at java.lang.Object.wait(Native method)- waiting on <0x0f2a1d80> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)...HEAP SUMMARY:Alloc = 125678KB, Used = 115678KB, Free = 10000KBHeap sizes: 128MB, max: 256MB, growth limit: 256MBAllocation spaces: [image: 1048KB, large_objects: 15678KB, normal: 110000KB]...Garbage collector: 15 collections, 0 failed, 42ms elapsed, 0% CPU time[ 2025-07-25 14:35:00 ] Heap before GC invocations=14 (full 1):...- 2 instances of com.example.myapp.ui.OldActivity, 160B (160B retained)- 10 instances of com.example.myapp.data.User, 400B (400B retained)...[ 2025-07-25 14:38:00 ] Heap after GC invocations=15 (full 1):...- 2 instances of com.example.myapp.ui.OldActivity, 160B (160B retained)- 10 instances of com.example.myapp.data.User, 400B (400B retained)...
2. 問題分析
-
關鍵線索:
- GC 后仍存在多個已銷毀 Activity 實例:日志顯示 GC 后仍有 2 個
OldActivity
實例未被回收(正常應被銷毀)。 - 內存占用高:Heap 中
Used = 115678KB
,接近max: 256MB
,頻繁 GC 但內存未釋放。 - 無顯式阻塞線程:線程狀態正常,但內存持續增長,說明存在對象無法被 GC。
- GC 后仍存在多個已銷毀 Activity 實例:日志顯示 GC 后仍有 2 個
-
問題根源:
示例代碼中靜態集合sUserList
持有Activity
引用,導致Activity
銷毀后無法被回收。
3. 解決方案
- 避免靜態集合持有 Activity 引用:使用弱引用(
WeakReference
):public class UserManager {private static final List<WeakReference<Activity>> sActivityRefs = new ArrayList<>();public void addActivity(Activity activity) {sActivityRefs.add(new WeakReference<>(activity));} }
- 及時清理引用:在 Activity 銷毀時移除引用:
@Override protected void onDestroy() {super.onDestroy();userManager.removeActivity(this); // 從靜態集合中移除 }
總結對比
場景 | traces.txt 關鍵特征 | 問題根源 | 解決方案 |
---|---|---|---|
iowait 過高 | 主線程卡在 java.io 相關方法,kernel CPU 占比高 | 主線程執行耗時 I/O 操作 | 異步化 I/O,優化讀寫效率 |
線程阻塞 | 主線程狀態為 BLOCKED ,等待鎖被其他線程持有 | 多線程爭奪同一把鎖,主線程參與競爭 | 減小鎖粒度,主線程異步化 |
內存泄漏 | GC 后 Activity 實例仍存在,內存持續增長 | 長生命周期對象(如靜態變量)持有 Activity 引用 | 使用弱引用,及時清理引用 |
通過分析 traces.txt
中的線程狀態、調用鏈和內存信息,可快速定位性能問題的根本原因。
=====================================================================
四、如何避免ANR
主線程執行耗時操作(最常見)
1. 將耗時操作移至子線程
- 網絡請求:使用
AsyncTask
、Kotlin協程
、RxJava
或Retrofit
等異步框架。 - 文件操作:通過線程池或
IntentService
執行。
2. 優化BroadcastReceiver
- 避免在
onReceive()
中執行耗時操作,可通過startService()
或發送Intent
給Service處理。
3. 使用Handler處理UI更新
- 子線程完成耗時操作后,通過
Handler
切換到主線程更新UI。
4. 避免死鎖
- 減少鎖的使用,確保線程按相同順序獲取鎖。
- 使用
ReentrantLock
的tryLock()
避免無限等待。
5. 檢測ANR的工具
- Systrace:分析系統性能瓶頸,定位耗時操作。
- Android Profiler:監控CPU、內存使用情況,識別長時間運行的方法。
總結
ANR是Android開發中需要重點關注的問題,核心解決思路是避免主線程執行耗時操作,并通過工具監控和優化代碼。遇到ANR時,優先分析traces.txt
中的線程堆棧,定位耗時操作的具體位置,再針對性地優化代碼邏輯或線程管理。
在Android開發和系統調試中,除了AndroidRuntime
(AndroidRuntime錯誤)和ANR
(應用無響應),還有許多常見的關鍵日志關鍵字、錯誤類型和系統事件,它們是定位問題的重要線索。以下是一些核心概念和場景分類說明:
3. 系統核心進程相關
Android系統進程的日志通常涉及系統穩定性,常見關鍵字:
1. ActivityManager
(AM)
系統組件管理的核心進程,日志中頻繁出現,用于追蹤Activity、Service、Broadcast等組件的生命周期:
- 示例:
ActivityManager: Start proc 1234:com.example.app/u0a123 for activity
(啟動應用進程)。 - 問題場景:
ActivityManager: Force finishing activity com.example.app/.MainActivity
(Activity被系統強制終止,可能因ANR或內存不足)。ActivityManager: Low Memory: Killing proc 1234:com.example.app/u0a123
(應用因低內存被系統殺死)。
2. PackageManager
(PM)
應用包管理進程,涉及安裝、卸載、權限等操作:
- 示例:
PackageManager: Installation error code: -103
(安裝失敗,通常因權限或簽名問題)。 - 問題場景:
PackageManager: Failed to grant permissions to package
(權限授予失敗)。
3. WindowManager
(WM)
窗口管理進程,與UI顯示、窗口切換相關:
- 示例:
WindowManager: android.view.WindowLeaked: Activity ... has leaked window ...
(窗口泄漏,如對話框未關閉時Activity被銷毀)。
三、資源與性能相關
1. 內存相關
GC_FOR_ALLOC
:因內存分配不足觸發的垃圾回收(GC)。
頻繁出現可能預示內存緊張,需檢查內存泄漏或大對象分配。MemoryLeak
:內存泄漏(部分工具如LeakCanary會顯式標記)。
日志中可能伴隨LeakCanary: Found 1 memory leak
。OOM
:即OutOfMemoryError
,內存溢出(前文已提及)。
2. CPU與線程相關
Thread
:線程相關日志,如Thread-1: InterruptedException
(線程中斷異常)。DeadLock
:死鎖,日志中可能出現Found a potential deadlock
(需通過adb shell dumpsys threaddump
分析)。Systrace
:系統跟蹤工具生成的日志,用于分析CPU調度、幀渲染延遲(關鍵字如Choreographer
、VSYNC
)。
3. IO與網絡相關
IOException
:IO異常,如文件不存在(FileNotFoundException
)、網絡連接失敗(ConnectException
)。NetworkOnMainThreadException
:主線程網絡操作異常(Android 3.0+禁止主線程直接請求網絡)。
四、權限與安全相關
PermissionDenied
:權限被拒絕,如java.lang.SecurityException: Permission denied: ...
。
場景:未申請WRITE_EXTERNAL_STORAGE
卻寫入文件。SELinux
:安卓安全機制相關錯誤,如avc: denied { read } for ...
(SELinux權限不足,多出現于系統應用或定制ROM)。Signature check failed
:簽名驗證失敗,如應用簽名與系統要求不匹配(多出現于系統級應用安裝)。
五、組件與生命周期相關
Activity
/Service
/BroadcastReceiver
/Fragment
:組件生命周期日志,如Activity.onPause()
、Service.onDestroy()
。
異常場景:Activity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView
(Activity銷毀后窗口未關閉)。Intent
:意圖相關錯誤,如ActivityNotFoundException
(找不到匹配的Activity)。
六、系統事件與狀態
BootCompleted
:系統啟動完成事件(廣播ACTION_BOOT_COMPLETED
觸發)。LowBattery
:低電量事件,系統可能觸發應用后臺限制。ScreenOn
/ScreenOff
:屏幕亮屏/熄屏事件,影響應用喚醒狀態。
七、第三方庫與工具相關
Retrofit
/OkHttp
:網絡庫日志,如OkHttp: --> GET https://api.example.com
(請求日志)、OkHttp: <-- HTTP 500
(服務器錯誤)。Glide
/Picasso
:圖片加載庫錯誤,如Glide: Load failed for ...
(圖片加載失敗)。Firebase
/Crashlytics
:崩潰上報工具日志,如Crashlytics: Sending crash report
。
總結:關鍵日志定位思路
- 崩潰問題:優先搜索
Exception
、Error
、Crash
,定位具體異常類型(如NPE、OOM)。 - 性能問題:關注
ANR
、GC_
、iowait
、Block
,結合Systrace
分析。 - 系統交互問題:查看
ActivityManager
、PackageManager
,判斷組件生命周期或權限問題。 - 第三方庫問題:根據庫名(如
OkHttp
、Glide
)篩選日志,結合官方文檔排查。
掌握這些關鍵字和場景,能快速縮小問題范圍,提高調試效率。實際排查時,可結合logcat
過濾命令(如adb logcat *:E
查看所有錯誤日志)精準定位。