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


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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容