为什么只能在主线程中操作UI?为什么子线程中setText不报错?

今天偶然看到之前写过的代码,在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.");
    }
}
代码很清楚,如果当前调用这个接口的线程和ViewRootImplmThread不同,就会抛出异常。

那么按照“主线程才能操作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 的对象先生成,这时就会抛出异常。


欢迎讨论,如果有说明不清楚或不对的地方,请批评指正。谢谢。



  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值