子线程一定不能更新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

到这里我们终于明白了,这里调用了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,子线程进行耗时操作的逻辑比较合理一些。

你可能感兴趣的:(源码剖析)