Looper 源码分析

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

1. 问题

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

2. 例

先来一个典型的关于 Looper 的例子。

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

public static void prepare() {
    prepare(true);
}

这也正对应了我们例子中写的, 要先调用 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(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()

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()

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.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() 进入循环模式, 不断的重复下面的操作, 直到没有消息时, 退出。
    读取 MessageQueue 的下一条数据. 并赋值给 Message.
    调用把 Handler.dispatchMessage() 方法把 Message 分发给相对应的 target 也就是 Handler.
    把分发后的 Message 打上标记后放到消息池回收. 以便重复使用.

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

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

5.Looper.quit 与 Looper.quitSafely 问题 3 的答案.
上面只是开启了循环方法, Looper 也提供了两个退出循环的方法, 分别是 quit 与 quitSafely
我们调用的 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: 会将消息队列中的所有消息移除 (延迟消息和非延迟消息)
Looper 的 quit() 方法内部的本质是调用 MessageQueue 的quit(boolean safe) 方法. 传入参数是 false.
quitSafely: 会将消息队列所有延迟消息移除, 非延迟消息则派发出去让 Handler 处理.
Looper 的 quitSafely() 方法内部调用的本质也是 MessageQueue 的quit(boolean safe) 方法. 只不过传入参数是 true.
quitSafely 相比于 quit 方法安全支出在于清空消息之前会派发出去所有的非延迟消息.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值