Android ANR

Android ANR的类型、定位与解决方法

ANR的类型(共4种)

根据Android系统机制,ANR主要分为以下4种类型:

  1. 输入调度ANR(最常见,有弹框提示)

    • 触发条件:主线程处理按键/触摸事件超过5秒
    • 关键词:Input dispatching timed out
    • 用户会看到"应用无响应"对话框
  2. 广播接收器ANR

    • 触发条件:onReceive()方法执行超过10秒 (前台广播)或60秒(后台广播)
    • 关键词:Timeout of broadcast
    • 系统不会显示对话框,仅输出日志
  3. 服务ANR

    • 触发条件:onCreate()onStartCommand()等生命周期方法执行超过20秒 (前台服务)或200秒(后台服务)
    • 关键词:Timeout executing service
    • 系统不会显示对话框,仅输出日志
  4. 无聚焦窗口ANR

    • 触发条件:应用无法找到聚焦窗口(通常是应用启动慢,无法绘制第一帧)
    • 关键词:No focused window
    • 超时时间:5秒

ANR定位方法(5步法)

  1. 获取关键日志文件

    • 通过ADB命令获取traces.txt

      bash 复制代码
      adb pull /data/anr/traces.txt ./anr_traces.log
    • 分析最新ANR记录:head -n 500 anr_traces.log

  2. 关键搜索关键词

    • 搜索main定位主线程
    • 检查线程状态:BLOCKED(被阻塞)、WAITING(等待资源)、TIMED_WAITING(带超时等待)
  3. 使用Android Vitals

    • 通过Play管理中心查看ANR统计
    • 关注"用户感知的ANR发生率"(核心指标)
  4. 分析工具

    • TraceView:查看主线程繁忙位置
    • StrictMode:检测主线程意外I/O操作
    • Perfetto:区分系统问题和应用问题
  5. 线上监控方案

    • 使用FileObserver监听/data/anr/目录变化
    • 集成Bugly等第三方监控工具

解决方案与关键点

  1. 避免主线程阻塞(70% ANR原因)

    • 网络请求、数据库操作、文件读写等全部移至工作线程
    • 使用HandlerAsyncTaskCoroutine等进行线程切换
  2. 优化锁机制

    • 减少主线程与其他线程的锁争用
    • 避免死锁(如:主线程等待其他线程锁,而其他线程又等待主线程)
  3. 优化UI渲染

    • 减少布局层级,避免过度复杂的UI
    • 使用Jetpack Paging库处理列表数据
  4. 合理使用生命周期

    • onCreate()onResume()中尽量少做耗时操作
    • 复杂初始化可使用Splash Screen或异步加载

常见关键词总结

关键词 说明
Input dispatching timed out 最常见的ANR类型,5秒超时
Timeout of broadcast 广播接收器超时
Timeout executing service 服务超时
main 主线程标识,日志中搜索的关键词
BLOCKED/WAITING/TIMED_WAITING 线程状态,定位阻塞原因
traces.txt ANR日志文件,关键诊断文件
StrictMode 用于检测主线程I/O操作的工具
Binder 同步Binder调用超时相关

记住:ANR的核心是主线程响应超时,所有优化都围绕"让主线程保持响应"这个原则进行。在开发过程中,使用StrictMode和TraceView能帮助你提前发现潜在的ANR问题,避免上线后才被用户反馈。

📱 详细解析:ANR定位第一步------获取并分析traces.txt日志

嘿,朋友!说到ANR定位,我特别想和你聊聊这个最基础但最重要 的步骤------获取和分析traces.txt文件。这就像医生做诊断时先要拍X光片一样,没有这个,其他分析都是空中楼阁。

为什么traces.txt这么重要?

想象一下:当你的应用突然"卡死",用户看到"应用无响应"弹窗。系统会自动记录那一刻所有线程的状态,这些信息就存放在traces.txt里。它包含了主线程(也就是UI线程)到底在干什么,为什么卡住了,甚至能告诉我们是哪个锁在作祟。

💡 小贴士:90%的ANR问题都能从traces.txt中找到线索,所以这一步绝对不能跳过!

📥 获取traces.txt的详细方法(2025年最新版)

📱 对于Android 11及以上版本(包括Android 12/13/14)

bash 复制代码
# 1. 连接设备(确保设备已开启USB调试)
adb devices

# 2. 获取traces.txt(最简单的方法)
adb shell "cat /data/anr/traces.txt" > anr_traces.log

