Android 里子线程真的不能刷新UI吗?
在开发应用中,如果子线程中更新UI会抛出异常,但并不是因为只有UI线程才能更新UI,
而是因为ViewRootImpl会进行检查,如果 mThread!=当前线程 时会抛出异常CalledFromWrongThreadException异常
ViewRootImple.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootImple.setView() -> requestLayout() 中有调用checkThread()
那mThread是什么呢?
通过ViewRootImpl的构造函数我们可以发现mThread会被赋值为创建ViewRootImp的那个线程
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
//此处进行mThread的赋值
mThread = Thread.currentThread();
//代码省略...
}
而ViewRootImpl是在主线程中创建的,所以,才需要在主线程中更新UI
不过,设定为在主线程更新UI也是为了安全和简化起见吧
那么,能否在子线程中更新UI呢
如果ViewRootImpl是由子线程创造的,那么自然可以在该子线程中更新UI
但是如果我们直接创建ViewRootImpl实例的话,会发现找不到该类。
可以通过WindowManager.addView来间接创建一个ViewRootImpl
比如
class TestThread1 extends Thread{
@Override
public void run() {
Looper.prepare();
TextView tx = new TextView(MainActivity.this);
tx.setText("test11111111111111111");
WindowManager wm = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
250, 250, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
wm.addView(tx, params);
Looper.loop();
}
}
MainActivity是建立android工程时生成的入口类,TestThread1是MainActivity的内部类。感兴趣的话,试试吧!看看是不是在屏幕上看到了”test11111111111111111”?
具体创建ViewRoot的地方在wm.addView(tx, params)
具体流程:
WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params)
->
WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params, boolean nest)
代码(精简):
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
//新建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
root.setView(view, wparams, panelParentView);
}