非UI线程真的不能更新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() {
                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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值