Android Framework系列5-3 UI线程

应用的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 sRunQueues = new ThreadLocal();

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在主线程中创建:


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。

你可能感兴趣的:(Android Framework系列5-3 UI线程)