Android性能優(yōu)化之捕獲java crash示例解析
背景
crash一直是影響app穩(wěn)定性的大頭,同時在隨著項目逐漸迭代,復雜性越來越提高的同時,由于主觀或者客觀的的原因,都會造成意想不到的crash出現(xiàn)。同樣的,在android的歷史化過程中,就算是android系統(tǒng)本身,在迭代中也會存在著隱含的crash。我們常說的crash包括java層(虛擬機層)crash與native層crash,本期我們著重講一下java層的crash。
java層crash由來
雖然說我們在開發(fā)過程中會遇到各種各樣的crash,但是這個crash是如果產(chǎn)生的呢?我們來探討一下一個crash是如何誕生的!
我們很容易就知道,在java中main函數(shù)是程序的開始(其實還有前置步驟),我們開發(fā)中,雖然android系統(tǒng)把應用的主線程創(chuàng)建封裝在了自己的系統(tǒng)中,但是無論怎么封裝,一個java層的線程無論再怎么強大,背后肯定是綁定了一個操作系統(tǒng)級別的線程,才真正得與驅(qū)動,也就是說,我們平常說的java線程,它其實是被操作系統(tǒng)真正的Thread的一個使用體罷了,java層的多個thread,可能會只對應著native層的一個Thread(便于區(qū)分,這里thread統(tǒng)一只java層的線程,Thread指的是native層的Thread。其實native的Thread也不是真正的線程,只是操作系統(tǒng)提供的一個api罷了,但是我們這里先簡單這樣定義,假設了native的線程與操作系統(tǒng)線程為同一個東西)
每一個java層的thread調(diào)用start方法,就會來到native層Thread的世界
public synchronized void start() {
throw new IllegalThreadStateException();
group.add(this);
started = false;
try {
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
最終調(diào)用的是一個jni方法
private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
而nativeCreate最終在native層的實現(xiàn)是
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
jboolean daemon) {
// There are sections in the zygote that forbid thread creation.
Runtime* runtime = Runtime::Current();
if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
jclass internal_error = env->FindClass("java/lang/InternalError");
CHECK(internal_error != nullptr);
env->ThrowNew(internal_error, "Cannot create threads in zygote");
return;
}
// 這里就是真正的創(chuàng)建線程方法
Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}
CreateNativeThread 經(jīng)過了一系列的校驗動作,終于到了真正創(chuàng)建線程的地方了,最終在CreateNativeThread方法中,通過了pthread_create創(chuàng)建了一個真正的Thread
Thread::CreateNativeThread 方法中
...
pthread_create_result = pthread_create(&new_pthread,
&attr,
Thread::CreateCallback,
child_thread);
CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
if (pthread_create_result == 0) {
// pthread_create started the new thread. The child is now responsible for managing the
// JNIEnvExt we created.
// Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
// between the threads.
child_jni_env_ext.release(); // NOLINT pthreads API.
return;
}
...
到這里我們就能夠明白,一個java層的thread其實真正綁定的,是一個native層的Thread,有了這個知識,我們就可以回到我們的crash主題了,當發(fā)生異常的時候(即檢測到一些操作不符合虛擬機規(guī)定時),注意,這個時候還是在虛擬機的控制范圍之內(nèi),就可以直接調(diào)用
void Thread::ThrowNewException(const char* exception_class_descriptor,
const char* msg) {
// Callers should either clear or call ThrowNewWrappedException.
AssertNoPendingExceptionForNewException(msg);
ThrowNewWrappedException(exception_class_descriptor, msg);
}
進行對exception的拋出,我們目前所有的java層crash都是如此,因為對crash的識別還屬于本虛擬機所在的進程的范疇(native crash 虛擬機就沒辦法直接識別),比如我們常見的各種crash

