Android性能優(yōu)化之捕獲java crash示例解析
背景
crash一直是影響app穩(wěn)定性的大頭,同時(shí)在隨著項(xiàng)目逐漸迭代,復(fù)雜性越來越提高的同時(shí),由于主觀或者客觀的的原因,都會造成意想不到的crash出現(xiàn)。同樣的,在android的歷史化過程中,就算是android系統(tǒng)本身,在迭代中也會存在著隱含的crash。我們常說的crash包括java層(虛擬機(jī)層)crash與native層crash,本期我們著重講一下java層的crash。
java層crash由來
雖然說我們在開發(fā)過程中會遇到各種各樣的crash,但是這個(gè)crash是如果產(chǎn)生的呢?我們來探討一下一個(gè)crash是如何誕生的!
我們很容易就知道,在java中main函數(shù)是程序的開始(其實(shí)還有前置步驟),我們開發(fā)中,雖然android系統(tǒng)把應(yīng)用的主線程創(chuàng)建封裝在了自己的系統(tǒng)中,但是無論怎么封裝,一個(gè)java層的線程無論再怎么強(qiáng)大,背后肯定是綁定了一個(gè)操作系統(tǒng)級別的線程,才真正得與驅(qū)動(dòng),也就是說,我們平常說的java線程,它其實(shí)是被操作系統(tǒng)真正的Thread的一個(gè)使用體罷了,java層的多個(gè)thread,可能會只對應(yīng)著native層的一個(gè)Thread(便于區(qū)分,這里thread統(tǒng)一只java層的線程,Thread指的是native層的Thread。其實(shí)native的Thread也不是真正的線程,只是操作系統(tǒng)提供的一個(gè)api罷了,但是我們這里先簡單這樣定義,假設(shè)了native的線程與操作系統(tǒng)線程為同一個(gè)東西)
每一個(gè)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)用的是一個(gè)jni方法
private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
而nativeCreate最終在native層的實(shí)現(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)過了一系列的校驗(yàn)動(dòng)作,終于到了真正創(chuàng)建線程的地方了,最終在CreateNativeThread方法中,通過了pthread_create創(chuàng)建了一個(gè)真正的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; } ...
到這里我們就能夠明白,一個(gè)java層的thread其實(shí)真正綁定的,是一個(gè)native層的Thread,有了這個(gè)知識,我們就可以回到我們的crash主題了,當(dāng)發(fā)生異常的時(shí)候(即檢測到一些操作不符合虛擬機(jī)規(guī)定時(shí)),注意,這個(gè)時(shí)候還是在虛擬機(jī)的控制范圍之內(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); }
進(jìn)行對exception的拋出,我們目前所有的java層crash都是如此,因?yàn)閷rash的識別還屬于本虛擬機(jī)所在的進(jìn)程的范疇(native crash 虛擬機(jī)就沒辦法直接識別),比如我們常見的各種crash
然后就會調(diào)用到Thread::ThrowNewWrappedException 方法,在這個(gè)方法里面再次調(diào)用到Thread::SetException方法,成功的把當(dāng)次引發(fā)異常的信息記錄下來
void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) { CHECK(new_exception != nullptr); // TODO: DCHECK(!IsExceptionPending()); tlsPtr_.exception = new_exception.Ptr(); }
此時(shí),此時(shí)就會調(diào)用Thread的Destroy方法,這個(gè)時(shí)候,線程就會在里面判斷,本次的異常該怎么去處理
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 這個(gè)方式就是處理的函數(shù),我們繼續(xù)看一下這個(gè)異常處理函數(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) // 關(guān)鍵點(diǎn)就在此,回到j(luò)ava層 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層的世界,而這個(gè)連接的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); }
到這里,我們就徹底了解到了一個(gè)java層異常的產(chǎn)生過程!
為什么java層異常會導(dǎo)致crash
從上面我們文章我們能夠看到,一個(gè)異常是怎么產(chǎn)生的,可能細(xì)心的讀者會了解到,筆者一直在用異常這個(gè)詞,而不是crash,因?yàn)楫惓0l(fā)生了,crash是不一定產(chǎn)生的!我們可以看到dispatchUncaughtException方法最終會嘗試著調(diào)用UncaughtExceptionHandler去處理本次異常,好家伙!那么UncaughtExceptionHandler是在什么時(shí)候設(shè)置的?其實(shí)就是在Init中,由系統(tǒng)提前設(shè)置好的!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“搗蛋”,在異常到來時(shí),就會通過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)生導(dǎo)致的crash,真正的源頭就是在此了!
捕獲crash
通過對前文的閱讀,我們了解到了crash的源頭就是KillApplicationHandler,因?yàn)樗J(rèn)處理就是殺死app,此時(shí)我們也注意到,它是繼承于UncaughtExceptionHandler的。當(dāng)然,有異常及時(shí)拋出解決,是一件好事,但是我們也可能有一些異常,比如android系統(tǒng)sdk的問題,或者其他沒那么重要的異常,直接崩潰app,這個(gè)處理就不是那么好了。但是不要緊,java虛擬機(jī)開發(fā)者也肯定注意到了這點(diǎn),所以提供
Thread.java public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
方式,導(dǎo)入一個(gè)我們自定義的實(shí)現(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); }
此時(shí)我們只需要寫一個(gè)類,模仿KillApplicationHandler一樣,就能寫出一個(gè)自己的異常處理類,去處理我們程序中的異常(或者Android系統(tǒng)中特定版本的異常)。例子demo比如
class MyExceptionHandler:Thread.UncaughtExceptionHandler { override fun uncaughtException(t: Thread, e: Throwable) { // 做自己的邏輯 Log.i("hello",e.toString()) } }
總結(jié)
到這里,我們能夠了解到了一個(gè)java crash是怎么產(chǎn)生的了,同時(shí)我們也了解到了常用的UncaughtExceptionHandler為什么可以攔截一些我們不希望產(chǎn)生crash的異常,在接下來的android性能優(yōu)化系列中,會持續(xù)帶來相關(guān)的其他分享,感謝觀看
更多關(guān)于Android捕獲java crash的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中傳遞對象的三種方法的實(shí)現(xiàn)
本篇文章主要介紹了Android中傳遞對象的三種方法的實(shí)現(xiàn),可以通過Bundle、Intent或者JSON字符串,有興趣的可以了解一下。2017-02-02超簡單實(shí)現(xiàn)Android自定義Toast示例(附源碼)
本篇文章主要介紹了超簡單實(shí)現(xiàn)Android自定義Toast示例(附源碼),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-02-02Android編程下拉菜單spinner用法小結(jié)(附2則示例)
這篇文章主要介紹了Android編程下拉菜單spinner用法,結(jié)合實(shí)例較為詳細(xì)的總結(jié)分析了下拉菜單Spinner的具體實(shí)現(xiàn)步驟與相關(guān)技巧,并附帶兩個(gè)示例分析其具體用法,需要的朋友可以參考下2015-12-12Android ListView實(shí)現(xiàn)ImageLoader圖片加載的方法
這篇文章主要介紹了Android ListView實(shí)現(xiàn)ImageLoader圖片加載的方法,結(jié)合實(shí)例形式簡單分析了開源框架Imageloader的功能、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-07-07unity5.6 導(dǎo)出gradle工程 Android Studio 導(dǎo)入問題及處理方法
這篇文章主要介紹了unity5.6 導(dǎo)出gradle工程 Android Studio 導(dǎo)入問題及處理方法,需要的朋友可以參考下2017-12-12Android開發(fā)注解排列組合出啟動(dòng)任務(wù)ksp
這篇文章主要為大家介紹了Android開發(fā)注解排列組合出啟動(dòng)任務(wù)ksp示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android實(shí)現(xiàn)商城購物車功能的實(shí)例代碼
最近公司項(xiàng)目做商城模塊,需要實(shí)現(xiàn)購物車功能,主要實(shí)現(xiàn)了單選、全選,金額合計(jì),商品刪除,商品數(shù)量加減等功能,這篇文章主要介紹了Android實(shí)現(xiàn)商城購物車功能,需要的朋友可以參考下2019-06-06