Android中為什么主線程不會因?yàn)長ooper.loop()里的死循環(huán)阻塞?

標(biāo)題是偽命題

參考資料 Android中為什么主線程不會因?yàn)長ooper.loop()里的死循環(huán)卡死? 知乎
之前對這個概念一直處于比較模糊的狀態(tài),也是一直被自己忽略了,認(rèn)為可能涉及的東西過于復(fù)雜,所以不敢對自己問,為什么?
這兩天狀態(tài)不錯,生活還是code比較有趣,簡單而真實(shí),所以曾經(jīng)被忽略的問題不經(jīng)意間又開始出現(xiàn)在腦海.
這個問題在我理解看來可以分為兩個問題

為什么主線程需要阻塞

何謂主線程,和其他線程有什么不同之處

  • 何謂主線程
    主線程,通常稱之為UI線程,也就是APP進(jìn)程被創(chuàng)建的時候所處的線程,和其他的線程一樣都是一個普通的線程.
  • 不同之處
    從app角度來說,不同之處主要集中在UI界面更新上面,普通線程不能更新UI界面,如此設(shè)計(jì)也是為了程序的健壯性,畢竟不同線程同時對對象操作時為了保證其準(zhǔn)確性都要進(jìn)行加鎖,而界面更新不能像對象那樣僅僅保證準(zhǔn)確性,還要保證其連貫性,一個按鈕在同一段時間同步(假同步)發(fā)生了向左又向右的滑動自然是不可取的.

線程的生命周期

一個進(jìn)程或線程在CPU看來無非就是一段的可執(zhí)行代碼,代碼執(zhí)行完畢,線程的生命也就到頭了.

APP的生命周期

從使用手機(jī)的角度來看,從點(diǎn)開APP圖標(biāo)開始,到完全退出APP結(jié)束.

主線程在哪里進(jìn)行了阻塞

我們知道APP的入口是在ActivityThread,一個Java類,有著main方法,而且main方法中的代碼也不是很多.

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        AndroidKeyStoreProvider.install();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

這就是main方法的全部代碼了,23的源碼.在其中Looper進(jìn)行了初始化,但是并不是常規(guī)的prepare(),而是prepareMainLooper(),其實(shí)差別不大,只不過是給靜態(tài)成員對象成員sMainLooper進(jìn)行了初始化賦值,并不準(zhǔn)予同進(jìn)程中sMainLooper初始化第二次而已.
然后在代碼末尾Looper.loop進(jìn)行阻塞.

標(biāo)題為什么是偽命題

我們都知道主線程是隨著APP的啟動而啟動,隨著APP的結(jié)束而結(jié)束的(多進(jìn)程對應(yīng)多主線程的情況我就將其看做一個統(tǒng)一的主線程).
APP要一直運(yùn)行直到用戶退出,那么主線程就必然不能代碼運(yùn)行完畢而終止,所以需要進(jìn)行阻塞,直到用戶退出了APP,才能停止阻塞,讓CPU執(zhí)行完剩下的代碼,爾后代碼執(zhí)行完畢,主線程從而壽終正寢.

為什么主線程阻塞還能更新UI

既然線程是在Looper中阻塞了,那么與Looper配合著出現(xiàn)的Handler肯定是少不了的.
至于Handler是如何進(jìn)行線程切換不了解的同學(xué)請戳這

ActivityThread.H

很容易就在Activity中找到了繼承自Handler的內(nèi)部類H,并且重寫了handleMessage方法,代碼就不列出了.

ActivityThread.H怎么和Looper交互的

光有Handler是不行的,關(guān)鍵要有調(diào)用Handler的地方,然后Handler才能去處理,才會在主線程調(diào)用一個又一個方法.
答案在這!


        ActivityThread thread = new ActivityThread();
        thread.attach(false);

進(jìn)一步來看attach()方法

    private void attach(boolean system) {
        ...
        if (!system) {
            ...
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
            ...
        } else {
            ...
        }

       ...
    }

恩,想必你們都知道我想看什么了,這里傳入了對象mAppThread,我只關(guān)心mAppThread對象,而他作為參數(shù)最終通過IPC傳遞到哪里去,能力有限就不再繼續(xù)跟進(jìn)了.

ApplicationThread

上面我們說到了mAppThread對象,那么這個對象是哪個對象呢?就是ApplicationThread的實(shí)例化對象,代碼不多,也就500來行,我就截取一點(diǎn)點(diǎn)來示例一下

        public final void schedulePauseActivity(IBinder token, boolean finished,
                boolean userLeaving, int configChanges, boolean dontReport) {
            sendMessage(
                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                    token,
                    (userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
                    configChanges);
        }

        public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

        public final void scheduleCreateService(IBinder token,
                                                        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }

        public final void scheduleBindService(IBinder token, Intent intent,
                                              boolean rebind, int processState) {
            updateProcessState(processState, false);
            BindServiceData s = new BindServiceData();
            s.token = token;
            s.intent = intent;
            s.rebind = rebind;

            if (DEBUG_SERVICE)
                Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
                        + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
            sendMessage(H.BIND_SERVICE, s);
        }

聰明的同學(xué)已經(jīng)明了,主線程阻塞之后生命周期等方法是如何啟用的.
這一個個形似各種聲明周期的方法,最終還調(diào)用了sendMessage()方法,讓我們再來看看sendMessage方法的是怎么操作的

    private void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1) {
        sendMessage(what, obj, arg1, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2) {
        sendMessage(what, obj, arg1, arg2, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

可以看到最終調(diào)用了mH.sendMessage()方法,而mH是誰呢?ActivityThread的成員變量是ActivityThread.H的實(shí)例化對象.

總結(jié)

到此想必各位也就明了,主線程確實(shí)是阻塞的,不阻塞那APP怎么能一直運(yùn)行,所以說主線程阻塞是一個偽命題,只不過是沒有弄明白既然阻塞了,為什么還能調(diào)用各種聲明周期而已.
調(diào)用生命周期是因?yàn)橛蠰ooper,有MessageQueue,還有溝通的橋梁Handler,通過IPC機(jī)制調(diào)用Handler發(fā)送各種消息,保存到MessageQueue中,然后在主線程中的Looper提取了消息,并在主線程中調(diào)用Handler的方法去處理消息.最終完成各種聲明周期.

文章到此結(jié)束,順便給自己打個小廣告,深圳求職,目前在職招人頂缸中(ps:找個人頂缸真不好招...全是假簡歷)
簡歷戳我

擴(kuò)展的知識點(diǎn)

IPC機(jī)制下的startService流程分析
為什么選擇Binder為什么 Android 要采用 Binder 作為 IPC 機(jī)制?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容