- 尝试直接在子线程中更新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()
}
image.png
可以看到界面正常展示,textView的内容被更新且没有crash.
那我们就能得出可以在子线程中随意更新UI的结论了吗?
- 在thread中加上延时呢?
Thread(Runnable {
Thread.sleep(300)
text.text = "can I change you?"
}).start()
运行,竟然崩溃了。。
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)
异常翻译:只有创建这个view的线程才能操作这个view!
注意此时我们的子线程都在oncreate中,那如果放在onresume中呢?
override fun onResume() {
super.onResume()
Thread(Runnable {
// Thread.sleep(300) 放在onResume中后,不加延迟不会崩溃,加延时的话,延时短的情况下偶尔崩溃,长的话必崩
text.text = "can I change you?"
}).start()
Log.d("TAG", "onResume()")
}
为什么加了长延迟,就必崩呢?
看下崩溃的堆栈,发现是在ViewRootImpl.requestLayout的时候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.");
}
}
//那这个mThread又是什么时候赋值的呢?
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
....
}
//可以看出mThread是在ViewRootImpl初始化的时候赋值的,那ViewRootImpl初始化是什么时候呢?其实在onResume时,最终会调用到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);
//最终调用ViewRootImpl.setView开始刷新绘制View
root.setView(view, wparams, panelParentView);
...
}
由于ViewRootImpl初始化是在onResume 中调用的,也就是在主线程调用,因此ViewRootImpl的mThread在onResume后才被赋值,因此之后子线程中更新text,调用到requestLayout的时候检查线程就会异常崩溃。
在onCreate中子线程不加延时不崩溃是因为此时ViewRootImpl还没完成初始化,还没开始绘制,绘制是在onresume中调用了ViewRootImpl.setView之后开始的,就会把绘制之前对view设置的属性进行绘制。
进阶
- ViewPropertyAnimator Android 5.0之后通过RenderThread实现异步layout,
measure 从而实现异步动画。只能通过反射使用
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();
}
}
缺点是 使用限制较多,不能使用监听器,不能开启硬件加速等。
网友评论