Android 子线程更新UI

在子线程中是不能更新UI的,为啥呢?
之前我是这么认为的因为View的方法都是不安全的,没有Synchronous修饰。所以UI线程和子线程如果通过操作就会造成同步问题。
可是Java中,就算多线程操作同一块不安全没有加锁的内存,只是会造成数据错乱的问题,但是仍然可以正常运行。为什么Android中就会报异常,强制退出呢?

所以还是没有找到问题的关键,虽然说google设计的初衷肯定是因为多线程造成的同步问题

参考

1.为什么我们可以在非UI线程中更新UI

2.【Android开发经验】来来来,同学,咱们讨论一下“只能在UI主线程更新View”这件小事


代码

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="StartAlarm"/>
</RelativeLayout>

MainActivity中
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        new Thread() {
            @Override
            public void run() {
                /*try {
                    Thread.sleep(2000);
                    start.setText("thread update");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                start.setText("thread update");
            }
        }.start();
    }
运行结果:

如果直接start.setText("thread update");那么不报异常,更新成功。
如果休眠了两秒,报异常,强制退出

E/AndroidRuntime: Process: lbb.demo.test, PID: 1799
E/AndroidRuntime: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime:     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
E/AndroidRuntime:     at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:907)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
E/AndroidRuntime:     at android.view.View.requestLayout(View.java:18722)
E/AndroidRuntime:     at android.widget.TextView.checkForRelayout(TextView.java:7172)
E/AndroidRuntime:     at android.widget.TextView.setText(TextView.java:4342)
E/AndroidRuntime:     at android.widget.TextView.setText(TextView.java:4199)
E/AndroidRuntime:     at android.widget.TextView.setText(TextView.java:4174)
E/AndroidRuntime:     at lbb.demo.test.MainActivity$1.run(MainActivity.java:31)

沿着异常栈打印消息,我们可以发现
调用了TextView的setText方法,接着调用TextView.checkForRelayout(因为是设置了textview的内容,所以textview的大小很可能发生变化,所以是requestLayout,而不是invalidate),checkForRelayout中调用基类View.requestLayout,requestLayout方法中有mParent.requestLayout();调用了父布局的方法,这里的父布局是RelativeLayout。因为在RelativeLayout中requestLayout只是调用了基类View的requestLayout方法,所以一直沿着视图层次往上,最后是到了DecorView这个根View上,
我们知道DecorView中其实很多处理都是放在ViewRootImpl中的
ViewRootImpl中的requestLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
checkThread检查当前线程是否是main线程,如果不是那么抛出异常throw new CalledFromWrongThreadException,原来如此

但为什么如果没有休眠2s,不会报异常呢? 因为此时ViewRootImpl都还没来得及去创建,setText只是为这个view更新了一下属性,自然不会抛异常。2s后创建ViewRootImpl对象,所以会抛异常。
ViewRootImpl在什么时候创建呢?简单的讲其实是在activity的onResume调用之后的。。。

可以参考前面的博客    Android View视图层次

更详细的分析可以参考 为什么我们可以在非UI线程中更新UI


另外一种情况,如果是自定义的View直接继承自View,在子线程中刷新会出现什么情况呢?实际上也会抛出CalledFromWrongThreadException异常

因为,我们知道

myview.invalidate();

实际上,view视图层次结构中对任何一个view,调用invalidate,最终都会调用到最顶层View,也就是DecorView的invalidateChildInParent方法,,,然而实际上是有ViewRootImpl来处理的,可以看到

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
        ...........
    }
这里同样的调用了checkThread方法,来检查当前线程是否是main线程。虽然 ViewRootImpl的invalidate方法没有调用checkThead

    void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值