android 子线程子线程一定不会影响主线程吗

Android的UI访问是没有加锁的,这样在多个线程访问UI是不安全的。所以Android中规定只能在UI线程中访问UI。
但是有没有极端的情况?使得我们在子线程中访问UI也可以使程序跑起来呢?接下来我们用一个例子去证实一下。
新建一个工程,activity_main.xml布局如下所示:
&?xml version="1.0" encoding="utf-8"?&
xmlns:android="/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_centerInParent="true"
很简单,只是添加了一个居中的TextView
MainActivity代码如下所示:
public class MainActivity extends AppCompatActivity {
private TextView main_
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
main_tv = (TextView) findViewById(R.id.main_tv);
new Thread(new Runnable() {
public void run() {
main_tv.setText("子线程中访问");
}).start();
也是很简单的几行,在onCreate方法中创建了一个子线程,并进行UI访问操作。
点击运行。你会发现即使在子线程中访问UI,程序一样能跑起来。结果如下所示:
咦,那为嘛以前在子线程中更新UI会报错呢?难道真的可以在子线程中访问UI?
先不急,这是一个极端的情况,修改MainActivity如下:
public class MainActivity extends AppCompatActivity {
private TextView main_
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
main_tv = (TextView) findViewById(R.id.main_tv);
new Thread(new Runnable() {
public void run() {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
main_tv.setText("子线程中访问");
}).start();
让子线程睡眠200毫秒,醒来后再进行UI访问。
结果你会发现,程序崩了。这才是正常的现象嘛。抛出了如下很熟悉的异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
作为一名开发者,我们应该认真阅读一下这些异常信息,是可以根据这些异常信息来找到为什么一开始的那种情况可以访问UI的。那我们分析一下异常信息:
首先,从以下异常信息可以知道
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581)
这个异常是从android.view.ViewRootImpl的checkThread方法抛出的。
那现在跟进ViewRootImpl的checkThread方法瞧瞧,源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
只有那么几行代码而已的,而mThread是主线程,在应用程序启动的时候,就已经被初始化了。
由此我们可以得出结论:
在访问UI的时候,ViewRootImpl会去检查当前是哪个线程访问的UI,如果不是主线程,那就会抛出如下异常:
Only the original thread that created a view hierarchy can touch its views
这好像并不能解释什么?继续看到异常信息
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
那现在就看看requestLayout方法,
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
这里也是调用了checkThread()方法来检查当前线程,咦?除了检查线程好像没有什么信息。那再点进scheduleTraversals()方法看看
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
注意到postCallback方法的的第二个参数传入了很像是一个后台任务。那再点进去
final class TraversalRunnable implements Runnable {
public void run() {
doTraversal();
找到了,那么继续跟进doTraversal()方法。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
可以看到里面调用了一个performTraversals()方法,View的绘制过程就是从这个performTraversals方法开始的。PerformTraversals方法的代码有点长就不贴出来了,如果继续跟进去就是学习View的绘制了。而我们现在知道了,每一次访问了UI,Android都会重新绘制View。这个是很好理解的。
分析到了这里,其实异常信息对我们帮助也不大了,它只告诉了我们子线程中访问UI在哪里抛出异常。
而我们会思考:当访问UI时,ViewRootImpl会调用checkThread方法去检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常,这是没问题的。但是为什么一开始在MainActivity的onCreate方法中创建一个子线程访问UI,程序还是正常能跑起来呢??
唯一的解释就是执行onCreate方法的那个时候ViewRootImpl还没创建,无法去检查当前线程。
那么就可以这样深入进去。寻找ViewRootImpl是在哪里,是什么时候创建的。好,继续前进
在ActivityThread中,我们找到handleResumeActivity方法,如下:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
unscheduleGcIdler();
mSomeActivitiesChanged = true;
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
可以看到内部调用了performResumeActivity方法,这个方法看名字肯定是回调onResume方法的入口的,那么我们还是跟进去瞧瞧。
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) {
r.activity.performResume();
可以看到r.activity.performResume()这行代码,跟进 performResume方法,如下:
final void performResume() {
performRestart();
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
mCalled = false;
mInstrumentation.callActivityOnResume(this);
Instrumentation调用了callActivityOnResume方法,callActivityOnResume源码如下:
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i&N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
找到了,activity.onResume()。这也证实了,performResumeActivity方法确实是回调onResume方法的入口。
那么现在我们看回来handleResumeActivity方法,执行完performResumeActivity方法回调了onResume方法后,
会来到这一块代码:
r.activity.mVisibleFromServer = true
mNumVisibleActivities++
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible()
activity调用了makeVisible方法,这应该是让什么显示的吧,跟进去探探。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager()
wm.addView(mDecor, getWindow().getAttributes())
mWindowAdded = true
mDecor.setVisibility(View.VISIBLE)
往WindowManager中添加DecorView,那现在应该关注的就是WindowManager的addView方法了。而WindowManager是一个接口来的,我们应该找到WindowManager的实现类才行,而WindowManager的实现类是WindowManagerImpl。
找到了WindowManagerImpl的addView方法,如下:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
里面调用了WindowManagerGlobal的addView方法,那现在就锁定
WindowManagerGlobal的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index &= 0) {
removeViewLocked(index, true);
终于击破,ViewRootImpl是在WindowManagerGlobal的addView方法中创建的。
回顾前面的分析,总结一下:
ViewRootImpl的创建在onResume方法回调之后,而我们一开篇是在onCreate方法中创建了子线程并访问UI,在那个时刻,ViewRootImpl是没有创建的,无法检测当前线程是否是UI线程,所以程序没有崩溃一样能跑起来,而之后修改了程序,让线程休眠了200毫秒后,程序就崩了。很明显200毫秒后ViewRootImpl已经创建了,可以执行checkThread方法检查当前线程。
这篇博客的分析如题目一样,Android中子线程真的不能更新UI吗?在onCreate方法中创建的子线程访问UI是一种极端的情况,这个不仔细分析源码是不知道的。我是最近看了一个面试题,才发现这个。
从中我也学习到了从异常信息中跟进源码寻找答案,你呢?
本文已收录于以下专栏:
相关文章推荐
如果之前你没有尝试过onCreate方法里面用子线程的run方法去设置UI(比如对Textview进行setText操作)
在相信你看到这个标题,也会感到困惑和好奇吧。
废话不多说,先来个Demo...
问题描述  做过android开发基本都遇见过ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是只能在主线程中更改ui,子线程要修改ui...
在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法。
首先来看一下Android中消息机制:
专业术语:...
方法一:用Handler 
1、主线程中定义Handler: 
Java代码  
Handler mHandler = new Handler() {  
 &#16...
最近看了网上,在子线程更新UI的方法,说法很多,但都不是很全面。在次我争取做到总结的全面一些,希望以后对自己,对大家都有一些帮助。方法一: view.post(Runnable action)假如该方...
本博文地址:http://blog.csdn.net/mylzc/article/details/0;转载请注明出处
Android异步处理系列文章索引
Android...
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】1
背景还记得前面《Android应用setContentView与LayoutInflate...
大家听到的往往都是子线程中不能更新UI,尤其是培训班出来的学生,听惯了老师说“子线程中不能更新ui啊”,这样的老师我只能说不负责任,今天我要讲的是在子线程中更新ui的几种方法方法一:用Handler ...
在Android平台下,进行多线程编程时,经常需要在主线程之外的一个单独的线程中进行某些处理,然后更新用户界面显示。但是,在主线线程之外的线程中直接更新页面显示的问题是:系统会报这个异常:
1、常规写法:new Handler()的handleMessage()和handler.sendMessage(msg)
2、handler的另一种用法:
3、handler.post(runn...
他的最新文章
讲师:王哲涵
讲师:王渊命
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)Android 真的不能在子线程更新UI吗? - 简书
Android 真的不能在子线程更新UI吗?
写过Android 代码的同学应该都听过Android不能在子线程更新UI,只能在主线程即UI线程处理视图。
public class MainActivity extends AppCompatActivity {
private TextView textV
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(tv);
new Thread(){
public void run() {
super.run();
textView.setText("not from UI thread!!!");
}.start();
猜一下运行结果呢? 抛出CalledFromWrongThreadException吗? No!No!No!
不信的话可以试下。这是为啥?说好的不能子线程更新UI呢! 这个我们等下再说
CalledFromWrongThreadException
首先我们测试一下:
public class MainActivity extends AppCompatActivity {
private TextView textV
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.btn);
textView = (TextView) findViewById(R.id.tv);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new Thread(){
public void run() {
super.run();
textView.setText("not from UI Thread");
}.start();
我们在点击button时,给textView 设置文字,运行App,点击button时我们会看到这个异常
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.说的很清楚,只有创建视图层次结构的原始线程可以访问它的视图。
通过查看Android是源码,我们发现这个异常是在 android.view.ViewRootImpl 抛出来的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
当Activity对象被创建完毕后,会创建ViewRootImpl对象,mThread 是在ViewRootImpl 的构造方法里这样初始化的所以mThread被赋值成主线程。
public ViewRootImpl(Context context, Display display) {
mContext =
······
mThread = Thread.currentThread();
checkThread() 方法是怎样被调用的呢
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:1083)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:5205)
at android.view.View.invalidateInternal(View.java:13656)
at android.view.View.invalidate(View.java:13620)
at android.view.View.invalidate(View.java:13604)
at android.widget.TextView.checkForRelayout(TextView.java:7347)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
at com.acemurder.test.MainActivity$1$1.run(MainActivity.java:27)
这样更直白一点
com.acemurder.test.MainActivity$1$1.run
-& android.widget.TextView.setText
-& android.widget.TextView.checkForRelayout
-& android.view.View.invalidate
-& android.view.ViewGroup.invalidateChild
-& android.view.ViewRootImpl.invalidateChildInParent
-& android.view.ViewRootImpl.invalidateChild
-& android.view.ViewRootImpl.checkThread
当运行到`checkThread()时候,Thread.currentThread()我们在OnCreate()方法里创建的一个子线程,所以抛出来了异常。
通过创建ViewRootImpl在子线程更新UI
通过方才的分析,我们发现异常的原因是TextView
的 ViewRootImpl是在我们创建Activity的时候创建的,所以我们能不能通过给一个View单独的一个ViewRootImpl呢?
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new Thread(){
public void run() {
super.run();
Looper.prepare();
TextView tv = new TextView(MainActivity.this);
tv.setText("not from UI Thread");
WindowManager windowManager = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST, PixelFormat.OPAQUE);
windowManager.addView(tv, params);
Looper.loop();
}.start();
等等,不是说好的单独创建一个ViewRootImpl吗?别急,我们来理一下通过看源码发现,WindowManager是一个抽象类,我们看他的子类WindowManagerImpl
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
addView方法最终调用了mGlobal的addView方法这里的mGlobal是private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();这样得到的,我们看下它的addView方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
······
View panelParentView =
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i &= 0; --i) {
mRoots.get(i).loadSystemProperties();
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
int index = findViewLocked(view, false);
if (index &= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
// The previous removeView() had not completed executing. Now it has.
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type &= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type &= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i & i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
看见了吧,在这里创建了一个ViewRootImpl对象,所以在子线程更新UI成功。
不创建ViewRootImpl在子线程更新UI
逼逼了这么久,终于说到文章开始那个问题了。我们OnCreate()里直接开启一个子线程去更新UI,并没有创建单独的ViewRootImpl对象啊?原因就在于ViewRootImpl的建立时间,它是在ActivityThread.Java的final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward)里创建的。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
······
if (r != null) {
final Activity a = r.
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedA
if (!willBeVisible) {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor =
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardB
if (r.mPreserveWindow) {
a.mWindowAdded =
r.mPreserveWindow =
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
······
原因就是在Activity的onResume之前ViewRootImpl实例没有建立,所以没有ViewRootImpl.checkThread检查。而textView.setText时设定的文本却保留了下来,所以当ViewRootImpl真正去刷新界面时,就把"not from UI Thread"刷了出来!  Android单线程模型是这样描述的:
Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行
  如果在其它线程访问UI线程,Android提供了以下的方式:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
  为什么呢?在子线程中就不能操作UI么?
  当一个程序第一次启动的时候,Android会同时启动一个对应的主线程,这个主线程就是UI线程,也就是ActivityThread。UI线程主要负责处理与UI相关的事件,如用户的按键点击、用户触摸屏幕以及屏幕绘图等。系统不会为每个组件单独创建一个线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。所以,响应系统回调的方法永远都是在UI线程里运行,如响应用户动作的onKeyDown()的回调。
  那为什么选择一个主线程干这些活呢?换个说法,Android为什么使用单线程模型,它有什么好处?
  先让我们看下单线程化的事件队列模型是怎么定义的:
采用一个专门的线程从队列中抽取事件,并把他们转发给应用程序定义的事件处理器
  这看起来就是Android的消息队列、Looper和Handler嘛。类似知识请参考:
  其实现代GUI框架就是使用了类似这样的模型:模型创建一个专门的线程,事件派发线程来处理GUI事件。单线程化也不单单存在Android中,Qt、XWindows等都是单线程化。当然,也有人试图用多线程的GUI,最终由于竞争条件和死锁导致的稳定性问题等,又回到单线程化的事件队列模型老路上来。单线程化的GUI框架通过限制来达到线程安全:所有GUI中的对象,包括可视组件和数据模型,都只能被事件线程访问。
  这就解释了Android为什么使用单线程模型。
  那Android的UI操作并不是线程安全的又是怎么回事?
  Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程中使用。换句话说,Android的UI操作不是线程安全可以表述为invalidate在子线程中调用会导致线程不安全。作一个假设,现在我用invalidate在子线程中刷新界面,同时UI线程也在用invalidate刷新界面,这样会不会导致界面的刷新不能同步?既然刷新不同步,那么invalidate就不能在子线程中使用。这就是invalidate不能在子线程中使用的原因。
  postInvalidate可以在子线程中使用,它是怎么做到的?
  看看源码是怎么实现的:
public void postInvalidate() {
postInvalidateDelayed(0);
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
if (mAttachInfo != null) {
Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_MSG;
msg.obj = this;
mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
  说到底还是通过Handler的sendMessageDelayed啊,还是逃不过消息队列,最终还是交给UI线程处理。所以View的更新只能由UI线程处理。
  如果我非要在子线程中更新UI,那会出现什么情况呢?
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
  抛了一个CalledFromWrongThreadException异常。
  相信很多人遇到这个异常后,就会通过前面的四种方式中的其中一种解决:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
  说到底还没触发到根本,为什么会出现这个异常呢?这个异常在哪里抛出来的呢?
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
  该代码出自&framework/base/core/java/android/view/ViewRootImpl.java
  再看下ViewRootImpl的构造函数,mThread就是在这初始化的:
public ViewRootImpl(Context context, Display display) {
mContext =
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay =
mBasePackageName = context.getBasePackageName();
mDisplayAdjustments = display.getDisplayAdjustments();
mThread = Thread.currentThread();
  再研究一下这个CalledFromWrongThreadException异常的堆栈,会发现最后到了invalidateChild和invalidateChildInParent方法中:
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
  最终通过checkThread形成了这个异常。说到底,非UI线程是可以刷新UI的呀,前提是它要拥有自己的ViewRoot。如果想直接创建ViewRoot实例,你会发现找不到这个类。那怎么做呢?通过WindowManager。
class NonUiThread extends Thread{
public void run() {
Looper.prepare();
TextView tx = new TextView(MainActivity.this);
tx.setText("non-UiThread update textview");
WindowManager windowManager = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
windowManager.addView(tx, params);
Looper.loop();
  就是通过windowManager.addView创建了ViewRoot,WindowManagerImpl.java中的addView方法:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  mGlobal是一个WindowManagerGlobal实例,代码在 frameworks/base/core/java/android/view/WindowManagerGlobal.java中,具体实现如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
if (display == null) {
throw new IllegalArgumentException("display must not be null");
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i &= 0; --i) {
mRoots.get(i).loadSystemProperties();
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
int index = findViewLocked(view, false);
if (index &= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
// The previous removeView() had not completed executing. Now it has.
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type &= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type &= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i & i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index &= 0) {
removeViewLocked(index, true);
  所以,非UI线程能更新UI,只要它有自己的ViewRoot。
  延伸一下:Android Activity本身是在什么时候创建ViewRoot的呢?
  既然是单线程模型,就要先找到这个UI线程实现类ActivityThread,看里面哪里addView了。没错,是在onResume里面,对应ActivityThread就是handleResumeActivity这个方法:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor =
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardB
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
  所以,如果在onCreate中通过子线程直接更新UI,并不会抛CalledFromWrongThreadException异常。但是一般情况下,我们不会在onCreate中做这样的事情。
  这就是Android为我们设计的单线程模型,核心就是一句话:Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。但这一句话背后,却隐藏着我们平时看不见的代码实现,只有搞懂这些,我们才能知其然知其所以然。
阅读(...) 评论()}

我要回帖

更多关于 android 子线程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信