1. 程式人生 > >從原始碼角度分析Android系統的異常捕獲機制是如何執行的

從原始碼角度分析Android系統的異常捕獲機制是如何執行的

我們在開發的時候經常會遇到各種異常,當程式遇到異常,便會將異常資訊拋到LogCat中,那這個過程是怎麼實現的呢?

我們以一個例子開始:

import android.app.Activity;
import android.os.Bundle;

public class MainActivity4 extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		throw new NullPointerException();
	}
}

這個程式一啟動便會拋一個異常到Logcat中,就像這樣:
10-10 16:44:16.200: W/dalvikvm(381): threadid=1: thread exiting with uncaught exception (group=0x41588d58)
10-10 16:44:16.200: W/System.err(381): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sahadev.renren/com.sahadev.activitythemetest.MainActivity4}: java.lang.NullPointerException
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2263)
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2313)
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread.access$800(ActivityThread.java:147)
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1226)
10-10 16:44:16.200: W/System.err(381): 	at android.os.Handler.dispatchMessage(Handler.java:102)
10-10 16:44:16.200: W/System.err(381): 	at android.os.Looper.loop(Looper.java:136)
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread.main(ActivityThread.java:5137)
10-10 16:44:16.200: W/System.err(381): 	at java.lang.reflect.Method.invokeNative(Native Method)
10-10 16:44:16.200: W/System.err(381): 	at java.lang.reflect.Method.invoke(Method.java:515)
10-10 16:44:16.200: W/System.err(381): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:801)
10-10 16:44:16.200: W/System.err(381): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:617)
10-10 16:44:16.200: W/System.err(381): 	at dalvik.system.NativeStart.main(Native Method)
10-10 16:44:16.200: W/System.err(381): Caused by: java.lang.NullPointerException
10-10 16:44:16.200: W/System.err(381): 	at com.sahadev.activitythemetest.MainActivity4.onCreate(MainActivity4.java:12)
10-10 16:44:16.200: W/System.err(381): 	at android.app.Activity.performCreate(Activity.java:5231)
10-10 16:44:16.200: W/System.err(381): 	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
10-10 16:44:16.200: W/System.err(381): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2227)
10-10 16:44:16.200: W/System.err(381): 	... 11 more

好,異常資訊就會通過Logcat輸出出來,接下來我們一起看一下它內部的工作原理:

首先:

我們知道通常我們在處理全域性自定義異常的時候通常會這麼寫:

import java.lang.Thread.UncaughtExceptionHandler;

public class YikaoGlobalCrashHandler implements UncaughtExceptionHandler {

	public YikaoGlobalCrashHandler() {
		super();

		Thread.setDefaultUncaughtExceptionHandler(this);

	}

	@Override
	public void uncaughtException(Thread thread, Throwable ex) {

	}
}
通過這樣的方式,我們便可以使程式在遇到異常的時候回撥我們的物件例項,然後呼叫我們的uncaughtException方法。

我們知道,如果我們不這麼設定,系統是會自己處理異常的,那就一定有一個預設的異常處理物件,沒錯:

Thread.getDefaultUncaughtExceptionHandler();
通過這個方法會返回一個系統預設的UncaughtExceptionHandler物件,那麼這個物件是在哪被設定進去的呢?我們從原始碼裡面找答案:

咱們從Java最基礎層面看起,

我們的JAVA入口是:com.android.internal.os.RuntimeInit類的main方法,至於main方法在哪被呼叫,我們以後再討論:

 public static final void main(String[] argv) {
        if (argv.length == 2 && argv[1].equals("application")) {
            if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
            redirectLogStreams();
        } else {
            if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
        }

        commonInit();

        /*
         * Now that we're running in interpreted code, call back into native code
         * to run the system.
         */
        nativeFinishInit();

        if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
    }

我們關注的是commonInit方法:
    private static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");

        /* set default handler; this applies to all threads in the VM */
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());

        /*
         * Install a TimezoneGetter subclass for ZoneInfo.db
         */
        TimezoneGetter.setInstance(new TimezoneGetter() {
            @Override
            public String getId() {
                return SystemProperties.get("persist.sys.timezone");
            }
        });
        TimeZone.setDefault(null);

        /*
         * Sets handler for java.util.logging to use Android log facilities.
         * The odd "new instance-and-then-throw-away" is a mirror of how
         * the "java.util.logging.config.class" system property works. We
         * can't use the system property here since the logger has almost
         * certainly already been initialized.
         */
        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.
         */
        NetworkManagementSocketTagger.install();

        /*
         * If we're running in an emulator launched with "-trace", put the
         * VM into emulator trace profiling mode so that the user can hit
         * F9/F10 at any time to capture traces.  This has performance
         * consequences, so it's not something you want to do always.
         */
        String trace = SystemProperties.get("ro.kernel.android.tracing");
        if (trace.equals("1")) {
            Slog.i(TAG, "NOTE: emulator trace profiling enabled");
            Debug.enableEmulatorTraceOutput();
        }

        initialized = true;
    }

在我們程式碼的第二行看到:Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());那這個UncaughtHandler類在哪被定義呢?我們還可以在RuntimeInit.java中找到答案:
    /**
     * Use this to log a message when a thread exits due to an uncaught
     * exception.  The framework catches these for the main threads, so
     * this should only matter for threads created by applications.
     */
    private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            try {
                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                if (mCrashing) return;
                mCrashing = true;

                if (mApplicationObject == null) {
                    Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
                } else {
                    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);
                }

                // Bring up crash dialog, wait for it to be dismissed
                ActivityManagerNative.getDefault().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
            } catch (Throwable t2) {
                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);
            }
        }
    }
我們看到程式碼中使用StringBuilder的message物件對基本資訊進行了組合,然後呼叫Clog_e方法,Clog_e方法通過

Log.println_native(Log.LOG_ID_CRASH, Log.ERROR, tag,msg + '\n' + Log.getStackTraceString(tr));將Log日誌輸出到控制檯。

接下來會呼叫

 // Bring up crash dialog, wait for it to be dismissed
 ActivityManagerNative.getDefault().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
方法將我們的崩潰的Dialog顯示出來,就像這樣:

最終它還會將我們的程式殺死退出:

 // Try everything to make sure this process goes away.
 Process.killProcess(Process.myPid());
 System.exit(10);

好這就是系統為我們提供的預設異常處理方法,接下來當然還有不少疑問:

1.RuntimeInit類的main方法是在哪被呼叫的。

2.throw new NullPointerException();這部分是怎麼執行的。

3.Thread的defaultUncaughtHandler屬性又是在哪被呼叫的。

4.等等

歡迎對這方面有興趣的可以在評論區參與討論,也有可能是我學的還太少。