# 3. 或者直接拉取到电脑(如果设备是Android 11+)
adb pull /data/anr/traces.txt ./anr_traces.log

📱 对于Android 6.0以下版本

bash 复制代码
adb pull /data/anr/traces.txt ./anr_traces.log

📱 对于Android 7.0-10.0版本(权限问题)

bash 复制代码
# 先获取bugreport(会生成一个压缩包)
adb bugreport > bugreport.zip

# 解压后,在解压目录的FS/data/anr/下找到traces.txt
unzip bugreport.zip

⚠️ 注意 :有些厂商(如小米、华为)对/data/anr/目录有额外限制,可能需要root权限。

🔍 详细分析traces.txt(实战案例)

让我们看一个真实的traces.txt片段,我来一步步解释:

scss 复制代码
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x73f44000 self=0x7f2c120000
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x7f2c12d6d0
  | state=WAITING (sleeping) gp=0x0 tid=1
  | stack=0x7fffe80000-0x7fffe82000 stackSize=1040KB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a7c1234> (a java.lang.Object)
  at java.lang.Object.wait(Object.java:422)
  at com.example.app.MainActivity$1.run(MainActivity.java:45)
  - locked <0x0a7c1234> (a java.lang.Object)
  at android.os.Handler.handleCallback(Handler.java:883)
  at android.os.Handler.dispatchMessage(Handler.java:100)
  at android.os.Looper.loop(Looper.java:214)
  at android.app.ActivityThread.main(ActivityThread.java:7356)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

🧠 逐行解读

  1. "main" prio=5 tid=1 Waiting

    • 这是主线程,正在等待状态(Waiting)
    • tid=1是线程ID,主线程ID通常是1
  2. at com.example.app.MainActivity$1.run(MainActivity.java:45)

    • 关键!问题出在MainActivity的第45行
    • 这是一个匿名内部类($1)的run方法
  3. - locked <0x0a7c1234> (a java.lang.Object)

    • 主线程正在等待获取一个锁(Object对象)
    • 这个锁的地址是0x0a7c1234
  4. at java.lang.Object.wait(Native Method)

    • 主线程在调用wait()方法等待锁

🕵️‍♂️ 为什么这很重要?

从这个片段我们可以立即知道

  • 问题出在MainActivity.java的第45行
  • 代码中使用了wait()方法,说明有线程同步问题
  • 主线程在等待一个锁,而这个锁被其他线程持有

💡 实际定位案例

假设你的代码是这样的:

java 复制代码
// MainActivity.java
private final Object lock = new Object();

public void doSomething() {
    // 45行
    synchronized (lock) {
        // 复杂的数据库查询
        List<Data> data = database.query();
        // ... 其他操作
    }
}

// 在另一个线程中
new Thread(() -> {
    synchronized (lock) {
        // 复杂的计算
        processBigData();
    }
}).start();

问题:主线程在等待锁,而工作线程也持有这个锁,导致主线程被阻塞。

解决方案

  1. 避免在主线程中持有锁(如将数据库查询移至工作线程)
  2. 减少锁的范围(只锁定必要的代码)
  3. 使用更高级的并发工具(如ConcurrentHashMap

🛠️ 分析traces.txt的实用技巧

  1. 搜索"main":这是找主线程的起点
  2. 关注线程状态
    • BLOCKED:线程被阻塞(等待锁)
    • WAITING:线程在等待资源
    • TIMED_WAITING:带超时的等待
  3. 找"locked"和"waiting to lock":这是锁争用的关键线索
  4. 看业务代码栈 :从at com.example...开始,找到你的业务代码

🌟 高级技巧:如何快速判断是锁争用?

在traces.txt中搜索:

css 复制代码
waiting to lock <0x12345678> (a com.example.MyClass)

css 复制代码
locked <0x12345678> (a com.example.MyClass)

如果同时出现这两行,基本可以确定是死锁锁争用导致的ANR。

💬 我的建议

下次遇到ANR,别急着猜,先按以下步骤操作:

  1. adb pull /data/anr/traces.txt导出日志
  2. 用文本编辑器打开,搜索"main"
  3. 找到线程状态和业务代码栈
  4. 根据锁信息定位问题

记住:ANR的本质是主线程被阻塞,而traces.txt就是你的"X光片",能让你看到问题所在。

📱 Android ANR类型详解与分析(附真实案例)

嗨!我特别喜欢分析ANR问题,因为这是让应用更流畅的关键。下面我来为你详细讲解每种ANR类型的示例和分析,帮你彻底理解它们的成因和解决方法。


🔥 1. 输入调度ANR(最常见,5秒超时)

触发条件 :主线程处理按键/触摸事件超过5秒(前台应用)

📌 示例:MainActivity中执行耗时数据库操作

java 复制代码
// MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // 耗时操作:在主线程中执行数据库查询(需要3秒)
    List<User> users = database.getAllUsers(); // 这个查询需要2秒
    // 之后的操作...
}

