Android性能優(yōu)化之捕獲java crash示例解析
背景
crash一直是影響app穩(wěn)定性的大頭,同時(shí)在隨著項(xiàng)目逐漸迭代,復(fù)雜性越來(lái)越提高的同時(shí),由于主觀或者客觀的的原因,都會(huì)造成意想不到的crash出現(xiàn)。同樣的,在android的歷史化過(guò)程中,就算是android系統(tǒng)本身,在迭代中也會(huì)存在著隱含的crash。我們常說(shuō)的crash包括java層(虛擬機(jī)層)crash與native層crash,本期我們著重講一下java層的crash。
java層crash由來(lái)
雖然說(shuō)我們?cè)陂_(kāi)發(fā)過(guò)程中會(huì)遇到各種各樣的crash,但是這個(gè)crash是如果產(chǎn)生的呢?我們來(lái)探討一下一個(gè)crash是如何誕生的!
我們很容易就知道,在java中main函數(shù)是程序的開(kāi)始(其實(shí)還有前置步驟),我們開(kāi)發(fā)中,雖然android系統(tǒng)把應(yīng)用的主線程創(chuàng)建封裝在了自己的系統(tǒng)中,但是無(wú)論怎么封裝,一個(gè)java層的線程無(wú)論再怎么強(qiáng)大,背后肯定是綁定了一個(gè)操作系統(tǒng)級(jí)別的線程,才真正得與驅(qū)動(dòng),也就是說(shuō),我們平常說(shuō)的java線程,它其實(shí)是被操作系統(tǒng)真正的Thread的一個(gè)使用體罷了,java層的多個(gè)thread,可能會(huì)只對(duì)應(yīng)著native層的一個(gè)Thread(便于區(qū)分,這里thread統(tǒng)一只java層的線程,Thread指的是native層的Thread。其實(shí)native的Thread也不是真正的線程,只是操作系統(tǒng)提供的一個(gè)api罷了,但是我們這里先簡(jiǎn)單這樣定義,假設(shè)了native的線程與操作系統(tǒng)線程為同一個(gè)東西)
每一個(gè)java層的thread調(diào)用start方法,就會(huì)來(lái)到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)過(guò)了一系列的校驗(yàn)動(dòng)作,終于到了真正創(chuàng)建線程的地方了,最終在CreateNativeThread方法中,通過(guò)了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è)知識(shí),我們就可以回到我們的crash主題了,當(dāng)發(fā)生異常的時(shí)候(即檢測(cè)到一些操作不符合虛擬機(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)行對(duì)exception的拋出,我們目前所有的java層crash都是如此,因?yàn)閷?duì)crash的識(shí)別還屬于本虛擬機(jī)所在的進(jìn)程的范疇(native crash 虛擬機(jī)就沒(méi)辦法直接識(shí)別),比如我們常見(jiàn)的各種crash

然后就會(huì)調(diào)用到Thread::ThrowNewWrappedException 方法,在這個(gè)方法里面再次調(diào)用到Thread::SetException方法,成功的把當(dāng)次引發(fā)異常的信息記錄下來(lái)
void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
CHECK(new_exception != nullptr);
// TODO: DCHECK(!IsExceptionPending());
tlsPtr_.exception = new_exception.Ptr();
}
此時(shí),此時(shí)就會(huì)調(diào)用Thread的Destroy方法,這個(gè)時(shí)候,線程就會(huì)在里面判斷,本次的異常該怎么去處理
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ù)最終通過(guò)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)生過(guò)程!
為什么java層異常會(huì)導(dǎo)致crash
從上面我們文章我們能夠看到,一個(gè)異常是怎么產(chǎn)生的,可能細(xì)心的讀者會(huì)了解到,筆者一直在用異常這個(gè)詞,而不是crash,因?yàn)楫惓0l(fā)生了,crash是不一定產(chǎn)生的!我們可以看到dispatchUncaughtException方法最終會(huì)嘗試著調(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;
}
好家伙,原來(lái)是KillApplicationHandler“搗蛋”,在異常到來(lái)時(shí),就會(huì)通過(guò)KillApplicationHandler去處理,而這里的處理就是,殺死app?。?/p>
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
通過(guò)對(duì)前文的閱讀,我們了解到了crash的源頭就是KillApplicationHandler,因?yàn)樗J(rèn)處理就是殺死app,此時(shí)我們也注意到,它是繼承于UncaughtExceptionHandler的。當(dāng)然,有異常及時(shí)拋出解決,是一件好事,但是我們也可能有一些異常,比如android系統(tǒng)sdk的問(wèn)題,或者其他沒(méi)那么重要的異常,直接崩潰app,這個(gè)處理就不是那么好了。但是不要緊,java虛擬機(jī)開(kāi)發(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的異常,在接下來(lái)的android性能優(yōu)化系列中,會(huì)持續(xù)帶來(lái)相關(guān)的其他分享,感謝觀看
更多關(guān)于Android捕獲java crash的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中傳遞對(duì)象的三種方法的實(shí)現(xiàn)
本篇文章主要介紹了Android中傳遞對(duì)象的三種方法的實(shí)現(xiàn),可以通過(guò)Bundle、Intent或者JSON字符串,有興趣的可以了解一下。2017-02-02
超簡(jiǎn)單實(shí)現(xiàn)Android自定義Toast示例(附源碼)
本篇文章主要介紹了超簡(jiǎn)單實(shí)現(xiàn)Android自定義Toast示例(附源碼),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-02-02
Android編程下拉菜單spinner用法小結(jié)(附2則示例)
這篇文章主要介紹了Android編程下拉菜單spinner用法,結(jié)合實(shí)例較為詳細(xì)的總結(jié)分析了下拉菜單Spinner的具體實(shí)現(xiàn)步驟與相關(guān)技巧,并附帶兩個(gè)示例分析其具體用法,需要的朋友可以參考下2015-12-12
Android ListView實(shí)現(xiàn)ImageLoader圖片加載的方法
這篇文章主要介紹了Android ListView實(shí)現(xiàn)ImageLoader圖片加載的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了開(kāi)源框架Imageloader的功能、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-07-07
unity5.6 導(dǎo)出gradle工程 Android Studio 導(dǎo)入問(wèn)題及處理方法
這篇文章主要介紹了unity5.6 導(dǎo)出gradle工程 Android Studio 導(dǎo)入問(wèn)題及處理方法,需要的朋友可以參考下2017-12-12
Android 自定義AlertDialog對(duì)話框樣式
實(shí)際的項(xiàng)目開(kāi)發(fā)當(dāng)中,經(jīng)常需要根據(jù)實(shí)際的需求來(lái)自定義AlertDialog。最近在開(kāi)發(fā)一個(gè)WIFI連接的功能,點(diǎn)擊WIFI需要彈出自定義密碼輸入框,具體代碼大家參考下本文2017-09-09
Android開(kāi)發(fā)注解排列組合出啟動(dòng)任務(wù)ksp
這篇文章主要為大家介紹了Android開(kāi)發(fā)注解排列組合出啟動(dòng)任務(wù)ksp示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Android實(shí)現(xiàn)商城購(gòu)物車功能的實(shí)例代碼
最近公司項(xiàng)目做商城模塊,需要實(shí)現(xiàn)購(gòu)物車功能,主要實(shí)現(xiàn)了單選、全選,金額合計(jì),商品刪除,商品數(shù)量加減等功能,這篇文章主要介紹了Android實(shí)現(xiàn)商城購(gòu)物車功能,需要的朋友可以參考下2019-06-06

