Logcat日志分析
1. AndroidRuntime关键字(跟整个系统代码相关)
1.1、AndroidRuntime
的核心作用
AndroidRuntime
是Android系统负责启动和运行应用程序的核心组件,当应用因未处理的异常(如空指针、数组越界等)导致崩溃时,AndroidRuntime会捕获这些异常,并在log中输出详细信息,帮助开发者定位问题。
1.2、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:
2.1、ANR的触发条件(系统默认超时时间)
- 输入事件(如点击、触摸):5秒内未处理完成。(
定义在ActivityManagerService的KEY_DISPATCHING_TIMEOUT
) - BroadcastReceiver:前台广播10秒内、后台广播60秒内未处理完成。
- Service:启动超时20秒、绑定超时10秒。
- ContentProvider:publish超时10秒。
2.2、ANR的常见原因
- 主线程执行耗时操作(最常见):
- 网络请求(如HTTP请求、数据库查询)。
- 大量IO操作(如文件读写、大图片解码)。
- 复杂计算(如加密、算法处理)。
- 死锁:两个或多个线程互相等待对方释放锁。
- Binder通信超时:跨进程通信(IPC)时,目标进程响应过慢。
- 系统资源耗尽:CPU、内存不足,导致应用无法正常执行。
2.3、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
日志示例,并分析问题根源与解决方案。
举例:
2.3.1、iowait过高(I/O等待导致的性能问题)
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
问题分析
-
关键线索:
- 主线程状态为
TIMED_WAITING
,卡在java.io.FileInputStream.readBytes()
本地方法,说明正在进行磁盘读取。 - CPU 使用率中 kernel 占比极高(80%),表明大量时间消耗在内核态的 I/O 操作。
- 调用链显示
MainActivity.onCreate()
→loadData()
→LargeFileParser.parse()
,说明在 Activity 创建时同步读取大文件。
- 主线程状态为
-
问题根源:
在主线程执行耗时 I/O 操作(如读取 100MB+ 的文件),导致 CPU 长时间等待磁盘响应,iowait 升高,应用卡顿甚至 ANR。
解决方案
- 异步化:将 I/O 操作移至子线程(如
Executors
、Coroutine
):// 主线程 Executors.newSingleThreadExecutor().execute(() -> {// 子线程执行文件解析LargeFileParser.parse(filePath);// 解析完成后通过 Handler 切回主线程更新 UI });
- 优化 I/O:使用
BufferedInputStream
减少磁盘访问次数,或分块读取大文件。
=====================================================================
2.3.2、线程阻塞(Block)
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
问题分析
-
关键线索:
- 主线程状态为
BLOCKED
,卡在UserManager.getUser()
,等待锁<0x0f2a1b30>
。 NetworkThread
状态为RUNNABLE
,持有锁<0x0f2a1b30>
,正在执行UserManager.updateUser()
。- 调用链显示:主线程在
onResume()
时调用getUser()
,而子线程在网络请求回调中调用updateUser()
,两者争夺同一把锁。
- 主线程状态为
-
问题根源:
- 锁竞争:
UserManager
中的getUser()
和updateUser()
使用同一把对象锁,导致线程互相等待。 - 主线程风险:主线程参与锁竞争,若锁被长时间持有(如网络请求期间),会直接导致 ANR。
- 锁竞争:
解决方案
- 减小锁粒度:仅在必要代码块加锁,避免整个方法同步:
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 });
=====================================================================
2.3.3、内存泄漏(Memory Leak)
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)...
问题分析
-
关键线索:
- GC 后仍存在多个已销毁 Activity 实例:日志显示 GC 后仍有 2 个
OldActivity
实例未被回收(正常应被销毁)。 - 内存占用高:Heap 中
Used = 115678KB
,接近max: 256MB
,频繁 GC 但内存未释放。 - 无显式阻塞线程:线程状态正常,但内存持续增长,说明存在对象无法被 GC。
- GC 后仍存在多个已销毁 Activity 实例:日志显示 GC 后仍有 2 个
-
问题根源:
示例代码中静态集合sUserList
持有Activity
引用,导致Activity
销毁后无法被回收。
解决方案
- 避免静态集合持有 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
中的线程状态、调用链和内存信息,可快速定位性能问题的根本原因。
=====================================================================
2.4、如何避免ANR
主线程执行耗时操作(最常见)
将耗时操作移至子线程
- 网络请求:使用
AsyncTask
、Kotlin协程
、RxJava
或Retrofit
等异步框架。 - 文件操作:通过线程池或
IntentService
执行。
优化BroadcastReceiver
- 避免在
onReceive()
中执行耗时操作,可通过startService()
或发送Intent
给Service处理。
使用Handler处理UI更新
- 子线程完成耗时操作后,通过
Handler
切换到主线程更新UI。
避免死锁
- 减少锁的使用,确保线程按相同顺序获取锁。
- 使用
ReentrantLock
的tryLock()
避免无限等待。
检测ANR的工具
- Systrace:分析系统性能瓶颈,定位耗时操作。
- Android Profiler:监控CPU、内存使用情况,识别长时间运行的方法。
总结
ANR是Android开发中需要重点关注的问题,核心解决思路是避免主线程执行耗时操作,并通过工具监控和优化代码。遇到ANR时,优先分析traces.txt
中的线程堆栈,定位耗时操作的具体位置,再针对性地优化代码逻辑或线程管理。
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被销毁)。
4. 资源与性能相关
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+禁止主线程直接请求网络)。
5. 权限与安全相关
PermissionDenied
:权限被拒绝,如java.lang.SecurityException: Permission denied: ...
。
场景:未申请WRITE_EXTERNAL_STORAGE
却写入文件。SELinux
:安卓安全机制相关错误,如avc: denied { read } for ...
(SELinux权限不足,多出现于系统应用或定制ROM)。Signature check failed
:签名验证失败,如应用签名与系统要求不匹配(多出现于系统级应用安装)。
6. 组件与生命周期相关
Activity
/Service
/BroadcastReceiver
/Fragment
:组件生命周期日志,如Activity.onPause()
、Service.onDestroy()
。
异常场景:Activity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView
(Activity销毁后窗口未关闭)。Intent
:意图相关错误,如ActivityNotFoundException
(找不到匹配的Activity)。
7. 系统事件与状态
BootCompleted
:系统启动完成事件(广播ACTION_BOOT_COMPLETED
触发)。LowBattery
:低电量事件,系统可能触发应用后台限制。ScreenOn
/ScreenOff
:屏幕亮屏/熄屏事件,影响应用唤醒状态。
8. 第三方库与工具相关
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
查看所有错误日志)精准定位。