记得看文章三部曲,点赞,评论,转发。 微信搜索【程序员小安】关注还在移动开发领域苟活的大龄程序员,“面试系列”文章将在公众号同步发布。java
看完《你为何在如今的公司不离职?》,不少同窗踏上了面试之路,做为颜值担当的天才少年_也开始了面试之路。android
天才少年_来到一家公司等待面试中。。。
一个眼睛又大又亮的小姐姐,萌萌的站在我去 的面前。 你像一片轻柔的云在我眼前飘来飘去,你清丽秀雅的脸上荡漾着春天般美丽的笑容,我连咱们孩子的名字都起好了。等等,我tm不是来面试的吗?程序员
小伙子,据说你是来面试的,我是今天的面试官,你先介绍一下你本身吧。面试
我叫【天才少年_】,男,30未婚,家里有车有房,个人优势是英俊潇洒,个人座右铭是:既往不纠结,纵情向前看,继续努力。微信
额,你这介绍,怎么感受是来相亲的。markdown
果真面试官已经被我英俊的外表深深吸引,不能自拔,嗯,萌萌的外表都是不太聪明的样子,今天面试有但愿啦,我心中一阵暗喜。多线程
Android消息处理机制(Handler、Looper、MessageQueue与Message)已经被问烂了,那咱们今天来谈谈为何须要主线程更新UI,子线程不能更新UI?并发
卧槽,不按套路出牌啊,果真漂亮的女人都难搞定。app
1)首先,并不是在子线程里面更新UI就必定有问题,以下所示的代码,则能够完美更新UI。ide
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); init(); new Thread(new Runnable() { @Override public void run() { tv_sport_mile.setText("测试界面更新"); } }).start(); } 复制代码
可是,若是咱们让线程等待2秒后再更新UI,则会发生报错,代码以下所示:
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); init(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } tv_sport_mile.setText("测试界面更新"); } }).start(); } 复制代码
异常报错日志以下图所示:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7021) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1047) 复制代码
为何在onActivityCreated方法里面能够实现子线程更新UI,可是线程等待两秒后就异常呢?
你要是不傻,你就知道,确定是刷新线程判断时机的缘由,当时这是个人心理想法,脑子里说不要,嘴上仍是很真诚的。
从at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7021)的报错能够看到是在ViewRootIml类的checkThread方法中出现异常,多说无益,开启撸源码:
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } 复制代码
view的绘制流程是从scheduleTraversals()方法开始的,包括不少面试官喜欢问的onMeasure、onLayout、onDraw都是由该方法发起的。而在调用scheduleTraversals()方法前,调用了checkThread()方法,该方法会检查当前线程是否跟VewiRootImpl的线程一致,由于VewiRootImpl通常都是在主线程中建立,因此通常都说为是否为主线程。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } 复制代码
若是当前线程不是主线程,则抛出异常Only the original thread that created a view hierarchy can touch its views,跟咱们的异常一直吻合。总结一下就是在刷新页面前会判断当前是否在主线程,若是不在主线程则抛异常,因此咱们开始学Android的时候,别人就告诉咱们:更新UI必定要在主线程。
那为何上面第一次没有线程等待的时候没有报错呢?能够讲讲吗?
我想...大概,多是ViewRootImp尚未建立出来吧,因此没有走到checkThread()方法。
ViewRootImp何时建立的,在onActivityCreated方法后面吗?
我想起了那个风黑夜高的晚上,我跟小韩(咱们部门的程序媛)干着羞羞的事情,嘿嘿~~ 不对,是一块儿加班看源码的经历,我努力回忆着ViewRootImp的建立过程。
从ActivityThread源码开始,找到handleResumeActivity()方法:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ... mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } ... r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } ... } } 复制代码
从上面的代码能够看到,调用r.activity.makeVisible();咱们看下Activity的makeVisible()的处理逻辑
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); } 复制代码
经过上面的方法能够看到,makeVisible调用了WindowManager的addView方法,WindowManager是个接口,他的具体实现类是WindowManagerImp,直接看WindowManagerImp的addView()方法:
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } 复制代码
mGlobal是WindowManagerGlobal对象,即调用了WindowManagerGlobal的addView方法,继续深刻,快乐继续。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... 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对象,后面View的刷新正是经过ViewRootImpl实现的,因为你面试官没有问,这边不展开讨论,否则把我留到天黑,面试官可能有危险,嘿嘿。
赠送一个知识点:真正把mDecor加到WindowManager上是并显示出来在makeVisible()方法中实现的,Activity的Window才能正在被使用。
小伙子理解讲得还不错哦 那ViewRootImp是在onActivityCreated方法后面建立的吗?
看来面试官小姐姐仍是没有忘记这个问题,咱们回过头来看handleResumeActivity()
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ... mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } ... r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } ... } } 复制代码
能够看到里面调用了performResumeActivity()方法,继续跟到performResumeActivity()方法体:
public final ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide, String reason) { ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); ... r.activity.performResume(); synchronized (mResourcesManager) { // If there is a pending local relaunch that was requested when the activity was // paused, it will put the activity into paused state when it finally happens. // Since the activity resumed before being relaunched, we don't want that to // happen, so we need to clear the request to relaunch paused. for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) { final ActivityClientRecord relaunching = mRelaunchingActivities.get(i); if (relaunching.token == r.token && relaunching.onlyLocalRequest && relaunching.startsNotResumed) { relaunching.startsNotResumed = false; } } } ... } } return r; } 复制代码
performResumeActivity()方法调用了r.activity.performResume(),咱们继续看Activity的performResume()的源码,再次深刻,再次快乐。
final void performResume() { ... mCalled = false; // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + " did not call through to super.onResume()"); } ... } 复制代码
而后又调用了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()); } } } } 复制代码
能够看到callActivityOnResume()方法调用了activity.onResume(),即回调到Activity的onResume()方法,综合上面的分析能够得出:ViewRootImpl是在Activity的OnResume()方法后面建立出来的。
到这里能够过后一支烟了,不是,是总结一下了:
1)ViewRootImpl是在Activity的onResume()方法后面建立出来的,因此在onResume以前的UI更新能够在子线程操做而不报错,由于这个时候ViewRootImpl尚未建立,没有执行checkThread()方法。
2)安卓系统中,操做viwe对象没有加锁,因此若是在子线程中更新UI,会出现多线程并发的问题,致使页面展现异常。
小伙子分析得很不错,把我打动了,回去等offer吧。
微信搜索【程序员小安】“面试系列(java&andriod)”文章将在公众号同步发布。