Android Tinker运行时错误上报流程原理深度剖析
一、引言
在Android应用开发中,热修复技术能够快速修复线上问题,提升用户体验。Tinker作为一款优秀的热修复框架,其运行时错误上报流程是保证框架稳定性和可维护性的关键。本文将从源码级别深入分析Tinker的运行时错误上报流程原理,详细阐述每一个步骤的具体实现和作用,帮助开发者更好地理解和使用Tinker框架。
二、运行时错误上报概述
2.1 运行时错误的定义
运行时错误是指应用在运行过程中出现的异常情况,包括未捕获的异常(Uncaught Exception)和ANR(Application Not Responding)等。这些错误会导致应用崩溃或无响应,严重影响用户体验。
2.2 错误上报的重要性
及时准确地收集和上报运行时错误信息,对于开发者来说至关重要。通过分析错误信息,开发者可以快速定位问题,及时修复,提高应用的稳定性和可靠性。
2.3 Tinker错误上报的特点
Tinker的错误上报机制具有以下特点:
- 与热修复功能深度集成:能够区分正常错误和热修复相关错误
- 轻量级:对应用性能影响小
- 高度可定制:支持自定义错误处理和上报逻辑
- 多维度数据收集:收集丰富的错误上下文信息,帮助开发者快速定位问题
三、错误捕获机制
3.1 全局异常处理器
Tinker通过注册全局异常处理器来捕获未被处理的异常。源码如下:
// TinkerUncaughtHandler.java
public class TinkerUncaughtHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "Tinker.UncaughtHandler";
private final Thread.UncaughtExceptionHandler originalHandler;
public TinkerUncaughtHandler() {
// 获取原始的异常处理器
this.originalHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置当前实例为全局异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// 处理异常
handleException(e);
// 如果原始处理器不为空,则调用原始处理器处理异常
if (originalHandler != null) {
originalHandler.uncaughtException(t, e);
} else {
// 否则终止当前线程
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
private void handleException(Throwable e) {
// 记录异常信息
TinkerLog.e(TAG, "uncaughtException: " + e.getMessage(), e);
// 检查是否是Tinker相关异常
if (isTinkerRelatedException(e)) {
// 处理Tinker相关异常
handleTinkerException(e);
}
// 收集错误信息
collectErrorInfo(e);
// 上报错误信息
reportErrorInfo();
}
// 其他辅助方法...
}
3.2 ANR监控机制
Tinker还实现了ANR监控机制,用于捕获应用无响应的情况。源码如下:
// AnrMonitor.java
public class AnrMonitor {
private static final String TAG = "Tinker.AnrMonitor";
private static final long ANR_TIMEOUT = 5000; // 5秒
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final Object lock = new Object();
private boolean monitoring = false;
private long lastCheckTime = 0;
public void start() {
if (monitoring) {
return;
}
monitoring = true;
// 启动定时检查任务
mainHandler.postDelayed(new Runnable() {
@Override
public void run() {
checkAnr();
if (monitoring) {
mainHandler.postDelayed(this, ANR_TIMEOUT);
}
}
}, ANR_TIMEOUT);
}
public void stop() {
monitoring = false;
mainHandler.removeCallbacksAndMessages(null);
}
private void checkAnr() {
synchronized (lock) {
long currentTime = System.currentTimeMillis();
// 如果主线程处理时间过长,认为发生了ANR
if (currentTime - lastCheckTime > ANR_TIMEOUT * 2) {
TinkerLog.e(TAG, "ANR detected!");
// 收集ANR信息
collectAnrInfo();
// 上报ANR信息
reportAnrInfo();
}
lastCheckTime = currentTime;
}
}
// 其他辅助方法...
}
四、错误信息收集
4.1 基本错误信息收集
Tinker会收集基本的错误信息,包括异常堆栈、错误类型等。源码如下:
// ErrorReporter.java
public class ErrorReporter {
private static final String TAG = "Tinker.ErrorReporter";
public static void collectErrorInfo(Throwable throwable) {
if (throwable == null) {
return;
}
// 创建错误信息对象
ErrorInfo errorInfo = new ErrorInfo();
// 设置错误类型
errorInfo.setErrorType(throwable.getClass().getName());
// 设置错误消息
errorInfo.setErrorMessage(throwable.getMessage());
// 设置错误堆栈
errorInfo.setStackTrace(getStackTrace(throwable));
// 设置时间戳
errorInfo.setTimestamp(System.currentTimeMillis());
// 保存错误信息
saveErrorInfo(errorInfo);
}
private static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
// 打印异常堆栈
throwable.printStackTrace(pw);
// 获取嵌套异常堆栈
Throwable cause = throwable.getCause();
while (cause != null) {
pw.print("\nCaused by: ");
cause.printStackTrace(pw);
cause = cause.getCause();
}
return sw.toString();
} finally {
pw.close();
}
}
// 其他辅助方法...
}
4.2 环境信息收集
除了基本错误信息,Tinker还会收集应用运行的环境信息,帮助开发者更好地理解错误发生的上下文。源码如下:
// EnvironmentCollector.java
public class EnvironmentCollector {
private static final String TAG = "Tinker.EnvCollector";
public static EnvironmentInfo collectEnvironmentInfo(Context context) {
if (context == null) {
return null;
}
EnvironmentInfo envInfo = new EnvironmentInfo();
try {
// 收集设备信息
collectDeviceInfo(envInfo);
// 收集应用信息
collectAppInfo(context, envInfo);
// 收集Tinker信息
collectTinkerInfo(context, envInfo);
// 收集内存信息
collectMemoryInfo(envInfo);
// 收集CPU信息
collectCpuInfo(envInfo);
// 收集电池信息
collectBatteryInfo(context, envInfo);
// 收集网络信息
collectNetworkInfo(context, envInfo);
} catch (Exception e) {
TinkerLog.e(TAG, "collectEnvironmentInfo: Exception", e);
}
return envInfo;
}
// 其他辅助方法...
}
4.3 Tinker相关信息收集
Tinker会特别收集与热修复相关的信息,包括补丁版本、补丁状态等。源码如下:
// TinkerInfoCollector.java
public class TinkerInfoCollector {
private static final String TAG = "Tinker.InfoCollector";
public static void collectTinkerInfo(Context context, EnvironmentInfo envInfo) {
if (context == null || envInfo == null) {
return;
}
try {
// 获取Tinker实例
Tinker tinker = Tinker.with(context);
// 获取补丁信息
PatchInfo patchInfo = tinker.getPatchInfo();
if (patchInfo != null) {
// 设置补丁版本
envInfo.setPatchVersion(patchInfo.getNewVersion());
// 设置补丁状态
envInfo.setPatchState(patchInfo.isEnabled() ? "enabled" : "disabled");
// 设置补丁路径
envInfo.setPatchPath(patchInfo.getPatchPath());
// 设置补丁加载时间
envInfo.setPatchLoadTime(patchInfo.getLoadTime());
}
// 获取Tinker运行模式
envInfo.setTinkerRunMode(tinker.isMainProcess() ? "main_process" : "other_process");
// 获取Tinker版本
envInfo.setTinkerVersion(TinkerInstaller.getTinkerVersion(context));
} catch (Exception e) {
TinkerLog.e(TAG, "collectTinkerInfo: Exception", e);
}
}
}
五、错误信息存储
5.1 错误信息序列化
Tinker会将收集到的错误信息序列化为JSON格式,以便存储和传输。源码如下:
// ErrorInfoSerializer.java
public class ErrorInfoSerializer {
private static final String TAG = "Tinker.ErrorSerializer";
public static String serialize(ErrorInfo errorInfo) {
if (errorInfo == null) {
return null;
}
try {
// 创建JSON对象
JSONObject jsonObject = new JSONObject();
// 设置基本错误信息
jsonObject.put("errorType", errorInfo.getErrorType());
jsonObject.put("errorMessage", errorInfo.getErrorMessage());
jsonObject.put("stackTrace", errorInfo.getStackTrace());
jsonObject.put("timestamp", errorInfo.getTimestamp());
// 设置环境信息
if (errorInfo.getEnvironmentInfo() != null) {
jsonObject.put("environmentInfo", serializeEnvironmentInfo(errorInfo.getEnvironmentInfo()));
}
return jsonObject.toString();
} catch (JSONException e) {
TinkerLog.e(TAG, "serialize: JSONException", e);
return null;
}
}
private static JSONObject serializeEnvironmentInfo(EnvironmentInfo envInfo) throws JSONException {
JSONObject envJson = new JSONObject();
// 设置设备信息
envJson.put("deviceInfo", serializeDeviceInfo(envInfo.getDeviceInfo()));
// 设置应用信息
envJson.put("appInfo", serializeAppInfo(envInfo.getAppInfo()));
// 设置Tinker信息
envJson.put("tinkerInfo", serializeTinkerInfo(envInfo.getTinkerInfo()));
// 设置系统信息
envJson.put("systemInfo", serializeSystemInfo(envInfo.getSystemInfo()));
// 设置性能信息
envJson.put("performanceInfo", serializePerformanceInfo(envInfo.getPerformanceInfo()));
return envJson;
}
// 其他辅助方法...
}
5.2 错误信息存储到文件
Tinker会将序列化后的错误信息存储到本地文件中。源码如下:
// ErrorStorage.java
public class ErrorStorage {
private static final String TAG = "Tinker.ErrorStorage";
private static final String ERROR_DIR = "tinker_error";
private static final String ERROR_FILE_PREFIX = "error_";
private static final String ERROR_FILE_SUFFIX = ".json";
private static final int MAX_ERROR_FILES = 100; // 最多保存100个错误文件
private final Context context;
public ErrorStorage(Context context) {
this.context = context.getApplicationContext();
}
public void saveErrorInfo(ErrorInfo errorInfo) {
if (errorInfo == null) {
return;
}
try {
// 获取错误目录
File errorDir = getErrorDirectory();
// 创建错误文件
File errorFile = createErrorFile(errorDir);
// 序列化错误信息
String errorJson = ErrorInfoSerializer.serialize(errorInfo);
if (errorJson != null) {
// 写入文件
FileOutputStream fos = new FileOutputStream(errorFile);
fos.write(errorJson.getBytes());
fos.close();
TinkerLog.i(TAG, "Error info saved to: " + errorFile.getAbsolutePath());
}
// 清理旧的错误文件
cleanOldErrorFiles(errorDir);
} catch (Exception e) {
TinkerLog.e(TAG, "saveErrorInfo: Exception", e);
}
}
private File getErrorDirectory() {
File errorDir = new File(context.getFilesDir(), ERROR_DIR);
if (!errorDir.exists()) {
errorDir.mkdirs();
}
return errorDir;
}
// 其他辅助方法...
}
六、错误上报策略
6.1 上报条件判断
Tinker会根据一定的条件判断是否上报错误信息,避免过多的上报请求。源码如下:
// ErrorReportPolicy.java
public class ErrorReportPolicy {
private static final String TAG = "Tinker.ReportPolicy";
private static final int MAX_REPORTS_PER_DAY = 10; // 每天最多上报10次
private static final int MIN_INTERVAL = 5 * 60 * 1000; // 两次上报之间的最小间隔为5分钟
private final Context context;
private final SharedPreferences preferences;
public ErrorReportPolicy(Context context) {
this.context = context.getApplicationContext();
this.preferences = context.getSharedPreferences("tinker_error_policy", Context.MODE_PRIVATE);
}
public boolean shouldReport() {
try {
// 检查网络连接
if (!isNetworkAvailable()) {
TinkerLog.i(TAG, "Network not available, skip reporting");
return false;
}
// 检查上报频率
if (!checkReportFrequency()) {
TinkerLog.i(TAG, "Report frequency exceeded, skip reporting");
return false;
}
// 检查是否处于调试模式
if (isDebugMode()) {
TinkerLog.i(TAG, "App is in debug mode, skip reporting");
return false;
}
return true;
} catch (Exception e) {
TinkerLog.e(TAG, "shouldReport: Exception", e);
return false;
}
}
private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
// 其他辅助方法...
}
6.2 上报时机选择
Tinker会选择合适的时机上报错误信息,避免影响用户体验。源码如下:
// ErrorReportScheduler.java
public class ErrorReportScheduler {
private static final String TAG = "Tinker.ReportScheduler";
private static final long INITIAL_DELAY = 10 * 1000; // 初始延迟10秒
private static final long PERIOD = 30 * 60 * 1000; // 周期为30分钟
private final Context context;
private final ScheduledExecutorService executorService;
private final ErrorReportPolicy reportPolicy;
private final ErrorReporter errorReporter;
public ErrorReportScheduler(Context context) {
this.context = context.getApplicationContext();
this.executorService = Executors.newSingleThreadScheduledExecutor();
this.reportPolicy = new ErrorReportPolicy(context);
this.errorReporter = new ErrorReporter(context);
}
public void start() {
// 提交定时任务
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
// 检查是否应该上报
if (reportPolicy.shouldReport()) {
// 执行上报
errorReporter.reportPendingErrors();
}
} catch (Exception e) {
TinkerLog.e(TAG, "Report task error", e);
}
}
}, INITIAL_DELAY, PERIOD, TimeUnit.MILLISECONDS);
}
public void stop() {
executorService.shutdownNow();
}
// 其他辅助方法...
}
七、错误上报网络实现
7.1 网络请求封装
Tinker使用OkHttp封装网络请求,实现错误信息的上报。源码如下:
// NetworkClient.java
public class NetworkClient {
private static final String TAG = "Tinker.NetworkClient";
private static final String REPORT_URL = "https://example.com/tinker/report";
private final OkHttpClient client;
public NetworkClient() {
// 配置OkHttp客户端
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
}
public boolean reportError(String errorJson) {
if (errorJson == null || errorJson.isEmpty()) {
return false;
}
try {
// 创建请求体
RequestBody requestBody = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
errorJson
);
// 创建请求
Request request = new Request.Builder()
.url(REPORT_URL)
.post(requestBody)
.build();
// 执行请求
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
TinkerLog.i(TAG, "Error reported successfully");
return true;
} else {
TinkerLog.w(TAG, "Error report failed, code: " + response.code());
return false;
}
} catch (Exception e) {
TinkerLog.e(TAG, "reportError: Exception", e);
return false;
}
}
// 其他辅助方法...
}
7.2 批量上报实现
为了减少网络请求,Tinker会将多个错误信息批量上报。源码如下:
// ErrorReporter.java
public class ErrorReporter {
private static final String TAG = "Tinker.ErrorReporter";
private static final int BATCH_SIZE = 5; // 每次上报5个错误
private final Context context;
private final ErrorStorage errorStorage;
private final NetworkClient networkClient;
public ErrorReporter(Context context) {
this.context = context.getApplicationContext();
this.errorStorage = new ErrorStorage(context);
this.networkClient = new NetworkClient();
}
public void reportPendingErrors() {
try {
// 获取待上报的错误文件
List<File> errorFiles = errorStorage.getPendingErrorFiles();
if (errorFiles == null || errorFiles.isEmpty()) {
TinkerLog.i(TAG, "No pending errors to report");
return;
}
// 分批上报
int batchCount = (int) Math.ceil((double) errorFiles.size() / BATCH_SIZE);
for (int i = 0; i < batchCount; i++) {
int start = i * BATCH_SIZE;
int end = Math.min(start + BATCH_SIZE, errorFiles.size());
List<File> batchFiles = errorFiles.subList(start, end);
// 合并错误信息
String batchJson = mergeErrorFiles(batchFiles);
if (batchJson != null) {
// 上报
boolean success = networkClient.reportError(batchJson);
// 如果上报成功,删除已上报的错误文件
if (success) {
for (File file : batchFiles) {
file.delete();
}
}
}
}
} catch (Exception e) {
TinkerLog.e(TAG, "reportPendingErrors: Exception", e);
}
}
// 其他辅助方法...
}
八、错误上报结果处理
8.1 上报成功处理
当错误信息上报成功后,Tinker会删除已上报的错误文件,并记录上报成功信息。源码如下:
// ErrorReporter.java
private void handleReportSuccess(List<File> reportedFiles) {
if (reportedFiles == null || reportedFiles.isEmpty()) {
return;
}
try {
// 删除已上报的错误文件
for (File file : reportedFiles) {
if (file.exists()) {
file.delete();
}
}
// 记录上报成功信息
int count = reportedFiles.size();
TinkerLog.i(TAG, "Successfully reported " + count + " error(s)");
// 更新上报统计信息
updateReportStats(count);
} catch (Exception e) {
TinkerLog.e(TAG, "handleReportSuccess: Exception", e);
}
}
8.2 上报失败处理
当错误信息上报失败时,Tinker会保留错误文件,并记录上报失败信息。源码如下:
// ErrorReporter.java
private void handleReportFailure(List<File> failedFiles) {
if (failedFiles == null || failedFiles.isEmpty()) {
return;
}
try {
// 记录上报失败信息
int count = failedFiles.size();
TinkerLog.w(TAG, "Failed to report " + count + " error(s)");
// 更新失败统计信息
updateFailureStats(count);
// 安排重试
scheduleRetry();
} catch (Exception e) {
TinkerLog.e(TAG, "handleReportFailure: Exception", e);
}
}
九、错误信息展示与分析
9.1 开发者后台展示
Tinker提供了开发者后台,用于展示和分析上报的错误信息。开发者可以在后台查看错误详情、统计数据等。
9.2 错误分类与聚合
Tinker会对上报的错误信息进行分类和聚合,方便开发者快速定位和解决问题。源码如下:
// ErrorAnalyzer.java
public class ErrorAnalyzer {
private static final String TAG = "Tinker.ErrorAnalyzer";
public static List<ErrorGroup> analyzeErrors(List<ErrorInfo> errorInfos) {
if (errorInfos == null || errorInfos.isEmpty()) {
return Collections.emptyList();
}
// 按错误类型分组
Map<String, List<ErrorInfo>> errorGroups = new HashMap<>();
for (ErrorInfo errorInfo : errorInfos) {
String errorType = errorInfo.getErrorType();
if (!errorGroups.containsKey(errorType)) {
errorGroups.put(errorType, new ArrayList<>());
}
errorGroups.get(errorType).add(errorInfo);
}
// 转换为错误分组对象
List<ErrorGroup> result = new ArrayList<>();
for (Map.Entry<String, List<ErrorInfo>> entry : errorGroups.entrySet()) {
ErrorGroup group = new ErrorGroup();
group.setErrorType(entry.getKey());
group.setErrorCount(entry.getValue().size());
group.setErrorInfos(entry.getValue());
// 计算最早和最晚时间
long earliestTime = Long.MAX_VALUE;
long latestTime = Long.MIN_VALUE;
for (ErrorInfo errorInfo : entry.getValue()) {
long timestamp = errorInfo.getTimestamp();
earliestTime = Math.min(earliestTime, timestamp);
latestTime = Math.max(latestTime, timestamp);
}
group.setFirstOccurrenceTime(earliestTime);
group.setLastOccurrenceTime(latestTime);
result.add(group);
}
// 按错误数量排序
Collections.sort(result, (g1, g2) -> g2.getErrorCount() - g1.getErrorCount());
return result;
}
// 其他辅助方法...
}
十、自定义错误上报
10.1 自定义错误处理器
开发者可以通过实现自定义错误处理器,扩展Tinker的错误上报功能。源码如下:
// CustomErrorHandler.java
public class CustomErrorHandler implements TinkerUncaughtHandler.ErrorHandler {
private static final String TAG = "CustomErrorHandler";
@Override
public void handleError(Throwable throwable) {
// 记录错误信息
TinkerLog.e(TAG, "Custom error handling: " + throwable.getMessage(), throwable);
// 收集额外的错误信息
collectAdditionalErrorInfo(throwable);
// 调用默认的错误处理逻辑
TinkerUncaughtHandler.getDefaultHandler().handleError(throwable);
}
private void collectAdditionalErrorInfo(Throwable throwable) {
// 收集额外的错误信息,如用户行为、应用状态等
// ...
}
// 其他自定义方法...
}
10.2 注册自定义错误处理器
开发者可以在应用启动时注册自定义错误处理器。源码如下:
// MyApplication.java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注册自定义错误处理器
TinkerUncaughtHandler.registerErrorHandler(new CustomErrorHandler());
// 初始化Tinker
TinkerInstaller.install(this);
}
}
十一、错误上报性能优化
11.1 异步处理
Tinker的错误上报采用异步处理方式,避免阻塞主线程。源码如下:
// AsyncErrorReporter.java
public class AsyncErrorReporter {
private static final String TAG = "Tinker.AsyncReporter";
private static final int CORE_POOL_SIZE = 1;
private static final int MAX_POOL_SIZE = 3;
private static final long KEEP_ALIVE_TIME = 60L;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
private final ExecutorService executorService;
private final ErrorReporter errorReporter;
public AsyncErrorReporter(Context context) {
// 创建线程池
executorService = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TIME_UNIT,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Tinker-Error-Reporter-" + threadNumber.getAndIncrement());
}
}
);
this.errorReporter = new ErrorReporter(context);
}
public void reportErrorAsync(final Throwable throwable) {
if (throwable == null) {
return;
}
// 提交任务到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
// 收集错误信息
ErrorInfo errorInfo = ErrorInfoCollector.collectErrorInfo(throwable);
if (errorInfo != null) {
// 保存错误信息
errorReporter.saveErrorInfo(errorInfo);
// 检查是否需要立即上报
if (shouldReportImmediately(throwable)) {
errorReporter.reportPendingErrors();
}
}
} catch (Exception e) {
TinkerLog.e(TAG, "reportErrorAsync: Exception", e);
}
}
});
}
// 其他辅助方法...
}
11.2 上报频率控制
Tinker通过控制上报频率,避免过多的上报请求影响应用性能。源码如下:
// ErrorReportPolicy.java
private boolean checkReportFrequency() {
long currentTime = System.currentTimeMillis();
long lastReportTime = preferences.getLong("last_report_time", 0);
int todayReports = preferences.getInt("today_reports", 0);
// 检查是否是新的一天
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(currentTime);
int currentDay = calendar.get(Calendar.DAY_OF_YEAR);
calendar.setTimeInMillis(lastReportTime);
int lastReportDay = calendar.get(Calendar.DAY_OF_YEAR);
if (currentDay != lastReportDay) {
// 新的一天,重置计数
Editor editor = preferences.edit();
editor.putInt("today_reports", 0);
editor.apply();
todayReports = 0;
}
// 检查上报次数是否超过限制
if (todayReports >= MAX_REPORTS_PER_DAY) {
return false;
}
// 检查上报间隔
if (currentTime - lastReportTime < MIN_INTERVAL) {
return false;
}
return true;
}