抓住十一月的尾巴,分享一首童年回憶: ??brave heart
看到這個標題,好多人第一時間想到的是什么?
感興趣的不妨跟著下面的代碼看看會發(fā)生什么?
首先我在 onCreate 方法里調(diào)用 setText() 方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mContext = this
Log.e(TAG, "onCreate")
Thread {
val simpleDateFormat = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss") // HH:mm:ss
//獲取當前時間
val date = Date(System.currentTimeMillis())
// 更新TextView文本
tv_title.text="Date獲取當前日期時間" + simpleDateFormat.format(date))
}.start()
}
這個時候允許呢,會發(fā)現(xiàn),哎?為什么正常呢,不應(yīng)該報錯嗎?
但是呢, 當我改成了這樣以后再次運行
Thread {
try {
Thread.sleep(2000)
val simpleDateFormat = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss") // HH:mm:ss
//獲取當前時間
val date = Date(System.currentTimeMillis())
// 更新TextView文本內(nèi)容
tv_title.text = "Date獲取當前日期時間" + simpleDateFormat.format(date)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}.start()
結(jié)果
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6557)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:943)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:5081)
at android.view.View.invalidateInternal(View.java:12719)
at android.view.View.invalidate(View.java:12683)
at android.view.View.invalidate(View.java:12667)
at android.widget.TextView.checkForRelayout(TextView.java:7167)
at android.widget.TextView.setText(TextView.java:4347)
at android.widget.TextView.setText(TextView.java:4204)
at android.widget.TextView.setText(TextView.java:4179)
這應(yīng)該就是大家熟悉的報錯了吧,不允許在非UI線程中更新UI線程
既然報這個錯了,那就跟進去,看看 ViewRootImpl.java 為什么報這個錯,之前分享過看源碼的方式。
點我看源碼
既然報錯已經(jīng)告訴我們在哪一行了,那我們就點進去看看,可以很容易的找到
在這里要說明一下,在Android2.2以后是用ViewRootImpl來代替ViewRoot的,用來連接WindowManager和DecorView,而且View的繪制也是通過ViewRootImpl來完成的。
6554 void checkThread() {
6555 if (mThread != Thread.currentThread()) {
6556 throw new CalledFromWrongThreadException(
6557 "Only the original thread that created a view hierarchy can touch its views.");
6558 }
6559 }
當Activity對象被創(chuàng)建完畢后,將DecorView添加到Window中,同時會創(chuàng)建ViewRootImpl對象,在源碼中可以看到 mThread 是在ViewRootImpl 的構(gòu)造方法里這樣初始化的。然后再把他設(shè)為主線程。那現(xiàn)在捋一下,從上面的錯誤棧里,可以看到調(diào)用的流程是:
at android.widget.TextView.setText(TextView.java:4347)
at android.widget.TextView.checkForRelayout(TextView.java:7167)
at android.view.View.invalidate(View.java:12667)
at android.view.View.invalidateInternal(View.java:12719)
atandroid.view.ViewGroup.invalidateChild(ViewGroup.java:5081)
atandroid.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:943)
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6557)
所以現(xiàn)在聰明的同事是不是知道了,要去看看 ViewRootImpl 這個是在哪里被初始化的?
3126 final void handleResumeActivity(IBinder token,
3127 boolean clearHide, boolean isForward, boolean reallyResume) {
...
3158 if (r.window == null && !a.mFinished && willBeVisible) {
3159 r.window = r.activity.getWindow();
3160 View decor = r.window.getDecorView();
3161 decor.setVisibility(View.INVISIBLE);
3162 ViewManager wm = a.getWindowManager();
3163 WindowManager.LayoutParams l = r.window.getAttributes();
3164 a.mDecor = decor;
3165 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
3166 l.softInputMode |= forwardBit;
3167 if (a.mVisibleFromClient) {
3168 a.mWindowAdded = true;
3169 wm.addView(decor, l);
3170 }
3171
3172 ...
3247 }
最后的 wm.addView(decor, l) 就是我們要找的答案,這個時候看一下windowManager.addView(decorView)
public void addView(View view, ViewGroup.LayoutParams params,
232 Display display, Window parentWindow) {
233 if (view == null) {
234 throw new IllegalArgumentException("view must not be null");
235 }
236 if (display == null) {
237 throw new IllegalArgumentException("display must not be null");
238 }
239 if (!(params instanceof WindowManager.LayoutParams)) {
240 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
241 }
242
257 ViewRootImpl root;
258 View panelParentView = null;
...
303 mViews.add(view);
304 mRoots.add(root);
305 mParams.add(wparams);
306 }
307
308 // do this last because it fires off messages to start doing things
309 try {
310 root.setView(view, wparams, panelParentView);
311 } catch (RuntimeException e) {
312 // BadTokenException or InvalidDisplayException, clean up.
313 synchronized (mLock) {
314 final int index = findViewLocked(view, false);
315 if (index >= 0) {
316 removeViewLocked(index, true);
317 }
318 }
319 throw e;
320 }
321 }
看到這里,是不是有種豁然開朗的感覺,因為已經(jīng)找到了答案,答案就是跟 ViewRootImpl 的初始化有關(guān),因為我之前的代碼是在 onCreate() 的時候此時去設(shè)置textview,此時呢 View 還沒被繪制出來,ViewRootImpl 還未創(chuàng)建,它的創(chuàng)建是在 handleResumeActivity() 的調(diào)用到 windowManager.addView(decorView) 時候。