ANR和Crash的监控与分析

ANR和Crash的监控与分析

在Android应用开发中,应用的稳定性是用户体验的重要组成部分。ANR(Application Not Responding)和Crash(应用崩溃)是影响应用稳定性的两大主要问题。本文将深入探讨ANR和Crash的原因、监控方法以及分析策略,帮助开发者构建更加稳定可靠的应用。

一、理解ANR

1.1 什么是ANR

ANR(Application Not Responding)是指应用程序无响应的状态。当Android系统检测到应用的主线程被阻塞超过一定时间时,就会触发ANR对话框,提示用户选择等待或关闭应用。

1.2 ANR的触发条件

不同场景下ANR的触发时间阈值:

  1. 输入事件(按键和触摸):5秒内未响应
  2. 广播接收器(BroadcastReceiver):前台广播10秒,后台广播60秒
  3. 服务(Service):前台服务20秒,后台服务200秒
  4. ContentProvider:内容提供者的发布时间超过10秒

1.3 ANR的常见原因

  1. 主线程执行耗时操作:如网络请求、文件IO、大量计算等
  2. 主线程等待子线程的锁:死锁或长时间等待
  3. 系统资源不足:CPU负载过高、内存不足等
  4. Binder通信超时:与系统服务通信时超时
  5. 频繁GC:内存抖动导致频繁垃圾回收

二、理解Crash

2.1 什么是Crash

Crash是指应用程序由于未捕获的异常或严重错误而突然终止的情况。当Crash发生时,系统会强制关闭应用,并显示"应用已停止运行"的提示。

2.2 Crash的常见类型

  1. Java层Crash:未捕获的Java异常

    • NullPointerException(空指针异常)
    • IndexOutOfBoundsException(索引越界异常)
    • IllegalArgumentException(非法参数异常)
    • OutOfMemoryError(内存溢出错误)
    • ClassCastException(类型转换异常)
  2. Native层Crash:C/C++代码中的错误

    • 内存访问越界
    • 空指针解引用
    • 栈溢出
    • 内存泄漏

三、ANR的监控与分析

3.1 系统ANR日志分析

当ANR发生时,系统会在/data/anr/目录下生成traces.txt文件,包含所有线程的调用栈信息。

# 通过adb命令获取ANR日志
adb pull /data/anr/traces.txt ./

3.2 ANR日志解读

一个典型的ANR日志包含以下信息:

----- pid 12345 at 2023-06-01 12:34:56 -----
Cmd line: com.example.myapp

JRE: OpenJDK 64-Bit Server VM (11.0.15) (build 11.0.15+0-b2043.56-8887301)

THREADS:

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74b36fb8 self=0x7bc1a43800
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7bc1a47cb0
  | state=S schedstat=( 1234567890 123456789 1234 ) utm=123 stm=45 core=1 HZ=100
  | stack=0x7ff8a5c000-0x7ff8a5e000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait(Native method)
  - waiting on <0x01234567> (a java.lang.Object)
  at java.lang.Thread.join(Thread.java:1308)
  - locked <0x01234567> (a java.lang.Object)
  at java.lang.Thread.join(Thread.java:1372)
  at com.example.myapp.MainActivity$1.onClick(MainActivity.java:100)
  at android.view.View.performClick(View.java:7448)
  at android.view.View.performClickInternal(View.java:7425)
  at android.view.View.access$3600(View.java:810)
  at android.view.View$PerformClick.run(View.java:28305)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:223)
  at android.app.ActivityThread.main(ActivityThread.java:7656)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

关键信息解读:

  1. 线程状态:Blocked、Waiting、Runnable等
  2. 调用栈:从下往上阅读,找到应用代码中的问题点
  3. 锁信息:waiting on、locked等表示锁的状态
  4. CPU和调度信息:schedstat、utm、stm等

3.3 自定义ANR监控

3.3.1 基于FileObserver的监控
public class AnrMonitor {
    private static final String ANR_TRACE_PATH = "/data/anr/traces.txt";
    private FileObserver fileObserver;

    public void start() {
        if (fileObserver == null) {
            fileObserver = new FileObserver(ANR_TRACE_PATH, FileObserver.MODIFY) {
                @Override
                public void onEvent(int event, String path) {
                    if (event == FileObserver.MODIFY) {
                        // 检测到ANR文件变化
                        readAnrTrace();
                    }
                }
            };
            fileObserver.startWatching();
        }
    }