然后就會調(diào)用到Thread::ThrowNewWrappedException 方法,在這個方法里面再次調(diào)用到Thread::SetException方法,成功的把當次引發(fā)異常的信息記錄下來
void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
CHECK(new_exception != nullptr);
// TODO: DCHECK(!IsExceptionPending());
tlsPtr_.exception = new_exception.Ptr();
}
此時,此時就會調(diào)用Thread的Destroy方法,這個時候,線程就會在里面判斷,本次的異常該怎么去處理
void Thread::Destroy() {
...
if (tlsPtr_.opeer != nullptr) {
ScopedObjectAccess soa(self);
// We may need to call user-supplied managed code, do this before final clean-up.
HandleUncaughtExceptions(soa);
RemoveFromThreadGroup(soa);
Runtime* runtime = Runtime::Current();
if (runtime != nullptr) {
runtime->GetRuntimeCallbacks()->ThreadDeath(self);
}
HandleUncaughtExceptions 這個方式就是處理的函數(shù),我們繼續(xù)看一下這個異常處理函數(shù)
void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
if (!IsExceptionPending()) {
return;
}
ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
ScopedThreadStateChange tsc(this, ThreadState::kNative);
// Get and clear the exception.
ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
tlsPtr_.jni_env->ExceptionClear();
// Call the Thread instance's dispatchUncaughtException(Throwable)
// 關鍵點就在此,回到java層
tlsPtr_.jni_env->CallVoidMethod(peer.get(),
WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
exception.get());
// If the dispatchUncaughtException threw, clear that exception too.
tlsPtr_.jni_env->ExceptionClear();
}
到這里,我們就接近尾聲了,可以看到我們的處理函數(shù)最終通過jni,再次回到了java層的世界,而這個連接的java層函數(shù)就是dispatchUncaughtException(java_lang_Thread_dispatchUncaughtException)
public final void dispatchUncaughtException(Throwable e) {
// BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler();
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
// Throwables thrown by the initial handler are ignored
}
}
// END Android-added: uncaughtExceptionPreHandler for use by platform.
getUncaughtExceptionHandler().uncaughtException(this, e);
}
到這里,我們就徹底了解到了一個java層異常的產(chǎn)生過程!
為什么java層異常會導致crash
從上面我們文章我們能夠看到,一個異常是怎么產(chǎn)生的,可能細心的讀者會了解到,筆者一直在用異常這個詞,而不是crash,因為異常發(fā)生了,crash是不一定產(chǎn)生的!我們可以看到dispatchUncaughtException方法最終會嘗試著調(diào)用UncaughtExceptionHandler去處理本次異常,好家伙!那么UncaughtExceptionHandler是在什么時候設置的?其實就是在Init中,由系統(tǒng)提前設置好的!frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
/*
* Install a time zone supplier that uses the Android persistent time zone system property.
*/
RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
LogManager.getLogManager().reset();
new AndroidConfig();
/*
* Sets the default HTTP User-Agent used by HttpURLConnection.
*/
String userAgent = getDefaultUserAgent();
System.setProperty("http.agent", userAgent);
/*
* Wire socket tagging to traffic stats.
*/
TrafficStats.attachSocketTagger();
initialized = true;
}
好家伙,原來是KillApplicationHandler“搗蛋”,在異常到來時,就會通過KillApplicationHandler去處理,而這里的處理就是,殺死app??!
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 {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// Bring up crash dialog, wait for it to be dismissed
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 {
// Try everything to make sure this process goes away.
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.
}
}
}
}
看到了嗎!異常的產(chǎn)生導致的crash,真正的源頭就是在此了!
捕獲crash
通過對前文的閱讀,我們了解到了crash的源頭就是KillApplicationHandler,因為它默認處理就是殺死app,此時我們也注意到,它是繼承于UncaughtExceptionHandler的。當然,有異常及時拋出解決,是一件好事,但是我們也可能有一些異常,比如android系統(tǒng)sdk的問題,或者其他沒那么重要的異常,直接崩潰app,這個處理就不是那么好了。但是不要緊,java虛擬機開發(fā)者也肯定注意到了這點,所以提供
Thread.java public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
方式,導入一個我們自定義的實現(xiàn)了UncaughtExceptionHandler接口的類
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
此時我們只需要寫一個類,模仿KillApplicationHandler一樣,就能寫出一個自己的異常處理類,去處理我們程序中的異常(或者Android系統(tǒng)中特定版本的異常)。例子demo比如
class MyExceptionHandler:Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
// 做自己的邏輯
Log.i("hello",e.toString())
}
}
總結
到這里,我們能夠了解到了一個java crash是怎么產(chǎn)生的了,同時我們也了解到了常用的UncaughtExceptionHandler為什么可以攔截一些我們不希望產(chǎn)生crash的異常,在接下來的android性能優(yōu)化系列中,會持續(xù)帶來相關的其他分享,感謝觀看
更多關于Android捕獲java crash的資料請關注腳本之家其它相關文章!
相關文章
超簡單實現(xiàn)Android自定義Toast示例(附源碼)
本篇文章主要介紹了超簡單實現(xiàn)Android自定義Toast示例(附源碼),具有一定的參考價值,有興趣的可以了解一下。2017-02-02
Android編程下拉菜單spinner用法小結(附2則示例)
這篇文章主要介紹了Android編程下拉菜單spinner用法,結合實例較為詳細的總結分析了下拉菜單Spinner的具體實現(xiàn)步驟與相關技巧,并附帶兩個示例分析其具體用法,需要的朋友可以參考下2015-12-12
Android ListView實現(xiàn)ImageLoader圖片加載的方法
這篇文章主要介紹了Android ListView實現(xiàn)ImageLoader圖片加載的方法,結合實例形式簡單分析了開源框架Imageloader的功能、使用方法與相關注意事項,需要的朋友可以參考下2017-07-07
unity5.6 導出gradle工程 Android Studio 導入問題及處理方法
這篇文章主要介紹了unity5.6 導出gradle工程 Android Studio 導入問題及處理方法,需要的朋友可以參考下2017-12-12

