1湾宙、前言
- 眾所周知在
Android
中妒御,子線程是不能更新UI
的解愤; - 那么我在想,為什么不能乎莉,會產(chǎn)生什么問題送讲;
- 是否真的就一定不能在子線程更新
UI
;
2、能否在子線程中更新UI
答案是可以的惋啃,比如以下代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("測試是否報出異常");
}
}).start();
}
運行結(jié)果并無異常哼鬓,可以正常的在子線程中更新了TextView
控件;假如讓線程休眠1000ms
,就會發(fā)生錯誤:
Only the original thread that created a view hierarchy can touch its views.
這句話的意思是只有創(chuàng)建視圖層次結(jié)構(gòu)的原始線程才能更新這個視圖,也就是說只有主線程才有權(quán)力去更新UI
肥橙,其他線程會直接拋異常的;
從at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7905)
的異常路徑可以看到拋出異常的最終在ViewRootIml
的checkThread
方法里魄宏,ViewRootIml
是View
的根類,其控制著View
的測量存筏、繪制等操作宠互,那么現(xiàn)在我們轉(zhuǎn)到ViewRootImpl.java
源碼觀察:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
scheduleTraversals()
里是對View
進行繪制操作,而在繪制之前都會檢查當前線程是否為主線程mThread
椭坚,如果不是主線程予跌,就拋出異常;這樣做法就限制了開發(fā)者在子線程中更新UI的操作善茎;
但是為什么最開始的在onCreate()
里子線程對UI
的操作沒有報錯呢券册,可以設想一下是因為ViewRootImp
此時還沒有創(chuàng)建,還未進行當前線程的判斷垂涯;
現(xiàn)在烁焙,我們尋找ViewRootImp
在何時創(chuàng)建,從Activity
啟動過程中尋找源碼,通過分析可以查看ActivityThread.java
源碼,并找到handleResumeActivity
方法:
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {
···
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
···
}
內(nèi)部調(diào)用了performResumeActivity
方法:
···
public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide) {
if (r != null && !r.activity.mFinished) { r.activity.performResume(); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to resume activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } } return r; } ··· 在內(nèi)部調(diào)用了
Activity的
performResume方法耕赘,可以肯定應該是要回調(diào)生命周期的
onResume```方法了:
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()");
}
···
}
骄蝇,然后又調(diào)用了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());
}
}
}
}
可以看到執(zhí)行了 activity.onResume()
方法,也就是回調(diào)了Activity
生命周期的onResume
方法;現(xiàn)在讓我們回頭看看handleResumeActivity
方法操骡,會執(zhí)行這段代碼:
···
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
在內(nèi)部調(diào)用了Activity
的makeVisible
方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
內(nèi)部調(diào)用了WindowManager
的addView
方法九火,而WindowManager
方法的實現(xiàn)類是WindowManagerImp
類赚窃,直接找WindowManagerImp
的addView
方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
然后又調(diào)用了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);
}
···
}
在該方法中,終于看到了ViewRootImpl
的創(chuàng)建岔激;
結(jié)論:從以上的源碼分析可得知勒极,
ViewRootImpl
對象是在onResume
方法回調(diào)之后才創(chuàng)建,那么就說明了為什么在生命周期的onCreate
方法里虑鼎,甚至是onResume
方法里都可以實現(xiàn)子線程更新UI辱匿,因為此時還沒有創(chuàng)建ViewRootImpl
對象,并不會進行是否為主線程的判斷震叙;
3掀鹅、更新UI一定要在主線程實現(xiàn)
谷歌提出:“一定要在主線程更新UI
”,實際是為了提高界面的效率和安全性媒楼,帶來更好的流暢性乐尊;反推一下,假如允許多線程更新UI
划址,但是訪問UI
是沒有加鎖的扔嵌,一旦多線程搶占了資源,那么界面將會亂套更新了夺颤,體驗效果就不言而喻了痢缎;所以在Android
中規(guī)定必須在主線程更新UI
。
4世澜、總結(jié)
- 子線程可以在
ViewRootImpl
還沒有被創(chuàng)建之前更新UI
独旷; - 訪問
UI
是沒有加對象鎖的,在子線程環(huán)境下更新UI
寥裂,會造成不可預期的風險嵌洼; - 開發(fā)者更新
UI
一定要在主線程進行操作;