在Android应用开发中,应用的稳定性是用户体验的重要组成部分。ANR(Application Not Responding)和Crash(应用崩溃)是影响应用稳定性的两大主要问题。本文将深入探讨ANR和Crash的原因、监控方法以及分析策略,帮助开发者构建更加稳定可靠的应用。
ANR(Application Not Responding)是指应用程序无响应的状态。当Android系统检测到应用的主线程被阻塞超过一定时间时,就会触发ANR对话框,提示用户选择等待或关闭应用。
不同场景下ANR的触发时间阈值:
Crash是指应用程序由于未捕获的异常或严重错误而突然终止的情况。当Crash发生时,系统会强制关闭应用,并显示"应用已停止运行"的提示。
Java层Crash:未捕获的Java异常
Native层Crash:C/C++代码中的错误
当ANR发生时,系统会在/data/anr/
目录下生成traces.txt文件,包含所有线程的调用栈信息。
# 通过adb命令获取ANR日志
adb pull /data/anr/traces.txt ./
一个典型的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)
关键信息解读:
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;
}
}
}
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();
}
}
}
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) {
// 实现上报逻辑
}
}
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) {
// 实现上报逻辑
}
}
Google Breakpad是一个跨平台的崩溃报告系统,可以用来收集和分析Native崩溃。
android {
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation 'com.google.breakpad:breakpad-android:1.1.0'
}
#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);
}
public class NativeCrashHandler {
static {
System.loadLibrary("native-lib");
}
public static native void initBreakpad();
public static void init() {
initBreakpad();
}
}
腾讯Bugly是一个功能强大的异常上报和运营统计平台。
dependencies {
implementation 'com.tencent.bugly:crashreport:latest.release'
implementation 'com.tencent.bugly:nativecrashreport:latest.release'
}
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();
}
}
避免主线程耗时操作
优化UI性能
合理使用IPC
异常捕获原则
日志管理
上报策略
答:ANR主要由以下原因产生:
定位方法:
解决方案:
答:可以通过以下方式:
答:可以采取以下措施:
监控指标
优化流程
工具使用
ANR和Crash监控是保证应用质量的重要手段。通过合理的监控机制、分析工具和优化策略,我们可以:
在实际开发中,我们需要结合具体场景,选择合适的监控方案,并建立完整的处理流程,从而提高应用的稳定性和可靠性。