平时我们在开发过程中知道主线程不能进行耗时操作,子线程不能更新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,子线程进行耗时操作的逻辑比较合理一些。