📜 日志关键片段(traces.txt):

ini 复制代码
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x73f44000 self=0x7f2c120000
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x7f2c12d6d0
  | state=WAITING (sleeping) gp=0x0 tid=1
  | stack=0x7fffe80000-0x7fffe82000 stackSize=1040KB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a7c1234> (a java.lang.Object)
  at com.example.app.MainActivity.onCreate(MainActivity.java:25)
  at android.app.Activity.performCreate(Activity.java:7825)
  ...

🔍 问题分析:

  • 关键点 :主线程在MainActivity.onCreate第25行等待数据库查询
  • 根本原因:在主线程中执行了耗时的数据库操作
  • 为什么是ANR:系统检测到主线程在5秒内未响应输入事件(用户可能在等待界面加载完成)

💡 解决方案:

java 复制代码
// 正确做法:将数据库查询移到子线程
new Thread(() -> {
    List<User> users = database.getAllUsers();
    runOnUiThread(() -> {
        // 更新UI
        setupUI(users);
    });
}).start();

最佳实践 :避免在主线程执行任何I/O操作(数据库、网络、文件读写),使用AsyncTaskHandlerThreadCoroutines处理


📱 2. 广播接收器ANR(前台广播10秒,后台广播60秒)

触发条件onReceive()方法执行超过10秒 (前台广播)或60秒(后台广播)

📌 示例:BroadcastReceiver中执行网络请求

java 复制代码
// MyBroadcastReceiver.java
public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 耗时操作:在主线程中执行网络请求(需要8秒)
        String result = performNetworkRequest(); // 网络请求需要8秒
    }
}

📜 日志关键片段:

ini 复制代码
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x73f44000 self=0x7f2c120000
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x7f2c12d6d0
  | state=WAITING (sleeping) gp=0x0 tid=1
  | stack=0x7fffe80000-0x7fffe82000 stackSize=1040KB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a7c1234> (a java.lang.Object)
  at android.content.BroadcastReceiver.onReceive(BroadcastReceiver.java:123)
  at com.example.app.MyBroadcastReceiver.onReceive(MyBroadcastReceiver.java:15)
  ...

🔍 问题分析:

  • 关键点MyBroadcastReceiver.onReceive第15行执行了耗时网络请求
  • 为什么是ANR:前台广播超时10秒,这里执行了8秒操作(如果超过10秒就触发ANR)
  • 陷阱 :很多人以为使用goAsync()就能解决,但忘记调用PendingResult.finish()

💡 解决方案:

java 复制代码
public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final PendingResult result = goAsync(); // 开启异步处理
        
        new Thread(() -> {
            try {
                String resultData = performNetworkRequest();
                // 处理结果
            } finally {
                result.finish(); // 必须调用,否则会一直等待
            }
        }).start();
    }
}

重要提示goAsync()只是开启异步处理,但广播总执行时间仍受超时限制(前台10秒,后台60秒)


⚙️ 3. 服务ANR(前台服务20秒,后台服务200秒)

触发条件onCreate()onStartCommand()等生命周期方法执行超过20秒 (前台服务)或200秒(后台服务)

📌 示例:Service中执行文件操作

java 复制代码
// MyService.java
public class MyService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 耗时操作:在主线程中读取大文件(需要15秒)
        String content = readFile("large_file.txt");
        return START_STICKY;
    }
    
    private String readFile(String fileName) {
        // 实际实现:读取大文件
    }
}

📜 日志关键片段:

ini 复制代码
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x73f44000 self=0x7f2c120000
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x7f2c12d6d0
  | state=WAITING (sleeping) gp=0x0 tid=1
  | stack=0x7fffe80000-0x7fffe82000 stackSize=1040KB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a7c1234> (a java.lang.Object)
  at com.example.app.MyService.onStartCommand(MyService.java:22)
  at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3590)
  ...

