谁说的Android子线程一定不能更新UI,不用handler、View的post和Activity的runOnUiThread特定情况下一样可以再子线程更新UI。
天下武功,唯快不破。
先说一下结论,在Activity的onCreate方法执行入口创建子线程然后内部更新UI是可以正常更新的,不会抛异常发生闪退。但是,如果晚一会就不行了,前面加个1秒延时就会闪退。
这里测试的一个场景是在onCreate中创建一个子线程来更新状态栏颜色。
代码如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
try {
//延时 1秒钟
Thread.sleep(1000);
Log.e(TAG, "setListener-end: 2 "+Thread.currentThread().getName() );
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "setListener-end: " + Thread.currentThread().getName());
//子线程只有上面的延时不会闪退;子线程无延时只修改状态栏颜色不闪退;子线程先修改状态栏颜色再执行延时不闪退;但是先延时再该颜色就闪退。。。。。
StatusBarUtil.getInstance().setStatusBar(MainActivity.this, getResources().getColor(R.color.red), true);
}
}).start();
}
/**
* lightColor代表在黑白两个模式设置颜色,true展示wifi、电池、时间等为黑色,false时以上为白色,所以请设置色值的时候注意一下用哪个合适
* @param activity
* @param color
* @param lightColor
*/
public void setStatusBar(BaseActivity activity,int color, boolean lightColor) {
Log.e("setStatusBar ", "currentThread: "+Thread.currentThread().getName() );
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
activity.getWindow().setStatusBarColor(color);
if (lightColor) {
activity.getWindow().getDecorView()
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
activity.getWindow().getDecorView()
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
这是不加延正常更新了状态栏为红色:
这是加了延时以后的闪退报错信息,这里我就截图了,文字可能会换行导致阅读性变差。
抛异常的内容是“Only the original thread that created a view hierarchy can touch its views”,顺着日志一点一点查问题吧!
1、根据第一行很快定位直接原因是,ViewRootImpl类的checkThread方法:
2、根据第二行确定:ViewRootImpl类的requestLayout方法,调用checkThread方法
3、根据第三行确定:View类的requestLayout方法调用了ViewRootImpl类的requestLayout方法
4、根据第三行确定:View类的setLayoutParams
调用了requestLayout方法
5、然后现在基本可以确定是按这个链路走的,但是为什么加了1秒延时就会触发异常不加就没事呢?
问题就是上面第四部:
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
如果不满足这两个条件便不会走到最后的抛异常部分。
所以这个mParent是谁?给它赋值的只有两处,一处是 mParent=null,所以我们直接看另一处了。
他是在ViewRootImpl类的setView方法被调用的:
这个方法代码有点多,直接去有用部分:
- 所以上面第四部那个mParent就是ViewRootImpl了
- 那就看ViewRootImpl什么时候初始化的了。
7、ViewRootImpl的setView方法往上追踪是
WindowManagerGlobal的updateViewLayout方法调用:
8、再向上,到了ActivityThread的handleResumeActivity方法,这调用Activity的onResume声明周期的方法。
这是其中调用WindowManagrImple的updateViewLayout方法
因此再onCreate的开启子线程更新ui不跑异常是因为当时ViewRootImpl还没被初始化,而他的初始化时机是在onResume方法中。如果了延时等到延时结束如果ViewRootImpl正好已经初始化成功了,那就抛异常闪退了。
才疏学浅,如有错误,欢迎指正,多谢。