記得看文章三部曲,點贊,評論,轉發。 微信搜索【程序員小安】關注還在移動開發領域苟活的大齡程序員,「面試系列」文章將在公衆號同步發佈。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)」文章將在公衆號同步發佈。