- 嘗試直接在子線程中更新text
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var text: TextView = findViewById(R.id.text)
Thread(Runnable {
text.text = "can I change you?"
}).start()
}

可以看到界面正常展示,textView的內(nèi)容被更新且沒有crash.
那我們就能得出可以在子線程中隨意更新UI的結(jié)論了嗎?
- 在thread中加上延時(shí)呢?
Thread(Runnable {
Thread.sleep(300)
text.text = "can I change you?"
}).start()
運(yùn)行,竟然崩潰了。。
2020-08-09 10:55:51.895 26802-26858/com.drinkwater.meng.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.drinkwater.meng.myapplication, PID: 26802
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
at com.drinkwater.meng.myapplication.MainActivity2.run(MainActivity.kt:43)
異常翻譯:只有創(chuàng)建這個(gè)view的線程才能操作這個(gè)view!
注意此時(shí)我們的子線程都在oncreate中,那如果放在onresume中呢?
override fun onResume() {
super.onResume()
Thread(Runnable {
// Thread.sleep(300) 放在onResume中后,不加延遲不會(huì)崩潰,加延時(shí)的話,延時(shí)短的情況下偶爾崩潰,長的話必崩
text.text = "can I change you?"
}).start()
Log.d("TAG", "onResume()")
}
為什么加了長延遲,就必崩呢?
看下崩潰的堆棧,發(fā)現(xiàn)是在ViewRootImpl.requestLayout的時(shí)候checkThread 方法中檢查線程
@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.");
}
}
//那這個(gè)mThread又是什么時(shí)候賦值的呢?
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
....
}
//可以看出mThread是在ViewRootImpl初始化的時(shí)候賦值的,那ViewRootImpl初始化是什么時(shí)候呢?其實(shí)在onResume時(shí),最終會(huì)調(diào)用到WindowManagerGlobal.addView()之中。而這里也就可以看ViewRootImpl的“管理邏輯”:
public final class ActivityThread {
@Override
public void handleResumeActivity(...){
//...
windowManager.addView(decorView, windowManagerLayoutParams);
}
}
//WindowManagerImpl.java
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow){
...
//初始化ViewRootImpl的地方
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//最終調(diào)用ViewRootImpl.setView開始刷新繪制View
root.setView(view, wparams, panelParentView);
...
}
由于ViewRootImpl初始化是在onResume 中調(diào)用的,也就是在主線程調(diào)用,因此ViewRootImpl的mThread在onResume后才被賦值,因此之后子線程中更新text,調(diào)用到requestLayout的時(shí)候檢查線程就會(huì)異常崩潰。
在onCreate中子線程不加延時(shí)不崩潰是因?yàn)榇藭r(shí)ViewRootImpl還沒完成初始化,還沒開始繪制,繪制是在onresume中調(diào)用了ViewRootImpl.setView之后開始的,就會(huì)把繪制之前對(duì)view設(shè)置的屬性進(jìn)行繪制。
進(jìn)階
- ViewPropertyAnimator Android 5.0之后通過RenderThread實(shí)現(xiàn)異步layout,
measure 從而實(shí)現(xiàn)異步動(dòng)畫。只能通過反射使用
ViewPropertyAnimator animator = clickTest.animate().scaleX(4).setDuration(2000);
setViewPropertyAnimatorRT(animator,createViewPropertyAnimatorRT(clickTest));
animator.start();
private static Object createViewPropertyAnimatorRT(View view) {
try {
final Class<?> animRtClazz = Class.forName("android.view.ViewPropertyAnimatorRT");
final Constructor<?> animRtConstructor = animRtClazz.getDeclaredConstructor(View.class);
animRtConstructor.setAccessible(true);
return animRtConstructor.newInstance(view);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static void setViewPropertyAnimatorRT(ViewPropertyAnimator animator, Object rt) {
try {
final Class<?> animRtClazz = Class.forName("android.view.ViewPropertyAnimatorRT");
final Field animRtField = animRtClazz.getDeclaredField("mRTBackend");
animRtField.setAccessible(true);
animRtField.set(animator,rt);
} catch (Exception e) {
e.printStackTrace();
}
}
缺點(diǎn)是 使用限制較多,不能使用監(jiān)聽器,不能開啟硬件加速等。