应用的UI线程
本章主要围绕以下几点讲解:
- 什么是UI线程
- UI线程的启动流程,消息循环是怎么创建的
什么是UI线程
- UI线程就是刷新UI所在的线程
- UI是单线程刷新的
UI为啥是单线程刷新?如果是多线程刷新行不行?如果是多线程刷新UI的话,那么系统框架中就需要到处上锁,很容易出问题。但如果是单线程的话既简单又有效,所以一直以来UI框架基本都是单线程的。
那么问题来了:我们平常说的主线程跟UI线程有什么区别,主线程就一定是UI线程吗?
大家都知道耗时的操作不要在UI线程中进行,应该在子线程中处理,处理完后再到UI线程中刷新UI:有如下两个方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
Activity.runOnUiThread(Runnable)
// 代码来自Android23中:Activity.java
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
- 如果当前线程不是UI线程,则mHandler post到消息队列中。
- 如果是,则直接run()。
- mHandler是Activity中的全局变量,它没有指定Looper;也就是说当前Activity在哪个线程中创建,mHandler用的就是哪个线程的Looper。
mUiThread又是什么时候初始化的呢?
// 代码来自Android23中:Activity.java
final void attach(Context context, ) {
......
mUiThread = Thread.currentThread();
......
}
mUiThread是在attach方法中初始化的,attach方法是在Activity启动时调用,具体看 Activity的启动。 Activity的启动流程:创建Activity对象---> 创建Context ---> 调用attach给Activity赋上下文---> onCreate();这几个步骤都是在应用进程的主线程中进行的
所以对于Activity来说:UI线程 == 主线程
View.post(Runnable)
// 代码来自Android23中:View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
ViewRootImpl.getRunQueue().post(action);
return true;
}
// 代码来自Android23中:ViewRootImpl.java
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(......);
}
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
}
- mAttachInfo是ViewTree第一次绘制的时候(也就是在onResume回调之后),递归的给所有子View赋上一个mAttachInfo。
- mAttachInfo是在ViewRootImpl构造函数中初始化,ViewRootImpl是在主线程中创建,它的mHandler变量用的Looper是主线程Looper。
- 如果mAttachInfo为null(例如在onCreate中就直接调用View.post(..)), 则先把Runable丢到RunQueue中,等到ViewRootImpl创建好了,再丢到ViewRootImpl所在的线程中处理。
所以:View.post(..)最终都会丢到ViewRootImpl所在的线程中去处理,ViewRootImpl是在主线程中创建,也就是对于View来说:UI线程 == 主线程。
回顾下ViewRootImpl在主线程中创建:
小总结:
- 对Activity来说,UI线程就是主线程(activity.runOnUiThread)
- 对View来说,UI线程就是ViewRootImpl创建的时候所在的线程(View.post(Runnable r))
- Activity的DecorView对应的ViewRootImpl是在主线程创建的。
UI线程启动流程
应用进程启动的时候,主线程就有了,我们在回顾下:
入口函数也就是我们ActivityThread.main():
// 代码来自Android23中:ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
......
Looper.loop();
}
- Looper.prepareMainLooper() 创建主线程的Looper。
- Looper.loop();进入looper循环。
以上的大家应该都挺熟悉。就不说了。
总结:
这章的一些结论都是大家比较熟悉的,例如应用进程中主线程就是UI线程,可能平时大家都没区分这两个概念,举个例子:
new Thread() {
public void run() {
Looper.prepare();
......
getWindowManager().addView(view, params);
Looper.loop();
}
}.start();
- 这个例子getWindowManager().addView时会创建ViewRootImpl,也就是说ViewRootImpl是在子线程中创建的,这时这个子线程就是UI线程,在里面是可以去更新UI;
- 反而在应用进程的主线程中如果去更新view的绘制则会报CalledFromWrongThreadException。
// 代码来自Android23中:ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
CalledFromWrongThreadException中的错误提示也说的是原线程才能更新View而不是主线程才能更新View。