您的位置:首页 > 产品设计 > UI/UE

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

2017-11-24 01:21 176 查看
子线程真的不能更新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










内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: