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:区分系统问题和应用问题
相关推荐
Ryan ZHENG3 小时前
[Android][踩坑]Android Studio导入core-libart.jar
android·android studio·jar
q***R3083 小时前
Kotlin注解处理
android·开发语言·kotlin
Digitally3 小时前
如何将文件从三星平板传输到电脑
android
CHINAHEAO4 小时前
Bagisto单独将后台设置成中文
android
E***U9454 小时前
React Native开发
android·react native·react.js
4***99745 小时前
Kotlin序列处理
android·开发语言·kotlin
t***D2645 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
玲珑Felone5 小时前
flutter 状态管理--InheritedWidget、Provider原理解析
android·flutter·ios
BoomHe5 小时前
车载应用配置系统签名
android·android studio