今天偶然看到之前写过的代码,在activity中创建了一个子线程,然后在子线程中给TextView设置了文字。想到了Android基础原理:只能在主线程中操作UI。这不是矛盾吗?然后就去翻书查资料、看源码,得出了一些结论。
1.为什么只能在主线程中操作UI?
先看源码:
View
在对View的操作中,会调用到 invalidate,然后又会调用到 View 的 invalidateInternal
void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }
invalidateInternal 中会调用 ViewParent 的 invalidateChild 方法(...为省略的代码)(注:ViewParent是个接口)
final ViewParent p = mParent; if (p != null && ...) { ... p.invalidateChild(this, damage); }
ViewGroup 与 ViewRootImpl
然后会调用到 ViewGroup 中的 invalidateChild
此方法中又会执行如下代码(取部分主要代码):
ViewParent parent = this;
do { View view = null; if (parent instanceof View) { view = (View) parent; } if (view != null) { ...... } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } parent = parent.invalidateChildInParent(location, dirty); }while (parent != null);
可以看出这段代码会循环执行,直到 最后执行到 ViewRootImpl 中的 invalidateChildInParent,主要代码如下:
@Override public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); ...... return null; }最终执行的是 checkThread 方法,并返回 null 停止了之前的循环。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }代码很清楚,如果当前调用这个接口的线程和
ViewRootImpl
中mThread
不同,就会抛出异常。
那么按照“主线程才能操作UI”的原理,那么 mThread 应该是主线程咯?
通过源码,发现 mThread 是在 ViewRootImpl 中的构造函数中被赋值为 Thread.currentThread()。
public ViewRootImpl(Context context, Display display) { ...... mThread = Thread.currentThread(); ...... }
通过翻阅书籍了解到 ViewRootImpl 是在Activity 对象被创建完毕后才会创建ViewRootImpl 对象,它是在主线程中执行的。所以这也就得出了我们的答案,如果当前线程不是主线程,那么在调用 checkThread 方法时便会抛出异常。
2.那为什么有的时候在子线程 setText 和 setImageReasource 不会出错?
看了上面的问题,我们知道 ViewRootImpl 是在 Activity 创建对象完毕之后再创建对象的。
而如果我们在子线程中 setText 和 setImageReasource 执行的速度要比主线程创建 ViewRootImpl 对象的速度要快,那么最终就不会执行到 ViewRootImpl 的 checkThread 的方法,就不会抛出异常了。
不然可以在 setText 和 setImageReasource 之前加上 sleep,让ViewRootImpl 的对象先生成,这时就会抛出异常。
欢迎讨论,如果有说明不清楚或不对的地方,请批评指正。谢谢。