美文网首页
可不可以在主线程更新View

可不可以在主线程更新View

作者: vanchi | 来源:发表于2018-05-14 16:53 被阅读11次

    问题引入

    在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的话,就会报错。

    相关文章

      网友评论

          本文标题:可不可以在主线程更新View

          本文链接:https://www.haomeiwen.com/subject/mzfjdftx.html