Android 消息机制之 Looper 深入源码分析 [ 三 ]

Android 消息机制深入源码分析 [ 一 ]
Android 消息机制之 ThreadLocal 深入源码分析 [ 二 ]
Android 消息机制之 Looper 深入源码分析 [ 三 ]
Android 消息机制之 Message 与消息对象池的深入源码分析 [ 四 ]
Android 消息机制之 MessageQueue 深入源码分析 [ 五 ]
Android 消息机制之初识Handler [ 六 ]
Android 消息机制之 Handler 发送消息的深入源码分析 [ 七 ]
Android 消息机制之 MessageQueue.next() 消息取出的深入源码分析 [ 八 ]
Android 消息机制之消息的其他处理深入源码分析 [ 九 ]
Android 消息机制总结 [ 十 ]

上一章学习了消息机制中的 ThreadLocal, 本章接着来学习消息机制中的 Looper. 开篇也是先抛出几个问题.

1. 问题

    1. 可以在一个线程多次执行 Looper. prepare() 吗? 为什么 ?
    1. Looper.prepareMainLooper 是用来做什么的. 为什么我们在主线程可以直接使用 Handler, 而不需要调用 Looper. prepare() ?
    1. Looper.quit 与 Looper.quitSafely() 有什么区别.

2. 例

先来一个典型的关于 Looper 的例子. (因为本章只分析 Looper, Handler 会放到后面章节分析.)

class LooperThread extends Thread{
  public Handler mHandler;
  public void run(){
    //分析1
    Looper.prepare();
    mHandler = new Handler(){
      public void HhandleMessage(Message msg){
        Message msg = Message.obtain();
      }
    };
    //分析 2
    Looper.loop();
  }
}

3. 分析 1

进入 Looper.prepare() 方法. 代码在 Looper.java 97行.

public static void prepare() {
    prepare(true);
}
  • 官方注释:

初始化当前线程和 Looper, 这样可以在实际开始启动循环(Loop()) 之前创建一个 Handler, 并且关联一个 Looper. 要确保最先调用这个方法, 然后才调用 loop(), 并且通过调用 quit 结束.

这也正对应了我们例子中写的, 要先调用 Looper.prepare() , 然后创建一个 Handler, 最后才调用 Looper.loop() 启动循环.

继续回到 public static void prepare(), 在方法内部又调用了一个 private static void prepare(boolean quitAllowed), 注意, 这两个方法名是相同的, 不过一个是 public 无参的, 一个是 private 有参的. 而我们调用的是 public 无参的, 在这个方法内部, Android 系统又调用了 private 有参的, 并且传入的参数为 true, 实际上这个参数一直向下传递到了 MessageQueue 的构造函数中, 表示允许退出消息队列. 也就是说我们在外部调用 Looper.prepare() 后, 系统为我们创建的消息队列是允许退出的.
 

进入到 private static void Looper.prepare(true)
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
  if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
  }
  sThreadLocal.set(new Looper(quitAllowed));
}
  • 解析:

又看到了 ThreadLocal, 所以我们需要先学习了第二章, 再来看这个就会明白是什么意思了. 这个 sThreadLocal 中存储的是 Looper 对象.
调用 ThreadLocal.get() 看获取到的值是否为null, 如果为不为 null 说明当前线程已经创建过 Looper 对象了. 那么就会直接抛出异常. [每个线程只能执行一次 Looper], 那么这就是问题1 的答案,
如果获取到值为 null, 说明当前线程还未创建 Looper, 就会创建一个 Looper 对象, 并放到 ThreadLocal 中.

接着我们进入 Looper 的构造函数, 看看都做了些什么.
 

进入到 Looper(boolean quitAllowed)
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
  • 解析

看到构造函数内部创建了一个 MessageQueue, 传入 boolean quitAllowed 设置是否允许退出, 然后赋值给本地变量 mQueue, 然后获取当前线程, 也赋值给本地变量 mThread.

这样这个创建的 Looper 内就持有 MessageQueue 消息队列 与 Thread 当前线程的引用. 也就实现了 Looper 与 MessageQueue 和 Thread 的关联 ()

通过 Looper 的构造函数可以知道, Looper 是无法被直接创建的, (构造函数是 private 类型的). 我们必须通过 Looper 的两个静态方法, prepare()/prepareMainLooper() 来间接的创建. 那么 prepareMainLooper 是用来做什么的呢, 一起来看一下
 

进入到 prepareMainLooper()

Looper.java 114 行

public static void prepareMainLooper() {
  //设置不允许退出的 Looper
  prepare(false);
  synchronized (Looper.class) {
      //主线程有且只能调用一次 prepareMainLooper()
      if (sMainLooper != null) {
          throw new IllegalStateException("The main Looper has already been prepared.");
      }
      //赋值给 sMainlooper
      sMainLooper = myLooper();
  }
}
  • 官方注释

初始化当前线程的 looper. 并且标记为一个程序的主 Looper, 由 Android 环境来创建应用程序的主 Looper, 因此这个方法不能由自己调用,只能是 Android 自己调用

什么意思呢, 简单来说就是初始化主线程的 Looper, 而且只能由 Android 系统调用.

  • 解析

一开始也调用了 private static void prepare(boolean quitAllowed) 传入了 false, 结合上面的分析, 是创建了一个 Looper 对象, 然后 set 到当前线程的 ThreadLocal中, 并且在 Looper 构造函数中创建的 MessageQueue 是无法退出的.
接着做了 sMainLooper 的非空判断, sMainLooper 看名字就知道这是什么了, 就是主线程的 Looper, 如果非空, 就直接抛出异常, 所以这个 sMainLooper 一开始必须是空, 因为主线程有且只能调用一次 prepareMainLooper(), 如果 sMainLooper 非空, 说明 prepareMainLooper() 已经被系统调用过了. 为空, 就调用 myLooper() 方法, 将返回的 Looper 对象赋值给 sMainLooper. 那么 prepareMainLooper() 是在什么时候被调用的呢, 答案是在 ActivityThread,java 中的 main 方法.

那么问题 2 的答案也有了: prepareMainLooper() 是用来为主线程创建 Looper 的, 系统默认为我们创建好了, 所以我们可以在主线程中直接使用 Handler
 

进入到 myLooper()

Looper.java 的 254行

public static @Nullable Looper myLooper() {
  return sThreadLocal.get();
}
  • 解析

从当前线程的 ThreadLocal 中取出 Looper 对象并返回. 这里的 sThreadLocal.get();prepare(boolean) 方法里面的 sThreadLocal.set(new Looper( quitAllowed))对应的, 一个设置值一个取值.

现在完整的分析 1, 已经分析完了, 主要就是为当前线程创建一个Looper 对象放入到当前线程的 ThreadLocal中, 并同时持有 MessageQueue 对象与当前线程对象. 其中创建的消息队列, 是可以退出的. 也知道了我们为什么可以在主线程直接使用 Handler, 就是因为在ActivityThread.main 方法中调用了 Looper.prepareMainLooper() 方法, 系统已经为主线程创建好了 Looper.
Looper 已经创建好了, 那么接下来就是开始循环了. 接下来开始看分析 2 Looper.loop().

 

4. 分析 2

Looper.java 137 行, 进入 Looper.loop() 方法

public static void loop() {
    //获取当前线程 ThreadLocal 存储的 Looper
    final Looper me = myLooper();
    if (me == null) {
        //没有 Looper 直接抛出异常
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取当前 Looper 持有的消息队列
    final MessageQueue queue = me.mQueue;
    //确保权限检查基于本地进程, 而不是基于最初调用进程  
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //进入 loop 主循环方法
    //一个死循环, 不听的处理消息队列中的消息,消息的获取是通过 MessageQueue 的 next 方法
    for (;;) {
        //可能会阻塞
        Message msg = queue.next();
        //注: 这里的 msg = null, 并不是说没消息了, 而是如果消息队列正在关闭的情况下, 会返回 null.
        if (msg == null) {
            return;
        }
        ...        
        //用于分发消息,调用 Message 的 target 变量(也就是 Handler)的 dispatchMessage方法处理消息
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        ...
        //将 Message 回收标记后放入消息池.
        msg.recycleUnchecked();
    }
}
  • 解析

简单来说就是 Loop() 进入循环模式, 不断的重复下面的操作, 直到没有消息时, 退出.

  1. 读取 MessageQueue 的下一条数据. 并赋值给 Message.
  2. 调用把 Handler.dispatchMessage() 方法把 Message 分发给相对应的 target 也就是 Handler.
  3. 把分发后的 Message 打上标记后放到消息池回收. 以便重复使用.

这里面有几个重要的方法, 后面文章会解释到.

  1. MessageQueue.next 方法,读取下一条 Message, 有阻塞
  2. Message.Handler.dispatchMessage(Message msg) 方法:消息分发
  3. Message.recycleUnchecked() 方法:消息放入消息池

5. Looper.quit 与 Looper.quitSafely 问题 3 的答案.

上面只是开启了循环方法, Looper 也提供了两个退出循环的方法, 分别是 quitquitSafely
我们调用的 Looper 的两个退出循环的方法.

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}

MessageQueue.quit(boolean safe)

void quit(boolean safe) {
  //当 mQuitAllowed 为 false,表示不允许退出,强行调用 quit 会有异常
  //mQuitAllowed 是在 Looper 构造函数里面构造 MessageQueue() 以参数传入的.
  if (!mQuitAllowed) {
      throw new IllegalStateException("Main thread not allowed to quit.");
  }
    //同步代码块
  synchronized (this) {
      //防止多次执行退出操作
      if (mQuitting) {
          return;
      }
      mQuitting = true;
      if (safe) {
          //移除所有尚未触发的所有消息
          removeAllFutureMessagesLocked();
      } else {
          //移除所有消息
          removeAllMessagesLocked();
      }

      // We can assume mPtr != 0 because mQuitting was previously false.
      nativeWake(mPtr);
  }
}
  • quit: 会将消息队列中的所有消息移除 (延迟消息和非延迟消息)
    • Looperquit() 方法内部的本质是调用 MessageQueuequit(boolean safe) 方法. 传入参数是 false.
  • quitSafely: 会将消息队列所有延迟消息移除, 非延迟消息则派发出去让 Handler 处理.
    • LooperquitSafely() 方法内部调用的本质也是 MessageQueuequit(boolean safe) 方法. 只不过传入参数是 true.
  • quitSafely 相比于 quit 方法安全支出在于清空消息之前会派发出去所有的非延迟消息.

到这里. 相信对 Looper 都有了自己的认识, 比如为什么要先调用 Looper.prepare(), 再调用 Looper.loop(), 以及为什么主线程可以直接使用Hander, 而不需要调用 Looper.prepare(). 还有 loop() 循环中是怎么发送消息的. 下一章接着分析消息机制中的 Message.

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