前言:
針對(duì)crash業(yè)務(wù)分析
代碼
Android 10.0
詳細(xì):
一侧戴、日志分析
1.異常彈框日志
:54.358 1066 1066 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.post(java.lang.Runnable)' on a null object reference
Line 4065: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at com.example.myapplication.AutoCameraTestActivity.onClick(AutoCameraTestActivity.java:106)
Line 4066: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.view.View.performClick(View.java:5637)
Line 4067: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.view.View$PerformClick.run(View.java:22445)
Line 4068: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:755)
Line 4069: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:95)
Line 4070: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.os.Looper.loop(Looper.java:154)
Line 4071: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6141)
Line 4072: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
Line 4073: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
Line 4074: 11-05 23:17:54.358 1066 1066 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)
2.進(jìn)程啟動(dòng)加載的數(shù)據(jù)
1149 1149 W System.err: java.lang.Exception: RuntimeInit
11-05 23:17:47.687 1149 1149 W System.err: at com.android.internal.os.RuntimeInit.commonInit(RuntimeInit.java:122)
11-05 23:17:47.687 1149 1149 W System.err: at com.android.internal.os.RuntimeInit.zygoteInit(RuntimeInit.java:288)
11-05 23:17:47.687 1149 1149 W System.err: at com.android.internal.os.ZygoteConnection.handleChildProc(ZygoteConnection.java:757)
11-05 23:17:47.687 1149 1149 W System.err: at com.android.internal.os.ZygoteConnection.runOnce(ZygoteConnection.java:243)
11-05 23:17:47.687 1149 1149 W System.err: at com.android.internal.os.ZygoteInit.runSelectLoop(ZygoteInit.java:876)
11-05 23:17:47.688 1149 1149 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:798)
二、代碼分析
1.java層的異常彈框監(jiān)聽
com.android.internal.os.RuntimeInit.java
private static final void commonInit() {
//1.設(shè)置預(yù)處理異常業(yè)務(wù),目的輸出異常日志。此接口setUncaughtExceptionPreHandler為hide
//2.設(shè)置默認(rèn)異常處理業(yè)務(wù)及穗,目的是kill應(yīng)用和定制ams控制業(yè)務(wù)
//1和2分開,可以避免普通應(yīng)用把異常日志捕獲而系統(tǒng)無法留檔
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
···
}
//預(yù)處理中的異常信息記錄
//注意:普通應(yīng)用和system_server應(yīng)用異常日志區(qū)別
//共同TAG:FATAL EXCEPTION
//異常日志采用ID為L(zhǎng)OG_ID_CRASH的方式記錄:Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr)
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
// Don't re-enter if KillApplicationHandler has already run
if (mCrashing) return;
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
//system_server異常绵载,日志打印
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
//普通應(yīng)用異常埂陆,日志打印
StringBuilder message = new StringBuilder();
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
final String processName = ActivityThread.currentProcessName();
if (processName != null) {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
Clog_e(TAG, message.toString(), e);
}
}
}
//通知ams處理異常業(yè)務(wù)
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
//如果日志在預(yù)處理之前沒有抓取到,則再抓取一次
ensureLogging(t, e);
if (mCrashing) return;
mCrashing = true;
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
//通知ams處理異常業(yè)務(wù),例如通知dropbox記錄異常信息娃豹、彈異常對(duì)話框等等
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
//通過kill -9殺應(yīng)用
Process.killProcess(Process.myPid());
System.exit(10);
}
}
private void ensureLogging(Thread t, Throwable e) {
if (!mLoggingHandler.mTriggered) {
try {
mLoggingHandler.uncaughtException(t, e);
} catch (Throwable loggingThrowable) {
// Ignored.
}
}
}
}
總結(jié):
1)Zygote進(jìn)程fork app進(jìn)程時(shí)焚虱,調(diào)用RuntimeInit.zygoteInit,設(shè)置監(jiān)聽Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler())培愁。這是在啟動(dòng)進(jìn)程時(shí)著摔,進(jìn)程內(nèi)部創(chuàng)建的java層異常監(jiān)聽
2)ActivityManagerService.handleApplicationCrash就是展示對(duì)話框的業(yè)務(wù)入口函數(shù)。
3)異常TAG搜索:FATAL EXCEPTION
4)這種方式無法監(jiān)聽jni異常
5)特別說明定续,
設(shè)置預(yù)處理異常業(yè)務(wù)谍咆,目的輸出異常日志。此接口setUncaughtExceptionPreHandler為hide
設(shè)置默認(rèn)異常處理業(yè)務(wù)私股,目的是kill應(yīng)用和定制ams控制業(yè)務(wù)
將兩者分開摹察,可以避免普通應(yīng)用把異常日志捕獲而系統(tǒng)無法留檔
2.Native層的異常彈框處理
com.android.server.SystemServer
private void startOtherServices() {
···
mActivityManagerService.startObservingNativeCrashes();
···
}
com.android.server.am.ActivityManagerService
public void startObservingNativeCrashes() {
final NativeCrashListener ncl = new NativeCrashListener(this);
ncl.start();
}
com.android.server.am.NativeCrashListener
final class NativeCrashListener extends Thread {
···
static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
···
public void run() {
final byte[] ackSignal = new byte[1];
···
try {
//創(chuàng)建socket服務(wù)端
FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
DEBUGGERD_SOCKET_PATH);
Os.bind(serverFd, sockAddr);
Os.listen(serverFd, 1);
Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
while (true) {
FileDescriptor peerFd = null;
try {
//等待socket客戶端連接
peerFd = Os.accept(serverFd, null /* peerAddress */);
if (peerFd != null) {
StructUcred credentials =
Os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
//socket客戶端的uid為0才能處理異常數(shù)據(jù)
if (credentials.uid == 0) {
//消化native異常信息
consumeNativeCrashData(peerFd);
}
}
} catch (Exception e) {
Slog.w(TAG, "Error handling connection", e);
} finally {
if (peerFd != null) {
try {
Os.write(peerFd, ackSignal, 0, 1);
} catch (Exception e) {
}
try {
Os.close(peerFd);
} catch (ErrnoException e) {
}
}
}
}
} catch (Exception e) {
Slog.e(TAG, "Unable to init native debug socket!", e);
}
}
void consumeNativeCrashData(FileDescriptor fd) {
final byte[] buf = new byte[4096];
final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
try {
StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
//從fd中讀取信息到buf
int headerBytes = readExactly(fd, buf, 0, 8);
if (headerBytes != 8) {
return;
}
//從buf中讀取pid和signal
int pid = unpackInt(buf, 0);
int signal = unpackInt(buf, 4);
// now the text of the dump
if (pid > 0) {
final ProcessRecord pr;
synchronized (mAm.mPidsSelfLocked) {
pr = mAm.mPidsSelfLocked.get(pid);
}
if (pr != null) {
//如果是persistent進(jìn)程,則不記錄
if (pr.isPersistent()) {
return;
}
//將數(shù)據(jù)讀取到os中
int bytes;
do {
// get some data
bytes = Os.read(fd, buf, 0, buf.length);
if (bytes > 0) {
if (buf[bytes-1] == 0) {
os.write(buf, 0, bytes-1); // exclude the EOD token
break;
}
// no EOD, so collect it and read more
os.write(buf, 0, bytes);
}
} while (bytes > 0);
synchronized (mAm) {
pr.setCrashing(true);
pr.forceCrashReport = true;
}
//把os數(shù)據(jù)轉(zhuǎn)化為String
final String reportString = new String(os.toByteArray(), "UTF-8");
//報(bào)告原因
(new NativeCrashReporter(pr, signal, reportString)).start();
} else {
Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
}
} else {
Slog.e(TAG, "Bogus pid!");
}
} catch (Exception e) {
Slog.e(TAG, "Exception dealing with report", e);
// ugh, fail.
}
}
class NativeCrashReporter extends Thread {
ProcessRecord mApp;
int mSignal;
String mCrashReport;
NativeCrashReporter(ProcessRecord app, int signal, String report) {
super("NativeCrashReport");
mApp = app;
mSignal = signal;
mCrashReport = report;
}
@Override
public void run() {
try {
CrashInfo ci = new CrashInfo();
ci.exceptionClassName = "Native crash";
ci.exceptionMessage = Os.strsignal(mSignal);
ci.throwFileName = "unknown";
ci.throwClassName = "unknown";
ci.throwMethodName = "unknown";
ci.stackTrace = mCrashReport;
//通知ams
mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);//1
} catch (Exception e) {
Slog.e(TAG, "Unable to report native crash", e);
}
}
}
}
總結(jié)
1)native監(jiān)聽實(shí)現(xiàn)是在線程中開啟了一個(gè)while循環(huán)
2)注意倡鲸,對(duì)于persistent進(jìn)程供嚎,不做crash report
3)ams設(shè)置的native監(jiān)聽,是作為socket服務(wù)端峭状,而客戶端來自debuggerd進(jìn)程
4)ams接收異常后克滴,調(diào)用handleApplicationCrashInner
3.Ams.handleApplicationCrashInner分析
不管是java層還是native層的crash,最終都會(huì)通知Ams.handleApplicationCrashInner
1)分析handleApplicationCrashInner
ActivityManagerService
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
//events log日志:EventLogTags.AM_CRASH --> am_crash
EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
crashInfo.throwFileName,
crashInfo.throwLineNumber);
···
//異常信息注入dropbox
addErrorToDropBox(
eventType, r, processName, null, null, null, null, null, null, crashInfo);
//app異常報(bào)告
mAppErrors.crashApplication(r, crashInfo);
}
a)分析crash異常時(shí)优床,也可以關(guān)注events日志tag:am_crash
b)執(zhí)行是否重啟app或者彈對(duì)話框進(jìn)行人為點(diǎn)擊確定
c)dropbox也會(huì)記錄異常信息劝赔,前綴名稱system_server/system_app/data_app。即/data/system/dropbox
private static String processClass(ProcessRecord process) {
if (process == null || process.pid == MY_PID) {//system_server進(jìn)程
return "system_server";
} else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {//集成在system/app或priv-app的為system_app
return "system_app";
} else {//其他安裝的為data_app
return "data_app";
}
}
2)mAppErrors.crashApplication(r, crashInfo);
a)針對(duì)persistent或apexmodule進(jìn)程胆敞,進(jìn)行營(yíng)救記錄
b)過濾不彈對(duì)話框業(yè)務(wù)條件
c)通知handler處理對(duì)話框業(yè)務(wù)
d)等待handler并處理相關(guān)結(jié)果
com.android.server.am.AppErrors
void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
int callingPid, int callingUid) {
···
//針對(duì)persistent或apexmodule進(jìn)程着帽,進(jìn)行營(yíng)救記錄
if (r != null) {
···
if (r.isPersistent() || isApexModule) {
RescueParty.noteAppCrash(mContext, r.uid);
}
mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode());
}
···
synchronized (mService) {
//過濾不彈對(duì)話框業(yè)務(wù)條件
//這里可以實(shí)現(xiàn)IActivityController接口,從而滿足無須彈框業(yè)務(wù)移层。ActivityTaskManagerService.setActivityController
···
//通知handler處理對(duì)話框業(yè)務(wù)
final Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
taskId = data.taskId;
msg.obj = data;
mService.mUiHandler.sendMessage(msg);
}
//等待handler并處理相關(guān)結(jié)果
int res = result.get();//阻塞
···
}
3)通知handler處理對(duì)話框業(yè)務(wù)
主要處理是否展示對(duì)話框業(yè)務(wù)
com.android.server.am.AppErrors
void handleShowAppErrorUi(Message msg) {
AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
//針對(duì)anr業(yè)務(wù)是否需要展示對(duì)話框仍翰,默認(rèn)不展示
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
AppErrorDialog dialogToShow = null;
···
synchronized (mService) {
···
final boolean showFirstCrashDevOption = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
0,
mService.mUserController.getCurrentUserId()) != 0;
//靜默crash白名單。來自framework-res.apk的config_appsNotReportingCrashes观话,多個(gè)用“,”隔開
//例如:com.android.settings,com.android.systemui
final boolean crashSilenced = mAppsNotReportingCrashes != null &&
mAppsNotReportingCrashes.contains(proc.info.packageName);
//默認(rèn)展示對(duì)話框予借,可設(shè)置Settings.Global.HIDE_ERROR_DIALOGS為1,來隱藏對(duì)話框
if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
&& !crashSilenced
&& (showFirstCrashDevOption || data.repeating)) {
proc.crashDialog = dialogToShow = new AppErrorDialog(mContext, mService, data);
···
} else {
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
if (res != null) {
res.set(AppErrorDialog.CANT_SHOW);
}
}
}
if (dialogToShow != null) {
Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
dialogToShow.show();
}
}
針對(duì)異常對(duì)話框展示問題
a)局部,加白向framework-res.apk的config_appsNotReportingCrashes中加白處理
b)全局蕾羊,設(shè)置Settings.Global.HIDE_ERROR_DIALOGS為1喧笔,Settings.Secure.ANR_SHOW_BACKGROUND為0
4)怎么實(shí)現(xiàn)等待handler處理?
com.android.server.am.AppErrorResult
final class AppErrorResult {
//set就是notifyAll機(jī)制
public void set(int res) {
synchronized (this) {
mHasResult = true;
mResult = res;
notifyAll();
}
}
//get就是wait機(jī)制
public int get() {
synchronized (this) {
while (!mHasResult) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mResult;
}
boolean mHasResult = false;
int mResult;
}
參考學(xué)習(xí)
https://juejin.cn/post/6844904006041468935