子線程一定不能更新UI麼?爲什麼?

前言

平時我們在開發過程中知道主線程不能進行耗時操作,子線程不能更新UI,於是有了線程間通訊,有了Handler機制,那麼子線程真的不能更新UI麼?很多小夥伴在面試的時候也會經常被問到這個問題,網上已經有了不少詳解這一問題的博客,不過這裏我還是帶着複習一遍的態度,把這個流程再摸一遍。

正文

子線程一定不能更新UI麼?

先說答案:是不一定,在Activity的onResume聲明週期之前就可以。

下面我們看一下原理:

我們都知道在Android中有一個ActivityThread類,這個類非常重要,包括Activity的創建都和這個類有關,而且ActivityThread的main方法中還有Looper.preMainLooper,說明主線程的Looper也是在這裏創建的,還有Looper.looper也是在這個地方調用的,還創建了主線程的Handler H:

final H mH = new H();
class H extends Handler {
    ...
}

在這裏我們看ActivityThread這個類當中的一個方法handleResumeActivity:

 @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        

            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        ...
    }
只看這裏主要的幾行代碼,發現這裏調用了performResumeActivity方法:
@VisibleForTesting
    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
            String reason) {
        ...
            r.activity.performResume(r.startsNotResumed, reason);

            r.state = null;
            r.persistentState = null;
            r.setState(ON_RESUME);
        ...
        return r;
    }

裏面又調用了Activity中的performResume方法:

final void performResume(boolean followedByPause, String reason) {
        ...

        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);
        writeEventLog(LOG_AM_ON_RESUME_CALLED, reason);
        ...
    }

這裏又調用了Instrumentation的callActivityOnResume方法:

public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
        
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    am.match(activity, activity, activity.getIntent());
                }
            }
        }
    }

到這裏我們終於明白了,這裏調用了activity.onResume方法,說明是在這裏執行了Activity的onResume回調函數。

再反過頭來看一下,我們最開始ActivityThread類handleResumeActivity中的另一行代碼:

r.activity.makeVisible();

是調用了Activity中的makeVisible方法:

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
}

這裏通過getWindowManager方法獲取到了一個ViewManager,在這裏我們要說明的一點是,getWindowManager方法返回的是一個WindowManager類的對象,而WindowManager是一個接口繼承於ViewManager。而這裏得到的ViewManager對象實際上是WindowManager的實現類WindowManagerImpl。然後調用WindowManagerImpl中的addView方法:

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

這裏又調用了WindowManagerGlobal當中的addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

在這裏我們看到創建了一個ViewRootImpl的實例,並調用了ViewRootImpl的setView方法:

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
            
                requestLayout();
                
                ...
    }

發現這裏調用了ViewRootImpl中的requestLayout方法:

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

在requestLayout方法中,我們看到有調用一個checkThread方法和一個scheduleTraversals方法:

我們先看checkThread方法:

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

我們發現這裏是做了一個線程的判斷,拿主線程和當前線程做了一個判斷,也就是說如果當前線程不是主線程那麼就會拋出一個CalledFromWrongThreadException異常。

由此看來是在Activity的onResume回調函數之後做的checkThread線程判斷。所以說我們只要在Activity的onResume回調函數之前在子線程更新UI就不會拋異常。(也就是在onResume回調函數的super.onResume函數之前調用,就不會拋異常)。

好了,說到這裏,我們已經解答了本篇文章的問題,但是剛纔那個scheduleTraversals方法我們也來分析一下:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

我們看這裏,調用了

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

我們看一下這裏的mTraversalRunnable:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

裏面的run方法調用了doTraversal方法:

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

這裏又調用了performTraversals方法:

在performTraversals方法裏面分別調用了:performMeasure()、performLayout()、performDraw()方法。

而這三個方法又分別調用了measure、layout、draw。

又由於measure方法是final類型的,不可被子類繼承所以對外提供了onMeasure方法,所以我們在自定義View的使用,重寫的是onMeasure、onLayout、onDraw方法。

總結

Android可以在子線程更新UI,但是前提是要在Activity的onResume回調方法前調用子線程更新UI的操作。不過我們一般不這樣搞,還是遵循主線程更新UI,子線程進行耗時操作的邏輯比較合理一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章