刚开始做Android开发时,一不小心就会在非UI线程中做更新UI的操作,从而造成崩溃,那么这是什么原因呢?那是因为ViewRootImpl在添加View的时候通过以下代码做了线程检测
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
很清楚地看到mThread如果不等于当前线程就会抛出异常,而mThread地赋值是在ViewRootImpl构造函数中完成的,所以我们就猜测如果构造ViewRootImpl的线程等于当前线程就可以在子线程中更新View了。下面我们通过一下代码验证。
代码分为两步
步骤一:在子线程中新建ViewRootImpl;
步骤二:在子线程中更新View;
private void addViewFromThread() {
new Thread(() -> {
Looper.prepare();
//获取WindowManager实例
//步骤一
WindowManager wm = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
//设置LayoutParams属性
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
//宽高尺寸
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.format = PixelFormat.TRANSPARENT;
//设置背景阴暗
layoutParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
layoutParams.dimAmount = 0.6f;
//Window类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//构造TextView
TextView myView = new TextView(this);
myView.setText("hello window");
//设置背景为红色
myView.setBackgroundColor(Color.RED);
FrameLayout.LayoutParams myParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 400);
myParam.gravity = Gravity.CENTER;
myView.setLayoutParams(myParam);
//myFrameLayout 作为rootView
FrameLayout myFrameLayout = new FrameLayout(this);
//设置背景为绿色
myFrameLayout.setBackgroundColor(Color.GREEN);
myFrameLayout.addView(myView);
//添加到window
wm.addView(myFrameLayout, layoutParams);
//步骤二
Handler handler = new Handler(Looper.myLooper());
handler.postDelayed(()->{myView.setText("子线程更新view");},5000);
Looper.loop();
}).start();
}
验证结果如下图,结果符合预期,在子线程中实现了UI的添加和更新:
总结
1.Android更新UI操作的线程要和创建ViewRootImpl的线程一致;
2.当前线程要开启了Loop循环,因为UI刷新依赖了Handler机制;