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

来源:互联网 发布:mac系统删除文件 编辑:程序博客网 时间:2024/05/16 01:30

子线程真的不能更新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.csdn.net/u011733020/article/details/49465707


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