Android子线程和更新UI问题

引子:

情形1
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.home_tv);
        ImageView imageView = findViewById(R.id.home_img);

        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("更新TextView");
                imageView.setImageResource(R.drawable.img);
            }
        }).start();
    }
复制代码

运行结果:正常运行!!!

情形二
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.home_tv);
        ImageView imageView = findViewById(R.id.home_img);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                textView.setText("更新TextView");
                imageView.setImageResource(R.drawable.img);
            }
        }).start();
    }
复制代码

运行结果:异常

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
        at android.view.View.requestLayout(View.java:17476)
        at android.widget.TextView.checkForRelayout(TextView.java:6871)
        at android.widget.TextView.setText(TextView.java:4057)
        at android.widget.TextView.setText(TextView.java:3915)
        at android.widget.TextView.setText(TextView.java:3890)
        at com.dong.demo.MainActivity$1.run(MainActivity.java:44)
        at java.lang.Thread.run(Thread.java:818)
复制代码

不是说,子线程不能更新UI吗,为什么情形一可以正常运行,情形二不能正常运行呢;

1.子线程修改UI出现异常,与什么方法有关
1.子线程什么时候可以更新UI
2.Android为什么要规定主线程更新UI

一、子线程修改UI出现异常,与什么方法有关

首先从出现异常的log日志入手,发现出现异常的方法调用顺序如下:

TextView.setText(TextView.java:4057)

TextView.checkForRelayout(TextView.java:6871)

View.requestLayout(View.java:17476)

RelativeLayout.requestLayout(RelativeLayout.java:360)

View.requestLayout(View.java:17476)

ViewRootImpl.requestLayout(ViewRootImpl.java:874)

ViewRootImpl.checkThread(ViewRootImpl.java:6357)

更改ImageView时,出现的异常类似;

首先看TextView.setText()方法的源码

   private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
        
        //省略其他代码

        if (mLayout != null) {
            checkForRelayout();
        }

        sendOnTextChanged(text, 0, oldlen, textLength);
        onTextChanged(text, 0, oldlen, textLength);

        //省略其他代码
复制代码

然后,查看以下checkForRelayout()方法的与源码。

    private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT

            //省略代码

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }
复制代码

checkForReLayout方法,首先会调用需要改变的View的requestLayout方法,然后执行invalidate()重绘操作;

TextView没有重写requestLayout方法,requestLayout方法由View实现;

查看RequestLayout方法的源码:

    public void requestLayout() {
        //省略其他代码
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
复制代码

View获取到父View(类型是ViewParent,ViewPaerent是个接口,requestLayout由子类来具体实现),mParent,然后调用父View的requestLayout方法,比如示例中的父View就是xml文件的根布局就是RelativeLayout。 RelativeLayout的requestLayout()方法源码:

    @Override
    public void requestLayout() {
        super.requestLayout();
        mDirtyHierarchy = true;
    }
复制代码

继续跟踪super.requestLayout()方法,即ViewGroup没有重新,即调用的是View的requestLayout方法。

经过一系列的调用ViewParent的requestLayout方法,最终调用到ViewRootImp的requestLayout方法。ViewRootImp实现了ViewParent接口,继续查看ViewRootImp的requestLayout方法源码。

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
复制代码

ViewRootImp的requestLayout方法中有两个方法:

一、checkThread,检查线程,源码如下

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
复制代码

判断当前线程,是否是创建ViewRootImp的线程,而创建ViewRootImp的线程就是主线程,当前线程不是主线程的时候,就抛出异常。 二、scheduleTraversals(),查看源码:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
复制代码

查看mTraversalRunnable中run()方法的具体操作

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
复制代码

继续追踪doTraversal()方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

复制代码

查看到performTraversals()方法,熟悉了吧,这是View绘制的起点。

总结一下:

1.Android更新UI会调用View的requestLayout()方法,在requestLayout方法中,获取ViewParent,然后调用ViewParent的requestLayout()方法,一直调用下去,直到调用到ViewRootImp的requestLayout方法;

2.ViewRootImp的requetLayout方法,主要有两部操作一个是checkThread()方法,检测线程,一个是scheduleTraversals,执行绘制相关工作;

猜你喜欢

转载自juejin.im/post/5c7e19b7518825196101a47d
今日推荐