-
初学android一段时间,经常我们会听到说不能在子线程更新UI,不能在主线程执行耗时操作,但是谁知道这到底是为什么呢?
-
难道子线程一点UI都不能操作吗?若在子线程操作UI会出现什么问题呢?
-
还有android(java)为什么要有handler这种机制呢?
========================================================================
下来我们以一个settext方法为例直入主题,抛弃长篇大论的源码片段,摘出一小段源码,争取做到简单易懂,让君恍然大悟。
通过activity的启动和布局创建过程(如果嫌麻烦的话可以稍微了解一下就OK),我们知道activity的创建需要新建一个ViewRootImpl对象,那么看看ViewRootImpl的构造函数就很必要了:
/**avtivity的线程*/
public ViewRootImpl(Context context, Display display) {
mThread = Thread.currentThread();
...
}
在初始化一个ViewRootImpl函数的时候,会调用native方法,获取到该线程对象mThread(currentThread是指什么线程呢,先带着疑问往下看),接着setText函数会调用到requestLayout方法(TextView绘制出来之后,调用setText才会去调用requestLayout方法,没有绘制出来之前,在子线程中调用setText是不会抛出Exception):
/**settext的线程*/
public void requestLayout() {
.....
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”这个问题很清楚了吧,不管startActivity函数调用在什么线程,ActivityThread是运行在主线程中的:
/**
* This manages the execution of the main thread in an
* application process, scheduling and executing activities,
* broadcasts, and other operations on it as the activity
* manager requests.
*/
public final class ActivityThread {
}
所以ViewRootImpl对象的创建也是在主线程中,所以一个Activity的对应ViewRootImpl对象中的mThread一定是代表主线程!所以下面的Thread.currentThread()若不是主线程,是不是就异常了呢?
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
[为什么android会设计成只有创建viewRootImpl的原始线程才能更改ui呢?这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。](http://blog.csdn.net/self_study/article/details/50548894)
那么handler的出现是不是就显得理所应当了呢,简单粗暴,就是为了解决子线程更新UI而出现的。
你以为到这儿就完了吗? NO!NO! NO!
往下看:
有一次我在子线程中settext竟然没报错,搞什么! 难道子线程中可以更新UI? 一度让我怀疑那个线程是主线程了。
终于,找到了原因:
其实 ViewRootImpl 是在Activity 生命周期执行到 onResume()时产生的。
看下activity的生命周期 , onCreate()–>onStart()–>onResume(),并且在主线程开始的时候,子线程也开始了。
从onresume()开始追溯:
我们知道整个activity创建是在ActivityThread里面创建的,那我们就去看看,我们找到了一个handleResumeActivity()的方法,在onResume()的时候会回调这个方法:
final void handleResumeActivity(IBinder token,
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
...
if (a.mVisibleFromClient) {
wm.addView(decor, l);
}
}
这里的decor其实就是整个Activity的根布局(Layout),然后最后调用addView( decor,l)方法。
我们去ViewManager 看看 addView()的方法,而 WindowManager是一个接口,实际上就是调用了WindowManagerImpl里的addView 方法,那我们就瞧一眼。
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
addView()中又继续调用 mGlobal.addView(),然后再去Global去找父类addview()方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
}
}
}
看见了没,root = new ViewRootImpl(view.getContext(), display)
足以说明ViewRootImpl对象是在onresume()方法这儿创建的,也就是说:
-
在onResume()方法之前更新UI,mThread指向子线程,此时判断等于,不抛异常。
-
在onResume()方法之后更新UI,ViewRootImpl对象被创建,mThread指向主线程,此时判断不等于,抛出异常。
可见,子线程是不能更新UI的,但是因为执行顺序或者速度等一些方面的原因,允许在很短很短的时间内更新UI,可是这个时间谁也说不清楚,因此说明子线程更新UI是线程不安全的!!!!当然,也可以看出UI控件也是线程不安全的。
然后呢,再加上一点:
主线程不能做耗时操作,因为若耗时操作长达5s或以上,就会引发ANR问题,因此也拒绝在主线程中进行耗时操作。
号外:(因此,handler的出现很显然就是为了子线程向主线程发送UI请求而出现的~~出现的根本目的就是上面所说。)
读到这儿,我希望读者能有一种恍然大悟的感觉,若没有…咳咳,那我再练练吧
blog:http://blog.csdn.net/u012534831/article/details/52056072