剛開始做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的添加和更新:
01.png
總結
1.Android更新UI操作的線程要和創建ViewRootImpl的線程一致;
2.當前線程要開啟了Loop循環,因為UI刷新依賴了Handler機制;