    private void readAnrTrace() {
        // 需要root权限或系统应用权限
        try {
            Process process = Runtime.getRuntime().exec("su -c cat " + ANR_TRACE_PATH);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            reader.close();
            process.waitFor();
            
            // 分析ANR日志
            analyzeAnrTrace(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void analyzeAnrTrace(String trace) {
        // 解析ANR日志,提取关键信息
        // 上报到服务器或本地存储
    }

    public void stop() {
        if (fileObserver != null) {
            fileObserver.stopWatching();
            fileObserver = null;
        }
    }
}
3.3.2 基于主线程消息队列监控
public class MainThreadMonitor {
    private static final long CHECK_INTERVAL = 1000; // 检查间隔1秒
    private static final long ANR_THRESHOLD = 5000; // ANR阈值5秒
    
    private Handler mainHandler;
    private Handler checkHandler;
    private HandlerThread checkThread;
    private Runnable checkRunnable;
    private long lastResponseTime;
    
    public void start() {
        mainHandler = new Handler(Looper.getMainLooper());
        checkThread = new HandlerThread("ANRCheckThread");
        checkThread.start();
        checkHandler = new Handler(checkThread.getLooper());
        
        lastResponseTime = System.currentTimeMillis();
        
        // 在主线程定期更新时间戳
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                lastResponseTime = System.currentTimeMillis();
                mainHandler.postDelayed(this, CHECK_INTERVAL / 2);
            }
        });
        
        // 在子线程检查主线程是否响应
        checkRunnable = new Runnable() {
            @Override
            public void run() {
                long currentTime = System.currentTimeMillis();
                long blockTime = currentTime - lastResponseTime;
                
                if (blockTime >= ANR_THRESHOLD) {
                    // 检测到可能的ANR
                    dumpMainThreadStack();
                }
                
                checkHandler.postDelayed(this, CHECK_INTERVAL);
            }
        };
        
        checkHandler.post(checkRunnable);
    }
    
    private void dumpMainThreadStack() {
        StringBuilder sb = new StringBuilder();
        StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
        
        for (StackTraceElement element : stackTrace) {
            sb.append(element.toString()).append("\n");
        }
        
        // 记录或上报主线程堆栈
        Log.e("ANRMonitor", "Possible ANR detected:\n" + sb.toString());
        // 可以添加上报逻辑
    }
    
    public void stop() {
        if (mainHandler != null) {
            mainHandler.removeCallbacksAndMessages(null);
        }
        
        if (checkHandler != null) {
            checkHandler.removeCallbacks(checkRunnable);
        }
        
        if (checkThread != null) {
            checkThread.quit();
        }
    }
}

3.4 使用开源工具监控ANR

3.4.1 使用ANR-WatchDog
dependencies {
    implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
}
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        // 初始化ANR-WatchDog
        ANRWatchDog anrWatchDog = new ANRWatchDog(5000); // 设置超时为5秒
        anrWatchDog.setANRListener(new ANRWatchDog.ANRListener() {
            @Override
            public void onAppNotResponding(ANRError error) {
                // 获取ANR详细信息
                String anrMessage = error.getMessage();
                String stackTrace = Log.getStackTraceString(error);
                
                // 上报ANR信息
                reportANR(anrMessage, stackTrace);
            }
        });
        
        // 设置错误处理策略
        anrWatchDog.setANRInterceptor(new ANRWatchDog.ANRInterceptor() {
            @Override
            public long intercept(long duration) {
                if (duration < 10000) {
                    // 如果阻塞时间小于10秒,继续等待
                    return 1000; // 再等待1秒
                }
                return 0; // 立即报告ANR
            }
        });
        
        anrWatchDog.start();
    }
    
    private void reportANR(String message, String stackTrace) {
        // 实现上报逻辑
    }
}

四、Crash的监控与分析

4.1 Java层Crash监控

