非UI线程真的不能更新UI吗?

来源:互联网 发布:数据透视表列总计移动 编辑:程序博客网 时间:2024/04/29 12:22

废话不说,先看一个简单的效果

<span style="font-size:12px;">package com.xiaojiukeji.updateui;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                updataUI();            }        }.start();    }    private void updataUI() {        tv.setText("更新后的数据");    }}</span>

布局就不贴了,就只有一个textView,执行后的效果是

是不是有点匪夷所思?别急,改一下代码,再看!

这里只是把主线程休眠一秒,然后再更新UI

<span style="font-size:12px;">package com.xiaojiukeji.updateui;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                try {                    Thread.sleep(1000);                    updataUI();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }.start();    }    private void updataUI() {        tv.setText("更新后的数据");    }}</span>
果然,这次报了错

<span style="font-size:12px;">FATAL EXCEPTION: Thread-63736                 Process: com.xiaojiukeji.updateui, PID: 12295                                 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.                                 at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7665)                                 at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1119)</span>

很明显,错误提示不能在子线程中更新UI,那这就奇怪了,为什么呢?为什么直接更新就可以呢?且往下看

在setText()源码中可以找到这样一段代码

<span style="font-size:12px;">/**     * Check whether entirely new text requires a new view layout     * or merely a new text layout.     */    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 ||                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&                (mHint == null || mHintLayout != null) &&                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {            // Static width, so try making a new text layout.            int oldht = mLayout.getHeight();            int want = mLayout.getWidth();            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();            /*             * No need to bring the text into view, since the size is not             * changing (unless we do the requestLayout(), in which case it             * will happen at measure).             */            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),                          false);            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {                // In a fixed-height view, so use our new text layout.                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {                   <strong> <span style="color:#FF0000;">invalidate();</span></strong>                    return;                }                // Dynamic height, but height has stayed the same,                // so use our new text layout.                if (mLayout.getHeight() == oldht &&                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {                    <em><strong><span style="color:#FF0000;">invalidate();</span></strong></em>                    return;                }            }            // We lose: the height has changed and we have a dynamic height.            // Request a new view layout using our new text layout.            requestLayout();            <span style="color:#FF0000;"><em><strong>invalidate();</strong></em></span>        } else {            // Dynamic width, so we have no choice but to request a new            // view layout with a new text layout.            nullLayouts();            requestLayout();            <span style="color:#FF0000;"><strong>invalidate();</strong></span>        }    }</span>
可以看到不管怎么样,都会执行到上面代码中我用红色标出来了invalidata()方法,这个是让UI界面绘制的,可以点击去继续看

<span style="font-size:12px;">public void invalidate() {        invalidate(true);    }    /**     * This is where the invalidate() work actually happens. A full invalidate()     * causes the drawing cache to be invalidated, but this function can be     * called with invalidateCache set to false to skip that invalidation step     * for cases that do not need it (for example, a component that remains at     * the same dimensions with the same content).     *     * @param invalidateCache Whether the drawing cache for this view should be     *            invalidated as well. This is usually true for a full     *            invalidate, but may be set to false if the View's contents or     *            dimensions have not changed.     */    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) {        if (mGhostView != null) {            mGhostView.invalidate(true);            return;        }        if (skipInvalidate()) {            return;        }        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {            if (fullInvalidate) {                mLastIsOpaque = isOpaque();                mPrivateFlags &= ~PFLAG_DRAWN;            }            mPrivateFlags |= PFLAG_DIRTY;            if (invalidateCache) {                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;            }            // 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) {                final Rect damage = ai.mTmpInvalRect;                damage.set(l, t, r, b);               <span style="font-size:14px;color:#FF0000;"><strong> p.invalidateChild(this, damage);</strong></span>            }            // Damage the entire projection receiver, if necessary.            if (mBackground != null && mBackground.isProjected()) {                final View receiver = getProjectionReceiver();                if (receiver != null) {                    receiver.damageInParent();                }            }            // Damage the entire IsolatedZVolume receiving this view's shadow.            if (isHardwareAccelerated() && getZ() != 0) {                damageShadowReceiver();            }        }    }</span>
其中
<span style="font-size:12px;"><span style="font-size:14px;color:#FF0000;"><strong>p.invalidateChild(this, damage);</strong></span></span>
在判断当前操作是都在子线程中进行,也就是说,执行成功与否,在于这行代码的判断

感兴趣的同学可以通过source软件查看深层源代码,查看执行过程

总结:

                在非UI线程中是可以更新Ui的(在onCreate中创建新线程更新UI):
      当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,
      若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是

      在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),
      就可以逃避掉checkThread()的检查进而更新UI。


0 0
原创粉丝点击