ANR 线上问题监控与解析方案

一、ANR 基础认知

ANR (Application Not Responding) 是指应用程序在主线程执行耗时操作导致无法响应用户输入的事件(5秒内未响应输入事件或BroadcastReceiver在10秒内未执行完毕)。

常见触发场景

  • 主线程进行网络请求

  • 主线程执行大量数据库操作

  • 主线程进行复杂计算

  • 主线程等待锁或其它线程

  • 系统资源紧张导致主线程无法获取CPU时间片

二、ANR 线上监控方案

1. 文件监控法(推荐)

Android系统会将ANR信息记录到以下位置:

/data/anr/traces.txt
/data/anr/anr_*

实现方案:

public class ANRFileObserver extends FileObserver {

    public ANRFileObserver() {
        super("/data/anr/", FileObserver.CLOSE_WRITE);
    }

    @Override
    public void onEvent(int event, String path) {
        if (path != null && path.startsWith("anr_")) {
            // 读取ANR文件内容并上报
            reportANR(readANRFile("/data/anr/" + path));
        }
    }
    
    // 注册监控
    public static void startWatching() {
        new ANRFileObserver().startWatching();
    }
}

注意事项:

  • 需要READ_LOGS权限

  • Android 7.0+需要特殊处理

  • 不同厂商可能有路径差异

2. 消息队列监控法

public class ANRWatchDog extends Thread {
    
    private static final int ANR_THRESHOLD = 5000;
    private volatile long mLastTick = 0;
    private volatile boolean mShouldStop = false;
    
    @Override
    public void run() {
        while (!mShouldStop) {
            mLastTick = SystemClock.uptimeMillis();
            // 主线程重置计数器
            mHandler.post(() -> mLastTick = SystemClock.uptimeMillis());
            
            SystemClock.sleep(ANR_THRESHOLD);
            
            if (SystemClock.uptimeMillis() - mLastTick >= ANR_THRESHOLD 
                    && !mShouldStop) {
                // 获取主线程堆栈
                String stackTrace = getMainThreadStackTrace();
                // 上报ANR
                reportANR(stackTrace);
            }
        }
    }
    
    private String getMainThreadStackTrace() {
        for (Map.Entry<Thread, StackTraceElement[]> entry : 
                Thread.getAllStackTraces().entrySet()) {
            if (entry.getKey().getId() == Looper.getMainLooper().getThread().getId()) {
                return Arrays.toString(entry.getValue());
            }
        }
        return "";
    }
}

3. 使用第三方解决方案

  • Bugly:腾讯提供的ANR监控

  • Firebase Crashlytics:Google的ANR监控

  • Matrix:微信开源的APM框架,包含ANR监控

  • Hertz:字节跳动的性能监控框架

三、ANR 日志分析要点

1. 关键信息提取

典型ANR日志包含:

----- pid 12345 at 2023-01-01 12:00:00 -----
Cmd line: com.example.app
...
DALVIK THREADS (12):
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x72e9f8a8 self=0x7f88e14c00
  | sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7f96a84e98
  | state=D schedstat=( 123456789 987654321 1234 ) utm=12 stm=34 core=1 HZ=100
  | stack=0x7fc15a8000-0x7fc15aa000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait(Native Method)
  - waiting on <0x0cd5f3f2> (a java.lang.Object)
  at java.lang.Thread.parkFor$(Thread.java:2137)
  - locked <0x0cd5f3f2> (a java.lang.Object)
  at sun.misc.Unsafe.park(Unsafe.java:358)
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:868)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1021)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1328)
  at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)
  at com.example.app.MainActivity$1.run(MainActivity.java:56)
  at android.os.Handler.handleCallback(Handler.java:790)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:164)
  at android.app.ActivityThread.main(ActivityThread.java:6541)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

2. 分析步骤

  1. 确认ANR时间点:查看日志头部时间戳

  2. 定位主线程状态:查找"main"线程

  3. 分析堆栈调用:重点关注应用自身的代码

  4. 检查锁信息:查看"locked"和"waiting on"部分

  5. 查看CPU负载:检查"schedstat"和"utm/stm"值

  6. 检查内存信息:查看是否有low memory警告

四、ANR 预防与优化

1. 代码层面优化

  • 避免主线程阻塞操作

    // 错误示例
    public void loadData() {
        String data = networkRequest(); // 同步网络请求
        updateUI(data);
    }
    
    // 正确示例
    public void loadData() {
        new Thread(() -> {
            String data = networkRequest();
            runOnUiThread(() -> updateUI(data));
        }).start();
    }
  • 优化数据库操作

    // 使用异步查询
    Cursor cursor = db.rawQuery("SELECT * FROM table", null);
    // 改为
    CursorLoader loader = new CursorLoader(context, uri, projection, 
            selection, selectionArgs, sortOrder);

2. 架构层面优化

  • 使用IntentService处理长时间后台任务

  • 采用WorkManager管理后台任务

  • 实现任务优先级队列

  • 使用RxJava或Coroutines管理异步任务

3. 监控体系建设

  • 建立ANR分级机制

    • 关键路径ANR(如启动、支付) - P0

    • 普通页面ANR - P1

    • 后台任务ANR - P2

  • ANR聚合分析

    -- 示例:按堆栈特征聚合ANR
    SELECT stack_signature, COUNT(*) as count, 
           AVG(duration) as avg_duration
    FROM anr_logs
    GROUP BY stack_signature
    ORDER BY count DESC

五、厂商适配注意事项

  1. 华为EMUI

    • 增加了严格的后台限制

    • 需要加入自启动白名单

  2. 小米MIUI

    • 电源管理策略更激进

    • 需要在"神隐模式"中配置

  3. OPPO ColorOS

    • 应用速冻功能可能导致ANR

    • 需要加入"允许后台运行"列表

  4. VIVO FuntouchOS

    • 内存清理机制严格

    • 需要加入后台高耗电例外列表

六、高级调试技巧

  1. 使用StrictMode检测潜在问题

    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .build());
  2. Systrace分析ANR

    python systrace.py -a com.example.app -b 16384 -o trace.html \
            sched freq idle am wm gfx view binder_driver
  3. 模拟ANR场景测试

    // 在测试代码中人为制造ANR
    @Test
    public void testANRHandling() {
        ANRWatchDog anrWatchDog = new ANRWatchDog();
        anrWatchDog.start();
        
        // 主线程休眠6秒模拟ANR
        SystemClock.sleep(6000);
        
        assertTrue(anrWatchDog.isANRDetected());
    }

通过以上系统化的监控、分析和优化方案,可以有效降低线上ANR率,提升用户体验。建议结合业务特点选择适合的监控方案,并建立长期的ANR治理机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值