4.1.1 使用UncaughtExceptionHandler
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static CrashHandler instance;
    private Thread.UncaughtExceptionHandler defaultHandler;
    private Context context;
    
    private CrashHandler() {
    }
    
    public static CrashHandler getInstance() {
        if (instance == null) {
            synchronized (CrashHandler.class) {
                if (instance == null) {
                    instance = new CrashHandler();
                }
            }
        }
        return instance;
    }
    
    public void init(Context context) {
        this.context = context.getApplicationContext();
        defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && defaultHandler != null) {
            // 如果用户没有处理则让系统默认的异常处理器来处理
            defaultHandler.uncaughtException(thread, ex);
        } else {
            // 等待日志写入完成
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
    
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        
        try {
            // 收集设备信息
            collectDeviceInfo();
            // 保存错误日志
            saveCrashInfo(ex);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    private void collectDeviceInfo() {
        try {
            PackageManager pm = context.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            
            // 收集应用信息
            Map<String, String> info = new HashMap<>();
            info.put("versionName", pi.versionName);
            info.put("versionCode", String.valueOf(pi.versionCode));
            
            // 收集设备信息
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                info.put(field.getName(), field.get(null).toString());
            }
            
            // 保存设备信息
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> entry : info.entrySet()) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("\n");
            }
            FileOutputStream fos = context.openFileOutput("device_info.txt", Context.MODE_PRIVATE);
            fos.write(sb.toString().getBytes());
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void saveCrashInfo(Throwable ex) {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            ex.printStackTrace(pw);
            
            String crashTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
                    .format(new Date());
            
            StringBuilder sb = new StringBuilder();
            sb.append("Crash Time: ").append(crashTime).append("\n");
            sb.append("Exception Type: ").append(ex.getClass().getName()).append("\n");
            sb.append("Exception Message: ").append(ex.getMessage()).append("\n");
            sb.append("Stack Trace:\n").append(sw.toString());
            
            // 保存崩溃日志
            String fileName = "crash_" + crashTime.replace(" ", "_") + ".log";
            FileOutputStream fos = context.openFileOutput(fileName, Context.MODE_PRIVATE);
            fos.write(sb.toString().getBytes());
            fos.close();
            
            // 可以在这里添加上报逻辑
            uploadCrashLog(fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void uploadCrashLog(String fileName) {
        // 实现上报逻辑
    }
}

4.2 Native层Crash监控

4.2.1 使用Breakpad

Google Breakpad是一个跨平台的崩溃报告系统,可以用来收集和分析Native崩溃。

  1. 添加依赖:
android {
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation 'com.google.breakpad:breakpad-android:1.1.0'
}
  1. 初始化Breakpad:
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/minidump_descriptor.h"

bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
                 void* context,
                 bool succeeded) {
    // 处理dump文件
    return succeeded;
}

void InitBreakpad() {
    std::string dump_path = "/data/data/your.package.name/files/dumps";
    google_breakpad::MinidumpDescriptor descriptor(dump_path);
    static google_breakpad::ExceptionHandler eh(descriptor,
                                              NULL,
                                              DumpCallback,
                                              NULL,
                                              true,
                                              -1);
}
  1. 在Java层调用初始化:
public class NativeCrashHandler {
    static {
        System.loadLibrary("native-lib");
    }
    
    public static native void initBreakpad();
    
    public static void init() {
        initBreakpad();
    }
}

4.3 使用开源工具

4.3.1 集成Bugly

腾讯Bugly是一个功能强大的异常上报和运营统计平台。

  1. 添加依赖:
dependencies {
    implementation 'com.tencent.bugly:crashreport:latest.release'
    implementation 'com.tencent.bugly:nativecrashreport:latest.release'
}
  1. 初始化Bugly:
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        // 初始化Bugly
        CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false);
        
        // 设置用户数据
        CrashReport.setUserId("user_id");
        
        // 添加自定义日志
        CrashReport.putUserData(context, "key", "value");
        
        // 测试crash上报
        CrashReport.testJavaCrash();
    }
}

五、最佳实践

5.1 ANR预防

  1. 避免主线程耗时操作

    • 使用协程或线程池处理耗时任务
    • 使用WorkManager处理后台任务
    • 合理使用缓存机制
  2. 优化UI性能

    • 使用ViewHolder模式
    • 避免过度绘制
    • 使用硬件加速
  3. 合理使用IPC

    • 避免同步Binder调用
    • 使用AIDL异步接口
    • 控制进程间通信频率

5.2 Crash处理策略

  1. 异常捕获原则

    • 只捕获可处理的异常
    • 提供恢复机制
    • 保留现场信息
  2. 日志管理

    • 分级存储
    • 定期清理
    • 加密敏感信息
  3. 上报策略

    • 立即上报严重错误
    • 批量上报普通错误
    • 控制上报频率

六、面试题解析

6.1 常见面试题

  1. ANR产生的原因有哪些?如何定位和解决?

答:ANR主要由以下原因产生:

  • 主线程执行耗时操作
  • 主线程等待子线程的锁
  • 系统资源不足
  • Binder通信超时
  • 频繁GC

定位方法:

  • 分析traces.txt文件
  • 使用Systrace工具
  • 使用ANR-WatchDog等工具监控

解决方案:

  • 将耗时操作放到子线程
  • 优化锁使用
  • 合理管理系统资源
  • 优化Binder调用
  • 减少内存抖动
  1. 如何监控应用的崩溃?

答:可以通过以下方式:

  • 使用UncaughtExceptionHandler捕获Java层异常
  • 使用Breakpad捕获Native层异常
  • 集成第三方工具如Bugly
  • 自定义崩溃收集系统
  1. 如何降低应用的崩溃率?

答:可以采取以下措施:

  • 完善异常处理机制
  • 做好防御性编程
  • 增加自动化测试覆盖率
  • 建立灰度发布机制
  • 监控关键指标
  • 及时修复已知问题

6.2 实战经验

  1. 监控指标

    • 崩溃率 = 崩溃次数 / 启动次数
    • ANR率 = ANR次数 / 使用时长
    • 影响用户数
    • 重复发生率
  2. 优化流程

    • 问题收集
    • 现场保留
    • 原因分析
    • 修复验证
    • 效果跟踪
  3. 工具使用

    • Android Studio Profiler
    • systrace
    • ANR-WatchDog
    • LeakCanary
    • Bugly/Firebase

总结

ANR和Crash监控是保证应用质量的重要手段。通过合理的监控机制、分析工具和优化策略,我们可以:

  1. 及时发现和定位问题
  2. 提供更好的用户体验
  3. 建立完善的质量保障体系
  4. 持续优化应用性能

在实际开发中,我们需要结合具体场景,选择合适的监控方案,并建立完整的处理流程,从而提高应用的稳定性和可靠性。

你可能感兴趣的:(android)