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

子线程真的不能更新UI吗?其实,在onResume以及onResume之前,开启一个子线程来更新UI,都有可能是会成功的,并且成功率相当大,失败的情况应该也会有,比较极端的情况下,UI线程一直霸占的CPU,子线程一直执行不到。

子线程更新UI代码如下:

@Override
    protected void onResume() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("子线程更新UI");//经测试,这里是成功的,那么onCreate,onStart就更不用说了

            }
        }).start();

        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("再试试子线程更新UI");//这里就不行了
                    }
                }).start();
            }
        });
        super.onResume();
    }

揭晓原理:

我们都知道,在子线程中更新UI,会抛如下异常:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

翻译过来是:只有创建这个View层级的原始线程才能更新这些view. 也就是并不是一定非要在UI线程.

这个异常来自ViewRootImpl的checkThread方法:

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

只有ViewRootImpl创建完成以后,才会检查线程,那么ViewRootImpl什么时候创建的呢?

是在Activity的makeVisible中创建的:

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());//ViewRootImpl就是在这个过程中创建的,请自行追踪源码
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }


那么makeVisible又是什么时候调用的呢?

是在onResume回调之后调用的。

这里简要介绍Activity启动流程中的一小部分:

ActivityThread.handleLaunchActivity {ActivityThread.performLaunchActivity —> activity.attach } — >【这中间还有onCreate和onStart的回调过程】—> ActivityThread.handleResumeActivity —>ActivityThread.performResumeActivity —> activity.performResume() —> Instrumentation.callActivityOnResume 
—> activity.onResume —> activity.makeVisible()【这一步是在ActivityThread.handleResumeActivity中调用的

ActivityThread.handleResumeActivity源码,标出了重点代码行:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
        ...省略...
        ActivityClientRecord r = performResumeActivity(token, clearHide);//重点

        if (r != null) {
            final Activity a = r.activity;

            ...省略...
            
            // The window is now visible if it has been added, we are not
            // simply finishing, and we are not starting another activity.
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                
                ...省略...
                
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();//重点
                }
            }

            ...省略...

        } else {
            // If an exception was thrown when trying to resume, then
            // just end this activity.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {
            }
        }
    }

ActivityThread. performResumeActivity中调用了activity.performResume()

public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);
        if (localLOGV) Slog.v(TAG, "Performing resume of " + r
                + " finished=" + r.activity.mFinished);
        if (r != null && !r.activity.mFinished) {
            if (clearHide) {
                r.hideForNow = false;
                r.activity.mStartedActivity = false;
            }
            try {
                r.activity.onStateNotSaved();
                r.activity.mFragments.noteStateNotSaved();
                if (r.pendingIntents != null) {
                    deliverNewIntents(r, r.pendingIntents);
                    r.pendingIntents = null;
                }
                if (r.pendingResults != null) {
                    deliverResults(r, r.pendingResults);
                    r.pendingResults = null;
                }
                r.activity.performResume();//重点行

                EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
                        UserHandle.myUserId(), r.activity.getComponentName().getClassName());

                r.paused = false;
                r.stopped = false;
                r.state = null;
                r.persistentState = null;
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                        "Unable to resume activity "
                        + r.intent.getComponent().toShortString()
                        + ": " + e.toString(), e);
                }
            }
        }
        return r;
    }

activity.performResume()中通过Instrumentation.callActivityOnResume回调了Activity.onResume,

由上可以看出Activity.makeVisible是在onResume之后调用的,进而ViewRootImpl也是在onResume之后创建,所以就会有文章开头描述的现象:

在onResume以及onResume之前,开启一个子线程来更新UI,都有可能是会成功的,并且成功率相当大.

当ViewRootImpl创建完成,之后更新UI,比如TextView.setText(),会调用invalidate或者requestLayout,都会调用checkThread来检查线程的。


这个知识点的相关知识点其实很多:Activity的启动流程以及工作流程,Activity、Window、View之间的关系等

Activity启动流程,推荐老罗的Android之旅

Activity、Window、View之间的关系,就推荐下面这一篇吧

Android Activity 、 Window 、 View之间的关系

http://blog.youkuaiyun.com/u011733020/article/details/49465707


不当之处,欢迎指正
android交流群:230274309






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值