问题引入
在onCreate中开启子线程设置图片,发现并没有报错。而当我给button设置点击监听,在onClick中开启子线程设置图片的时候,却报错。代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bitmap2);
bt = (Button) findViewById(R.id.bt);
iv = (ImageView) findViewById(R.id.iv);
bt.setOnClickListener(this);
new ViewThread(R.mipmap.bg).start();
}
@Override
public void onClick(View v) {
new ViewThread(R.mipmap.background).start();
}
private class ViewThread extends Thread{
private int id;
public ViewThread(int id){
this.id=id;
}
@Override
public void run() {
super.run();
iv.setImageResource(id);
}
问题分析
在onCreate()的时候,Activity还没有完成viewtree的初始化操作,初始化在ViewRootImpl的performTraversals()进行的,整个过程会对viewtree从DecorView开始遍历,对所有视图进行初始化,初始化包括视图布局的大小,以及ViewParent和AttachInfo的初始化。
代码分析
当我们在onCreate中开启子线程设置imageview.setImageResource()的时候,详细的调用流程。
首先调用ImageView的setImageResource(int resId);
@android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
public void setImageResource(@DrawableRes int resId) {
...
invalidate();
}
紧接着调用View的invalidate();
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
//在这里判断如果ViewParent和AttachInfo没有初始化的话,就不会调用ViewParent的invalidateaChild()
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
}
}
接下来继续查看如果ViewParent和AttachInfo初始化完成的话,在子线程更新UI是怎么导致错误的。
@Deprecated
public void invalidateChild(View child, Rect r); //ViewParent是一个接口,它的实现是ViewRootImpl。
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread(); //可以看到在这个方法中,首先会进行线程检验,
...
}
void checkThread() { //checkThread()会判断当前线程是不是主线程,如果不是,就会报错。
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
总结
因此,如果我们在子线程中更新UI,这个时候,viewtree还没有初始化完毕,是不会调用invalidate(),也就不会调用checkThread(),所以可以在子线程更新UI。而当viewtree初始化完毕,如果我们在子线程更新UI的话,就会报错。