深入浅出安卓ANR(应用无响应)全解析

深入浅出安卓ANR(应用无响应)全解析

一、ANR到底是什么?

ANR(Application Not Responding)就是安卓系统的"卡死警报",相当于手机在说:"这个App不理我了!"。当应用主线程被阻塞超过规定时间,系统就会弹出ANR弹窗。

ANR触发阈值

场景 时间阈值
前台Activity/Service 5秒
前台广播(BroadcastReceiver) 10秒
后台Service 20秒
ContentProvider响应 10秒

二、为什么会出现ANR?

主线程在忙什么?

主线程就像餐厅唯一的服务员,要同时:

  • 处理用户点击(点菜)
  • 更新UI(上菜)
  • 执行代码逻辑(做菜)

如果让服务员去后厨炒菜(主线程做耗时操作),前台就会没人响应(ANR)。

常见ANR原因

pie title ANR原因分布 "主线程IO操作" : 35 "死锁" : 25 "过度布局/绘制" : 20 "Binder调用阻塞" : 15 "其他" : 5

三、如何检测ANR?

1. 开发阶段工具

(1) StrictMode(严苛模式)
java 复制代码
// 在Application中启用
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectDiskReads()  // 检测磁盘读
    .detectDiskWrites() // 检测磁盘写
    .detectNetwork()    // 检测网络
    .penaltyLog()       // 违规时打印日志
    .build());
(2) BlockCanary
gradle 复制代码
// build.gradle
dependencies {
    implementation 'com.github.markzhai:blockcanary-android:1.5.0'
}
java 复制代码
// Application初始化
BlockCanary.install(this, new AppBlockCanaryContext()).start();

2. 线上监控方案

(1) ANRWatchDog
java 复制代码
// 简易版实现
public class ANRWatchDog extends Thread {
    @Override
    public void run() {
        while (!isInterrupted()) {
            long lastRespondTime = SystemClock.uptimeMillis();
            // 在主线程发送ping
            new Handler(Looper.getMainLooper()).post(() -> {
                lastRespondTime = SystemClock.uptimeMillis();
            });
            
            sleep(5000);
            if (SystemClock.uptimeMillis() - lastRespondTime > 5000) {
                // 上报ANR堆栈
            }
        }
    }
}
(2) 系统日志分析
bash 复制代码
# 获取ANR日志
adb pull /data/anr/traces.txt

四、典型ANR案例解析

案例1:数据库主线程查询

java 复制代码
// 错误代码
fun loadUserData() {
    val cursor = database.rawQuery("SELECT * FROM users", null) // 主线程查询
    // ...
}

解决方案

kotlin 复制代码
// 使用Room+协程
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getUsers(): List<User>
}

// ViewModel中调用
viewModelScope.launch {
    val users = userDao.getUsers() // 自动切IO线程
    _userList.postValue(users)     // 切回主线程更新UI
}

案例2:SharedPreferences同步写入

java 复制代码
// 错误用法
sharedPreferences.edit()
    .putString("key", "value")
    .commit(); // 同步提交

正确做法

java 复制代码
sharedPreferences.edit()
    .putString("key", "value")
    .apply(); // 异步提交

案例3:死锁场景

java 复制代码
// 线程A
synchronized(lockA) {
    Thread.sleep(1000);
    synchronized(lockB) { ... }
}

// 线程B(主线程)
synchronized(lockB) {
    updateUI();
    synchronized(lockA) { ... } // ANR!
}

解决方案

  • 使用ReentrantLock.tryLock()设置超时
  • 统一加锁顺序

五、ANR优化进阶技巧

1. 主线程优化金字塔

graph TD A[避免主线程耗时操作] --> B[使用异步框架] B --> C[优化任务调度] C --> D[减少UI重绘] D --> E[内存优化]

2. 异步方案选型

方案 适用场景 特点
AsyncTask 简单异步任务 已废弃
HandlerThread 串行任务队列 需手动管理
RxJava 复杂异步流 学习曲线陡
协程 现代Kotlin首选 结构化并发

3. 关键优化点

  • 布局优化:减少嵌套,使用ConstraintLayout
  • 图片加载:Glide/Picasso自动切线程
  • 网络请求:Retrofit+协程
  • 数据库:Room+Flow
  • 广播接收 :使用goAsync()或切线程处理

六、ANR日志分析实战

1. 解读traces.txt

ini 复制代码
----- pid 12345 at 2023-01-01 12:00:00 -----
Cmd line: com.example.app
...
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x12c12345
  | held mutexes=
  at com.example.app.MainActivity$1.onClick(MainActivity.java:123)
  - waiting to lock <0x0456789> (a java.lang.Object) held by thread 3

关键信息

  • waiting to lock:等待哪个锁
  • held by thread:锁被哪个线程持有

2. systrace分析

bash 复制代码
# 生成systrace
python systrace.py -o trace.html -a com.example.app sched freq idle am wm gfx view

查看主线程的Wall duration(实际执行时间)


七、ANR防护体系

1. 开发规范

  • 主线程只做UI更新
  • 所有IO操作必须异步
  • 避免同步Binder调用
  • 谨慎使用第三方库(检查是否会在主线程操作)

2. 监控体系

graph LR A[客户端] --> B{ANR检测} B -->|正常| C[继续运行] B -->|异常| D[上传堆栈] D --> E[服务端聚合] E --> F[自动告警] E --> G[智能归因]

3. 容灾方案

  • 设置关键操作超时
java 复制代码
Future<Void> future = executor.submit(task);
try {
    future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    future.cancel(true);
    // 降级处理
}
  • 重要服务使用独立进程

总结

  1. ANR本质:主线程被阻塞超过阈值
  2. 三大防线
    • 预防(编码规范)
    • 检测(工具监控)
    • 治理(优化方案)
  3. 核心原则:主线程只做UI相关轻量操作
  4. 终极目标:让用户永远看不到ANR弹窗

掌握这些技巧,你的App将告别卡顿,流畅如飞! 🚀

相关推荐
奔跑吧 android4 分钟前
【android bluetooth 协议分析 01】【HCI 层介绍 1】【hci_packets.pdl 介绍】
android·bluetooth·bt·gabeldorsche·gd·aosp13·bluedroid
冰糖葫芦三剑客2 小时前
安卓 手机拨打电话录音保存地址适配
android
匹马夕阳3 小时前
(十五)安卓开发中不同类型的view之间继承关系详解
android
Jomurphys5 小时前
Android Studio - 解决 Please Select Android SDK
android·android studio
stevenzqzq5 小时前
kotlin扩展函数
android·开发语言·kotlin
V少年5 小时前
深入浅出Java内存模型(JMM)
android
行墨5 小时前
插件资源隔离冲突‌解决方案
android
Hello姜先森5 小时前
Kotlin日常使用函数记录
android·开发语言·kotlin
zhangphil6 小时前
Android Coil 3 Fetcher大批量Bitmap拼接成1张扁平宽图,Kotlin
android·kotlin
IT技术图谱6 小时前
【绝非标题党】Android15适配,太恶心了
android·面试