崩潰的基本原因
拋出異常導(dǎo)致崩潰分析
在日常開發(fā)中崩潰是我們遇到的很常見的情況,可能是 NullPointerException、IllegalArgumentException 等等,當(dāng)應(yīng)用程序拋出這些我們未捕獲的異常時(shí),緊跟著的是應(yīng)用的崩潰,進(jìn)程被殺死并退出。
或許你到現(xiàn)在都一直認(rèn)為是因?yàn)閽伋隽水惓?,所以才會?dǎo)致的進(jìn)程被殺死并退出,認(rèn)為拋出未捕獲的異常就是導(dǎo)致進(jìn)程退出的根本原因。但事實(shí)真的如此嗎?
主線程也是一個線程,所以我們從 Thread 源碼找找是否有什么蹤跡可以解釋:
Thread.java
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
// 如果沒有設(shè)置 uncaughtExceptionHandler,由 ThreadGroup group 線程組處理
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
ThreadGroup.java
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
// 如果有設(shè)置了 UncaughtExceptionHandler 處理異常捕獲
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
上面的源碼是 Thread 處理未捕獲的異常時(shí)會執(zhí)行的代碼,其中 dispatchUncaughtException() 是當(dāng)異常未被捕獲時(shí),它會由 JVM 發(fā)起調(diào)用。
當(dāng)程序代碼運(yùn)行錯誤拋出異常時(shí),如果沒有設(shè)置 Thread.UncaughtExceptionHandler,從 Thread 中并沒有看到有關(guān)進(jìn)程退出的代碼,那為什么我們的程序拋出異常會導(dǎo)致進(jìn)程退出呢?
在 Android 創(chuàng)建 app 進(jìn)程是由 zygote fork 進(jìn)程,而在 zygote 進(jìn)程創(chuàng)建之前則是由 init 進(jìn)程啟動的,每個應(yīng)用程序的入口都是 main() 函數(shù):
RuntimeInit.java
public static void main(String[] argv) {
...
commonInit();
...
}
protected static final void commonInit() {
...
// 在所在線程(即主線程)調(diào)用了 Thread.setDefaultUncaughtExceptionHandler
// 提供 KillApplicationHandler 在應(yīng)用拋出異常時(shí)退出 app 進(jìn)程
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
...
}
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
...
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
...
} finally {
// 捕獲到應(yīng)用程序拋出的異常,最后主動殺死退出進(jìn)程
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
可以發(fā)現(xiàn)應(yīng)用程序拋出異常進(jìn)程會退出,實(shí)際上是 RuntimeInit 在主線程設(shè)置了 KillApplicationHandler,捕獲到異常時(shí)主動將應(yīng)用進(jìn)程殺死退出。
所以在這里我們可以得出結(jié)論:拋出未捕獲的異常并不是導(dǎo)致進(jìn)程殺死退出的根本原因而逝導(dǎo)火索,Android 默認(rèn)提供了異常未捕獲時(shí)主動將應(yīng)用進(jìn)程殺死退出的邏輯。
簡單總結(jié)下 java crash 未捕獲異常的處理流程:
-
Thread 中遇到未捕獲異常時(shí)會由 JVM 調(diào)用 dispatchUncaughtException()
-
dispatchUncaughtException() 內(nèi)部是運(yùn)用 Thread.UncaughtExceptionHandler 進(jìn)行處理
-
Android 默認(rèn)提供一個 KillApplicationHandler 繼承 Thread.UncaughtExceptionHandler,它提供進(jìn)程退出功能
即我們程序中的代碼拋出未捕獲的異常時(shí),異常會一路往上拋直到由 JVM 處理,JVM 就會調(diào)用 Thread 的 dispatchUncaughtException(),接著就是交給了 Android 在創(chuàng)建應(yīng)用時(shí)設(shè)置的 KillApplicationHandler 將進(jìn)程退出。
AMS 如何承接應(yīng)用的異常信息上報(bào)
在上面分析 RuntimeInit 設(shè)置了 KillApplicationHandler 時(shí),在殺死進(jìn)程前將應(yīng)用崩潰的信息交給了 AMS:
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
...
// AMS 處理進(jìn)程退出前的信息處理
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
...
} finally {
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
那 AMS 在進(jìn)程退出前具體做了哪些事情,接著看源碼:
ActivityManagerService.java
public void handleApplicationCrash(IBinder app,
ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
// 拿到進(jìn)程信息
ProcessRecord r = findAppProcess(app, "Crash");
final String processName = app == null ? "system_server"
: (r == null ? "unknown" : r.processName);
handleApplicationCrashInner("crash", r, processName, crashInfo);
}
// eventType 根據(jù)不同類型記錄 log
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
...
// java crash、native crash、anr 都會走這里去記錄,通過 eventType 區(qū)分記錄 log
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
...
}
可以發(fā)現(xiàn) AMS 是拿到了應(yīng)用進(jìn)程信息后,最終調(diào)用了 addErrorToDropBox() 將錯誤信息提供給 DropBox。
Android DropBox 是 Android 8 引入的用來持續(xù)化存儲系統(tǒng)數(shù)據(jù)的機(jī)制。主要用于記錄 Android 運(yùn)行過程中,內(nèi)核、系統(tǒng)進(jìn)程、用戶進(jìn)程等出現(xiàn)嚴(yán)重問題時(shí)的 log,可以認(rèn)為這是一個可持續(xù)存儲的系統(tǒng)級別的 logcat。存儲的 log 信息存放在 /data/system/dropbox/(該目錄沒有 root 無法訪問),crash 和 anr 的日志都會存儲在這里。
其中 eventType 參數(shù)表示的是記錄的 log 信息類型,它有三種類型:
-
未捕捉的 java 異常:crash
-
ANR 異常:anr
-
native 異常:native crash
對于 native crash 系統(tǒng)如何做處理
上面講到了 app 是如何捕捉 java crash,那 native crash 又是如何捕捉的?
ActivityManagerService.java
public void startObservingNativeCrashes() {
// NativeCrashListener 繼承自 Thread
final NativeCrashListener ncl = new NativeCrashListener(this);
ncl.start();
}
而 AMS 的 startObservingNativeCrashes() 是在 SystemServer 創(chuàng)建啟動的時(shí)候調(diào)用:
SystemServer.java
private void startOtherServices() {
...
mActivityManagerService.startObservingNativeCrashes();
...
}
startOtherService() 是在一些核心服務(wù)啟動后才會緊接著調(diào)用的方法用于開啟其他服務(wù),native crash 的監(jiān)控就是在此時(shí)啟動。
接下來看下 native crash 是怎么監(jiān)聽的。
NativeCrashListener.java
// 通信地址,C 層是將數(shù)據(jù)推到這里,java 層也是從這個地址獲取到數(shù)據(jù)
static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
@Override
public void run() {
...
// 這里走的是信號機(jī)制通信,當(dāng)前接收建立管道綁定
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 {
...
// 掛載等待
peerFd = Os.accept(serverFd, null /* peerAddress */);
...
if (peerFd != null) {
// 處理 native crash 數(shù)據(jù)
consumeNativeCrashData(peerFd);
}
...
}
}
...
}
void consumeNativeCrashData(FileDescriptor fd) {
// 根據(jù) fd 拿到數(shù)據(jù)寫到 ByteArrayOutputStream
...
final String reportString = new String(os.toByteArray(), "UTF-8");
// 啟動一個子線程將數(shù)據(jù)結(jié)果給到 AMS 調(diào)用 addErrorToDropBox()
(new NativeCrashReport(pr, signal, reportString)).start();
}
class NativeCrashReport extends Thread {
ProcessRecord mApp;
int mSignal;
String mCrashReport;
NativeCrashReport(ProcessRecord app, int signal, String report) {
super("NativeCrashReport");
mApp = app;
mSignal = signal;
mCrashReport = report;
}
@Override
public void run() {
try {
// 封裝數(shù)據(jù),將數(shù)據(jù)交給 AMS
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;
if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
// eventType = native_crash,還是調(diào)用的 AMS.addErrorToDropBox()
mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
} catch (Exception e) {
Slog.e(TAG, "Unable to report native crash", e);
}
...
}
}
通過源碼可以知道,native crash 監(jiān)聽是通過信號量的方式監(jiān)聽一個 fd 文件描述符并掛載等待,當(dāng) fd 返回?cái)?shù)據(jù)時(shí)說明出現(xiàn) native crash,此時(shí)就可以通過 fd 拿到 native crash 的字節(jié)數(shù)據(jù)進(jìn)行封裝,最終還是將數(shù)據(jù)結(jié)果給到 AMS 調(diào)用 addErrorToDropBox(),只是 eventType 傳的是 native_crash。
簡單總結(jié)下 native crash 異常的處理流程:
-
通信機(jī)制建立,監(jiān)聽一個 fd 文件描述符
-
接收數(shù)據(jù),掛載等待
-
fd 文件描述符有數(shù)據(jù)返回,處理數(shù)據(jù)
-
封裝數(shù)據(jù),將結(jié)果給 AMS 調(diào)用 addErrorToDropBox() 寫入存儲信息
系統(tǒng)如何處理 ANR 異常數(shù)據(jù)
因?yàn)檫@里我們只說 ANR 異常最終是如何處理的,所以關(guān)于 ANR 異常發(fā)生的原因和具體流程可以查看 ANR 觸發(fā)原理和分析,這里不再贅述,我們直接定位到 ANR 已經(jīng)發(fā)生的位置:
AppErrors.java
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
// ANR 數(shù)據(jù)處理封裝
...
// 這里就是我們常用的 anr 日志存放在 /data/anr 的文件向外輸出了一份
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
...
// 調(diào)用 AMS 的 addErrorToDropBox()
mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
cpuInfo, tracesFile, null);
...
}
ANR 異常的信息是調(diào)用的 AppErrors 的 appNotResponding(),也是將數(shù)據(jù)處理封裝后給到 AMS 調(diào)用 addErrorToDropBox(),eventType 傳入的是 anr。
簡單總結(jié)下 anr 異常的處理流程:
-
記錄 anr 對應(yīng)數(shù)據(jù)到 SLOG(framework 日志體系中)
-
記錄 anr 數(shù)據(jù)到 LOG(主日志體系中)
-
dump 具體數(shù)據(jù)到指定文件中
-
調(diào)用 AMS 的 addErrorToDropBox() 寫入存儲信息
addErrorToDropBox()
經(jīng)過上面的分析我們知道,無論是 java crash、native crash 還是 anr,最終都會交由 AMS 的 addErrorToDropBox() 處理。那它又是怎么處理的呢?
ActivityManagerService.java
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, ActivityRecord activity,
ActivityRecord parent, String subject,
final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo) {
...
final StringBuilder sb = new StringBuilder(1024);
// 添加頭信息
appendDropBoxProcessHeaders(process, processName, sb);
// 添加一些頭部 log 信息
if (process != null) {
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
.append("\n");
}
if (activity != null) {
sb.append("Activity: ").append(activity.shortComponentName).append("\n");
}
if (parent != null && parent.app != null && parent.app.pid != process.pid) {
sb.append("Parent-Process: ").append(parent.app.processName).append("\n");
}
if (parent != null && parent != activity) {
sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n");
}
if (subject != null) {
sb.append("Subject: ").append(subject).append("\n");
}
sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
if (Debug.isDebuggerConnected()) {
sb.append("Debugger: Connected\n");
}
sb.append("\n");
...
// 讀取對應(yīng)異常數(shù)據(jù)拼裝成字符數(shù)據(jù)
if (lines > 0) {
sb.append("\n");
// Merge several logcat streams, and take the last N lines
InputStreamReader input = null;
try {
java.lang.Process logcat = new ProcessBuilder(
"/system/bin/timeout", "-k", "15s", "10s",
"/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
"-b", "main", "-b", "crash", "-t", String.valueOf(lines))
.redirectErrorStream(true).start();
try { logcat.getOutputStream().close(); } catch (IOException e) {}
try { logcat.getErrorStream().close(); } catch (IOException e) {}
input = new InputStreamReader(logcat.getInputStream());
int num;
char[] buf = new char[8192];
while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
} catch (IOException e) {
Slog.e(TAG, "Error running logcat", e);
} finally {
if (input != null) try { input.close(); } catch (IOException e) {}
}
}
// 交給 DropBoxManager 錄入
dbox.addText(dropboxTag, sb.toString());
...
}
DropBoxManager.java
public void addText(String tag, String data) {
try {
// mService 就是 DropBoxManagerService
mService.add(new Entry(tag, 0, data));
} catch (RemoteException e) {
if (e instanceof TransactionTooLargeException
&& mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data, so it was ignored", e);
return;
}
throw e.rethrowFromSystemServer();
}
}
當(dāng) AMS 調(diào)用 addErrorToDropBox() 時(shí),它會準(zhǔn)備添加到 DropBoxManager 所需要的頭信息、異常信息的字符數(shù)據(jù),最終交由 DropBoxManager 錄入信息。
DropBoxManager 在 Crash 方案中扮演的角色
DropBox 是 Android 在 API 8 引入的用來持續(xù)化存儲系統(tǒng)數(shù)據(jù)的機(jī)制,主要用于記錄 Android 運(yùn)行過程中,內(nèi)核、系統(tǒng)進(jìn)程、用戶進(jìn)程等出現(xiàn)嚴(yán)重問題時(shí)的 log,可以認(rèn)為 DropBox 就是一個可持續(xù)存儲的系統(tǒng)級別的 logcat。
DropBoxManagerService.java
public DropBoxManagerService(final Context context) {
// DropBox 信息的存儲日志目錄是 /data/system/dropbox
this(context, new File("/data/system/dropbox"), FgThread.get().getLooper());
}
public DropBoxManagerService(final Context context, File path, Looper looper) {
super(context);
mDropBoxDir = path;
...
}
// 最終其實(shí)就是 IO 寫入
public void add(DropBoxManager.Entry entry) {
File temp = null;
InputStream input = null;
OutputStream output = null;
final String tag = entry.getTag();
try {
int flags = entry.getFlags();
if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
init();
if (!isTagEnabled(tag)) return;
long max = trimToFit();
long lastTrim = System.currentTimeMillis();
byte[] buffer = new byte[mBlockSize];
input = entry.getInputStream();
// First, accumulate up to one block worth of data in memory before
// deciding whether to compress the data or not.
int read = 0;
while (read < buffer.length) {
int n = input.read(buffer, read, buffer.length - read);
if (n <= 0) break;
read += n;
}
// If we have at least one block, compress it -- otherwise, just write
// the data in uncompressed form.
// mDropBoxDir 就是 /data/system/dropbox 目錄
temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
int bufferSize = mBlockSize;
if (bufferSize > 4096) bufferSize = 4096;
if (bufferSize < 512) bufferSize = 512;
FileOutputStream foutput = new FileOutputStream(temp);
output = new BufferedOutputStream(foutput, bufferSize);
if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
output = new GZIPOutputStream(output);
flags = flags | DropBoxManager.IS_GZIPPED;
}
do {
output.write(buffer, 0, read);
long now = System.currentTimeMillis();
if (now - lastTrim > 30 * 1000) {
max = trimToFit(); // In case data dribbles in slowly
lastTrim = now;
}
read = input.read(buffer);
if (read <= 0) {
FileUtils.sync(foutput);
output.close(); // Get a final size measurement
output = null;
} else {
output.flush(); // So the size measurement is pseudo-reasonable
}
long len = temp.length();
if (len > max) {
Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");
temp.delete();
temp = null; // Pass temp = null to createEntry() to leave a tombstone
break;
}
} while (read > 0);
long time = createEntry(temp, tag, flags);
temp = null;
final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
if (!mBooted) {
dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
// Call sendBroadcast after returning from this call to avoid deadlock. In particular
// the caller may be holding the WindowManagerService lock but sendBroadcast requires a
// lock in ActivityManagerService. ActivityManagerService has been caught holding that
// very lock while waiting for the WindowManagerService lock.
mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
} catch (IOException e) {
Slog.e(TAG, "Can't write: " + tag, e);
} finally {
IoUtils.closeQuietly(output);
IoUtils.closeQuietly(input);
entry.close();
if (temp != null) temp.delete();
}
}
當(dāng)發(fā)生異常信息時(shí),最終的異常信息是交給 DropBoxManager(具體說是 DropBoxManagerService)通過 IO 將信息寫入到指定目錄文件。
總結(jié)
上面主要分析了當(dāng)發(fā)生 java crash、native crash 和 anr 時(shí),Android 為我們做了哪些事情,在這里在簡單總結(jié)一下:
-
java crash 由 JVM 觸發(fā)處理,最終走到 /data/system/dropbox 目錄用文件保存
-
native crash 由管道通信建立 socket 接收通知,最終走到 /data/system/dropbox 目錄用文件保存
-
anr 由多種情況(事件、前后臺服務(wù))觸發(fā)器處理,最終走到 /data/system/dropbox 目錄用文件保存
所有的 crash 處理在 Android 系統(tǒng)內(nèi)部都會將對應(yīng)的數(shù)據(jù)收集到 /data/system/dropbox 目錄下。
同時(shí)我們也梳理了 Android 的 crash 處理機(jī)制:文章來源:http://www.zghlxwxcb.cn/news/detail-505204.html
- Java 沒有捕獲異常時(shí)會由 JVM 調(diào)用 dispatchUncaughtException 調(diào)用一個 UncaughtExceptionHandler 處理,默認(rèn) RuntimeInit 提供了一個 KillApplicationHandler 會直接退出進(jìn)程。
如果我們要自己處理異常,可以自定義一個 UncaughtExceptionHandler 攔截處理。文章來源地址http://www.zghlxwxcb.cn/news/detail-505204.html
到了這里,關(guān)于Android 性能優(yōu)化系列:崩潰原因及捕獲的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!