🔍 问题分析:

  • 关键点MyService.onStartCommand第22行执行了耗时的文件操作
  • 为什么是ANR:前台服务超时20秒,这里执行了15秒操作(如果超过20秒就触发ANR)
  • 常见误解:以为服务在后台运行,所以可以长时间执行

💡 解决方案:

java 复制代码
public class MyService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(() -> {
            String content = readFile("large_file.txt");
            // 处理文件内容
        }).start();
        
        return START_STICKY;
    }
}

最佳实践:服务中的任何耗时操作都应移至子线程,避免阻塞主线程


🖼️ 4. 无聚焦窗口ANR(5秒超时)

触发条件:应用无法找到聚焦窗口(通常是应用启动慢,无法绘制第一帧)

📌 示例:启动Activity时执行复杂计算

java 复制代码
// SplashActivity.java
public class SplashActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        
        // 耗时操作:在主线程中进行复杂计算(需要4秒)
        performComplexCalculation(); // 需要4秒
    }
    
    private void performComplexCalculation() {
        // 实际实现:复杂计算
    }
}

📜 日志关键片段:

ini 复制代码
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x73f44000 self=0x7f2c120000
  | sysTid=12349 nice=0 cgrp=default sched=0/0 handle=0x7f2c12d6d0
  | state=WAITING (sleeping) gp=0x0 tid=1
  | stack=0x7fffe80000-0x7fffe82000 stackSize=1040KB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0a7c1234> (a java.lang.Object)
  at com.example.app.SplashActivity.onCreate(SplashActivity.java:25)
  at android.app.Activity.performCreate(Activity.java:7825)
  ...

🔍 问题分析:

  • 关键点SplashActivity.onCreate第25行执行了耗时计算
  • 为什么是ANR:应用无法在5秒内绘制第一帧,系统找不到聚焦窗口
  • 系统机制:系统会尝试冻结应用("LcdOff"),5秒后输入事件超时

💡 解决方案:

java 复制代码
public class SplashActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        
        // 将复杂计算移至子线程
        new Thread(() -> {
            performComplexCalculation();
            runOnUiThread(() -> {
                // 跳转到主界面
                startActivity(new Intent(this, MainActivity.class));
                finish();
            });
        }).start();
    }
}

优化建议 :使用SplashScreen API优化启动过程,避免在onCreate中做复杂计算


🧠 通用ANR诊断流程(四步法)

  1. 获取traces.txt

    bash 复制代码
    adb pull /data/anr/traces.txt ./anr_traces.log
  2. 搜索关键线索

    • 搜索"main"(主线程)
    • 搜索"Waiting"/"BLOCKED"(线程状态)
    • 搜索"at com.example"(业务代码位置)
  3. 分析线程状态

    • WAITING:等待资源(如锁)
    • BLOCKED:被其他线程阻塞
    • TIMED_WAITING:带超时等待
  4. 定位问题根源

    • 查看业务代码位置(如MainActivity.java:25
    • 检查是否有同步锁、I/O操作或复杂计算

💡 为什么这些ANR这么重要?

  • 用户感知的ANR发生率是Google Play的核心指标
  • 超过**0.47%**的用户感知ANR会导致应用曝光度降低
  • 甚至可能影响应用在Google Play的排名

🌟 记住 :ANR的核心是主线程被阻塞,所有优化都围绕"让主线程保持响应"这个原则。


🌟 最后小贴士

  • StrictMode:在开发阶段使用StrictMode检测主线程I/O操作
  • Android Studio Profiler:实时监控主线程活动
  • Perfetto:区分系统问题和应用问题
相关推荐
虫小宝3 小时前
微信群发消息API接口对接中Java后端的请求参数校验与异常反馈优化技巧
android·java·开发语言
三少爷的鞋3 小时前
架构避坑:为什么 UseCase 不该启动协程,也不该切线程?
android
Mr -老鬼3 小时前
Android studio 最新Gradle 8.13版本“坑点”解析与避坑指南
android·ide·android studio
xiaolizi56748911 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰1000111 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
梨落秋霜11 小时前
Python入门篇【文件处理】
android·java·python
遥不可及zzz14 小时前
Android 接入UMP
android
Coder_Boy_16 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript
冬奇Lab16 小时前
【Kotlin系列03】控制流与函数:从if表达式到Lambda的进化之路
android·kotlin·编程语言
冬奇Lab16 小时前
稳定性性能系列之十二——Android渲染性能深度优化:SurfaceFlinger与GPU
android·性能优化·debug