1.前言
看完《你爲什麼在現在的公司不離職?》,很多同學踏上了面試之路,作爲顏值擔當的天才少年_也開始了面試之路。
2.正文
天才少年_來到一家公司等待面試中。。。
一個眼睛又大又亮的小姐姐,萌萌的站在我去 的面前。 你像一片輕柔的雲在我眼前飄來飄去,你清麗秀雅的臉上盪漾着春天般美麗的笑容,我連我們孩子的名字都起好了。等等,我tm不是來面試的嗎?
小夥子,聽說你是來面試的,我是今天的面試官,你先介紹一下你自己吧。
我叫【天才少年_】,男,30未婚,家裏有車有房,我的優點是英俊瀟灑,我的座右銘是:既往不糾結,縱情向前看,繼續努力。
額,你這介紹,怎麼感覺是來相親的。
果然面試官已經被我英俊的外表深深吸引,不能自拔,嗯,萌萌的外表都是不太聰明的樣子,今天面試有希望啦,我心中一陣暗喜。
Android消息處理機制(Handler、Looper、MessageQueue與Message)已經被問爛了,那我們今天來談談爲什麼需要主線程更新UI,子線程不能更新UI?
臥槽,不按套路出牌啊,果然漂亮的女人都難搞定。
1)首先,並非在子線程裏面更新UI就一定有問題,如下所示的代碼,則可以完美更新UI。
@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方法中出現異常,多說無益,開啓擼源碼:
我們首先看ViewRootImpl源碼中的requestLayout()和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吧。