最通俗易懂的 Handler 源码解析

简介

在 Android 中 UI 线程是不安全的,若是在子线程中尝试进行更新 UI 操做,程序就有可能会崩溃;固然若是在 UI 线程中作耗时的操做,系统就会弹出 ANR 弹窗提示该程序无响应,十分影响用户体验。html

Android 系统中提供了 Handler,这样咱们就可使用 Handler 在子线程中发送消息来更新 UI;也能够将耗时操做交给子线程处理,等子线程处理完后再使用 Handler 发送消息来回到主线程。java

能够看到 Handler 的主要做用是进行线程间通讯的,本文将从源码的角度分析下 Handler,以便更好的理解 Handler 的工做流程。android

咱们先来回顾下 Handler 经常使用的方式:c++

// 在主线程中建立 Handler 来处理子线程发送的消息
private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
    switch (msg.what) {
      case 0:
        //TODO: 处理消息
        break;
    }
  }
};

// 使用方式一:在子线程中发送消息
new Thread(new Runnable() {
  @Override
  public void run() {
    Message message = new Message();
    message.what = 0;
    message.obj = "测试消息";
    // 子线程中发送消息
    handler.sendMessage(message);
  }
}).start();

// 使用方式二:handler.post()
handler.post(new Runnable() {
  @Override
  public void run() {
    // 运行在子线程中...
  }
});
复制代码

Handler

private Handler handler = new Handler();
复制代码

经过上面示例代码能够看到,在使用 Handler 时首先须要建立 Handler 对象,咱们先来看下 Handler 的构造方法。git

//frameworks/base/core/java/android/os/Handler.java

/* 构造方法一 */
public Handler() {
  this(null, false);
}
/* 构造方法二 */
public Handler(Callback callback) {
  this(callback, false);
}
/* 构造方法三 */
public Handler(Looper looper) {
  this(looper, null, false);
}
/* 构造方法四 */
public Handler(Looper looper, Callback callback) {
  this(looper, callback, false);
}
/* 构造方法五 */
public Handler(boolean async) {
  this(null, async);
}
/* 构造方法六 */
public Handler(Callback callback, boolean async) {
  // ...
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
      "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}
/* 构造方法七 */
public Handler(Looper looper, Callback callback, boolean async) {
  mLooper = looper;
  mQueue = looper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}
复制代码

能够看到 Handler 有不少构造方法,咱们通常经常使用的是「构造方法一」和「构造方法三」。github

咱们在「构造方法六」中能够看到:算法

//frameworks/base/core/java/android/os/Handler.java

/* 构造方法六 */
public Handler(Callback callback, boolean async) {
  // ...
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
      "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}
复制代码

这里调用了 Looper.myLooper() 方法,当 mLooper 为空时会抛出异常,提示咱们须要先调用 Looper.prepare() 方法,我接下来看下 Looper 中的这两个方法。编程

Looper

//frameworks/base/core/java/android/os/Looper.java

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;

final MessageQueue mQueue;
final Thread mThread;
复制代码

从上面源码中能够看到 Looper 有 4 个成员变量:segmentfault

  • sThreadLocal:保存的是当前线程的 Looper。
  • sMainLooper:Application 中主线程中的 Looper。
  • mQueue:当前线程中的 MessageQueue。
  • mThread:建立 Looper 的线程。

myLoop()

//frameworks/base/core/java/android/os/Looper.java

/* Handler 构造方法六中调用的方法 */
public static Looper myLooper() {
  // 返回当前线程中的 looper
  return sThreadLocal.get();
}
复制代码

能够看到 myLooper() 逻辑很简单,调用了 ThreadLocal 的 get() 方法。ThreadLocal 咱们稍后再分析。数组

prepare()

在 Handler 构造方法六中能够看到,若是 myLoop() 的结果为空会直接抛出异常,提示须要先调用 prepare() 方法,接下来分析下 prepare() 方法。

//frameworks/base/core/java/android/os/Looper.java

/* Handler 构造方法六中调用的方法 */
public static void prepare() {
  prepare(true);
}
/* 带参数的 prepare 方法 */
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));
}
/* Looper 构造方法 */
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
复制代码

prepare() 方法中调用了 prepare(quitAllowed) 方法,这里判断了 Looper 是否为空。

若是当前线程已经建立了 Looper 直接抛出异常,也就是说一个线程中只能建立一个 Looper,常用 Handler 的小伙伴应该对这个异常很熟悉。

若是当前线程没有建立 Looper 会直接调用 Looper(quitAllowed) 的构造方法,建立一个 Looper 并建立一个 MessageQueue,而后保存一下当前线程的信息。

MessageQueue

接下来咱们分析下 MessageQueue 的具体实现。

//frameworks/base/core/java/android/os/Looper.java

final MessageQueue mQueue;

/* Looper 构造方法 */
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
复制代码

咱们看下 MessageQueue 的构造方法:

//frameworks/base/core/java/android/os/MessageQueue.java

private native static long nativeInit();

MessageQueue(boolean quitAllowed) {
  mQuitAllowed = quitAllowed;
  mPtr = nativeInit();
}
复制代码

MessageQueue 的构造方法逻辑仍是很简单的。这里调用了一个 native 方法 nativeInit() 在 native 层进行了初始化,感兴趣的能够去查看 native 源码,文件以下:

//frameworks/base/core/jni/android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
  NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
  if (!nativeMessageQueue) {
    jniThrowRuntimeException(env, "Unable to allocate native queue");
    return 0;
  }

  nativeMessageQueue->incStrong(env);
  return reinterpret_cast<jlong>(nativeMessageQueue);
}
复制代码

分析到这里 Handler 的建立流程已经分析完了,目前能够看到 Handler 建立时建立了以下内容:

Handler 建立过程

如图所示,在建立 Handler 以前须要先调用 Looper.prepare(),该方法会初始化 Looper,建立 MessageQueue 和 ThreadLocal。

第二步当咱们建立 Handler 时会调用 Looper 中的 myLoop() 方法获取到 Looper 和 MessageQueue 保存到 Handler 中。

ThreadLocal

咱们如今来分析下 ThreadLocal 的做用。

ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal.set(new Looper(quitAllowed)); // 设置变量信息
sThreadLocal.get(); // 读取变量信息
复制代码

ThreadLocal 提供了线程本地变量,它能够保证访问到的变量属于当前线程,每一个线程都保存有一个变量副本,每一个线程的变量都不一样,而同一个线程在任什么时候候访问这个本地变量的结果都是一致的。

ThreadLocal 至关于提供了一种线程隔离,将变量与线程相绑定。而当线程结束生命周期时,全部的线程本地实例都会被 GC 回收掉。一般 ThreadLocal 定义为 private static 类型。

nextHashCode()

接下来分析下 ThreadLocal 的具体实现。

//java/lang/ThreadLocal.java

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode =
    new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
复制代码

ThreadLocal 经过 threadLocalHashCode 来标识每个 ThreadLocal 的惟一性。threadLocalHashCode 经过 CAS 操做进行更新,每次 hash 操做的增量为 0x61c88647。

set()

咱们来看看 ThreadLocal 的 set() 方法。

//java/lang/ThreadLocal.java

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码

能够看到经过 Thread.currentThread() 方法获取了当前的线程引用,并传给了 getMap(Thread) 方法获取一个 ThreadLocalMap 的实例。

getMap(Thread) 方法中直接返回 Thread 实例的成员变量 threadLocals。它的定义在 Thread 内部,访问级别为 package 级别:

//java/lang/Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码

到了这里,能够看出,每一个 Thread 里面都有一个 ThreadLocal.ThreadLocalMap 成员变量,也就是说每一个线程经过 ThreadLocal.ThreadLocalMapThreadLocal 相绑定,这样能够确保每一个线程访问到的 ThreadLocal 变量都是本线程的。

咱们往下继续分析。获取了 ThreadLocalMap 实例之后,若是它不为空则调用 ThreadLocalMap.ThreadLocalMap.set() 方法设值;若为空则调用 ThreadLocal.createMap() 方法 new 一个 ThreadLocalMap 实例并赋给 Thread.threadLocals。

ThreadLocalMap

下面咱们分析一下 ThreadLocalMap 的实现,能够看到 ThreadLocalMap 有一个常量和三个成员变量:

//java/lang/ThreadLocal.ThreadLocalMap

private static final int INITIAL_CAPACITY = 16;

private Entry[] table;

private int size = 0;

private int threshold; // Default to 0
复制代码

其中 INITIAL_CAPACITY 表明这个 Map 的初始容量;table 是一个 Entry 类型的数组,用于存储数据;size 表明表中的存储数目; threshold 表明须要扩容时对应 size 的阈值。

Entry 类是 ThreadLocalMap 的静态内部类,用于存储数据。它的源码以下:

//java/lang/ThreadLocal.ThreadLocalMap

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
复制代码

Entry 类继承了 WeakReference<ThreadLocal<?>>,即每一个 Entry 对象都有一个 ThreadLocal 的弱引用(做为 key),这是为了防止内存泄露。一旦线程结束,key 变为一个不可达的对象,这个 Entry 就能够被 GC 回收了。

ThreadLocalMap 类有两个构造函数,其中经常使用的是 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue):

//java/lang/ThreadLocal.ThreadLocalMap

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
复制代码

构造函数的第一个参数就是本 ThreadLocal 实例(this),第二个参数就是要保存的线程本地变量。构造函数首先建立一个长度为 16 的 Entry 数组,而后计算出 firstKey 对应的哈希值,而后存储到 table 中,并设置 size 和 threshold。

注意一个细节,计算 hash 的时候里面采用了 hashCode & (size - 1) 的算法,这至关于取模运算 hashCode % size 的一个更高效的实现(与 HashMap 中的思路相同)。正是由于这种算法,咱们要求 size 必须是 2 的指数,由于这可使得 hash 发生冲突的次数减少。

  • set()

接下来咱们来看 ThreadLocalMap.set() 方法的实现:

//java/lang/ThreadLocal.ThreadLocalMap

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
复制代码

若是冲突了,就会经过 nextIndex 方法再次计算哈希值:

//java/lang/ThreadLocal.ThreadLocalMap

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}
复制代码

到这里,咱们看到 ThreadLocalMap 解决冲突的方法是 线性探测法(不断加 1),而不是 HashMap 的 链地址法,这一点也能从 Entry 的结构上推断出来。

  • getEntry()

咱们继续看 ThreadLocalMap.getEntry() 的源码:

//java/lang/ThreadLocal.ThreadLocalMap

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
复制代码

逻辑很简单,hash 之后若是是 ThreadLocal 对应的 Entry 就返回,不然调用 getEntryAfterMiss 方法,根据线性探测法继续查找,直到找到或对应 entry 为 null,并返回。

因为篇幅有限,更多细节不是本文讨论的重点,感兴趣的小伙伴能够去查看源码。

经过上面分析能够看到 ThreadLocal 的工做原理以下:

ThreadLocal 工做原理

如图所示,ThreadLocal 中有一个 ThreadLocalMap 其中以 ThreadLocal 做为 Key,以须要保存的值做为 Value。这样不一样的线程访问同一个 ThreadLocal 时,获取到的值也就是各个线程存储时对应的值了。

ActivityThread

咱们已经分析了 Handler 的建立流程,也就下面代码执行的过程:

private Handler handler = new Handler();
复制代码

在 Handler 的构造方法中能够看到:

//frameworks/base/core/java/android/os/Handler.java

/* 构造方法六 */
public Handler(Callback callback, boolean async) {
  // ...
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
      "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}
复制代码

若是 Looper.myLooper() 获取到的 Looper 为空就直接抛出异常了,可是咱们在 Activity 中建立 Handler 时并不会抛出异常。

这是由于 Activity 在建立过程当中已经调用了 Looper.prepareMainLooper() 源码以下:

//frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
  SamplingProfilerIntegration.start();
  CloseGuard.setEnabled(false);
  Environment.initForCurrentUser();

  final File configDir = 
    Environment.getUserConfigDirectory(UserHandle.myUserId());
  TrustedCertificateStore.setDefaultUserDirectory(configDir);
  
  // 这里调用了 prepareMainLooper() 方法
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  
  // 而后调用了 loop() 方法
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码

咱们来看下 Looper.prepareMainLooper() 方法的具体实现。

Looper.prepareMainLooper()

在 Looper 类中还能够看到一个 prepareMainLooper() 方法。

//frameworks/base/core/java/android/os/Looper.java

/* 初始化一个 main looper */
public static void prepareMainLooper() {
  prepare(false);
  synchronized (Looper.class) {
    if (sMainLooper != null) {
      throw new IllegalStateException("The main Looper has already been prepared.");
    }
    sMainLooper = myLooper();
  }
}
/* 返回 main looper */
public static Looper getMainLooper() {
  synchronized (Looper.class) {
    return sMainLooper;
  }
}
复制代码

能够看到 prepareMainLooper() 方法中首先调用了 prepare(false) 建立了一个不能够退出的 Looper,而后检查 MainLooper 是否已经建立,最后保存了一下 MainLooper 的引用。原来 prepareMainLooper() 中已经调用了 prepare() 方法。

Looper.loop()

继续分析 Looper.loop() 方法。

//frameworks/base/core/java/android/os/Looper.java

public static void loop() {
  // 从 ThreadLocal 中取出当前线程的 Looper 对象
  final Looper me = myLooper();
  if (me == null) {
    // Looper 没有调用 Looper.prepare() 进行初始化,抛出异常
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  // 从 Looper 对象中取出消息队列
  final MessageQueue queue = me.mQueue;
  // ...
  for (;;) { // 死循环
    // 不断的取出消息
    Message msg = queue.next();
    if (msg == null) { // 没有消息直接返回
      return;
    }
    // ...
    try {
      // 取到消息,回调到 Handler 中的 dispatchMessage()
      msg.target.dispatchMessage(msg);
    } finally {
      // ...
    }
    // ...
    // 消息已经分发,进行回收操做
    msg.recycleUnchecked();
  }
}
复制代码

能够看到 Looper.loop() 就是不断的从 MessageQueue 中取出消息,而后回调到 Handler.dispatchMessage() 来处理消息。

//frameworks/base/core/java/android/os/Handler.java

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg); // 处理 post 消息,稍后再分析
  } else {
    if (mCallback != null) {
      // 回调到 Handler.handleMessage() 方法
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
复制代码

能够看到,最后回调到咱们最开始建立的 Handler 中了。

private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
    switch (msg.what) {
      case 0:
        //TODO: 处理消息
        break;
    }
  }
};
复制代码

分析到这里能够看到 Handler 的大概工做原理以下:

Handler 基本流程

如图所示,能够看到咱们以前建立 Handler 以前其实已经作了两步,第一步调用 Looper.prepare() 方法,建立 Looper 同时建立 MessageQueue 和 ThreadLocal。第二步调用 Looper.loop() 方法,不断地读取 MessageQueue 中的消息。第三步建立 Handler,Handler 的做用就是向 MessageQueue 中放入消息。

Handler.sendMessage()

咱们经常使用的发消息的方法以下:

//frameworks/base/core/java/android/os/Handler.java

public final boolean sendMessage(Message msg) {
  return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
  return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
  Message msg = Message.obtain();
  msg.what = what;
  return sendMessageDelayed(msg, delayMillis);
}
复制代码

能够看到上面无论哪一种发消息的方式,最后都调用了 sendMessageDelayed() 方法。

//frameworks/base/core/java/android/os/Handler.java

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
  if (delayMillis < 0) {
    delayMillis = 0;
  }
  return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  if (queue == null) {
    RuntimeException e = new RuntimeException(
      this + " sendMessageAtTime() called with no mQueue");
    Log.w("Looper", e.getMessage(), e);
    return false;
  }
  return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  msg.target = this;
  if (mAsynchronous) {
    msg.setAsynchronous(true);
  }
  return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

sendMessageDelayed() 方法最后调用了 MessageQueue.enqueueMessage()

MessageQueue.enqueueMessage()

咱们接着来看 enqueueMessage() 方法的实现:

//frameworks/base/core/java/android/os/MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
  if (msg.target == null) {
    throw new IllegalArgumentException("Message must have a target.");
  }
  if (msg.isInUse()) {
    throw new IllegalStateException(msg + " This message is already in use.");
  }

  synchronized (this) {
    if (mQuitting) {
      IllegalStateException e = new IllegalStateException(
        msg.target + " sending message to a Handler on a dead thread");
      Log.w(TAG, e.getMessage(), e);
      msg.recycle();
      return false;
    }

    msg.markInUse();
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) {
      // 若是消息队列里面没有消息,或者消息的执行时间比里面的消息早,
      // 就把这条消息设置成第一条消息;
			// 通常不会出现这种状况,由于系统必定会有不少消息。
      msg.next = p;
      mMessages = msg;
      needWake = mBlocked;
    } else {
      // 若是消息队列里面有消息
      needWake = mBlocked && p.target == null && msg.isAsynchronous();
      Message prev;
      for (;;) { // 循环找到消息队列里面的最后一条消息
        prev = p;
        p = p.next;
        if (p == null || when < p.when) {
          break;
        }
        if (needWake && p.isAsynchronous()) {
          needWake = false;
        }
      }
      msg.next = p; // invariant: p == prev.next
      prev.next = msg; // 把消息添加到最后
    }

    if (needWake) {
      nativeWake(mPtr);
    }
  }
  return true;
}
复制代码

分析到这里能够看到,咱们经过调用 Handler.sendMessage() 最后将 Message 添加到了 MessageQueue 的消息队列中。

在前面 Looper.loop() 方法中分析过,loop() 方法中有一个死循环一直在读取消息,当读取到刚才添加的消息后会回调到 Handler.dispatchMessage() 方法。

到这里 Handler 的工做流程你们应该已经很清楚了,以下图所示:

Handler 工做流程

假设在 Thread 1 中建立了 Handler,那么 Thread 2 向 Thread 1 发送消息的过程如上图所示。Handler 机制就像是一个传送机器,Looper 就是传送轮一直在不停的旋转,MessageQueue 就是传送带跟着Looper 旋转来运输 Message,Handler 就是机械手在 Thread 2 中将 Message 放到传送带 MessageQueue 上,传送到 Thread 1 后再将 Message 拿下来通知 Thread 1 进行处理。

Handler.post()

了解了 Handler 工做流程,咱们继续来分析下另外一种使用方式 Handler.post()

//frameworks/base/core/java/android/os/Handler.java

public final boolean post(Runnable r) {
  return  sendMessageDelayed(getPostMessage(r), 0);
}
复制代码

能够看到 post() 也是调用了 sendMessageDelayed() 方法,上面已经分析过了,这里再也不赘述。咱们来看下 getPostMessage(r) 方法的实现。

//frameworks/base/core/java/android/os/Handler.java

private static Message getPostMessage(Runnable r) {
  Message m = Message.obtain();
  m.callback = r;
  return m;
}
复制代码

原来这里建立了一个 Message,将 Runnable 放入了 Message 的 callback 上。

那 Message 最后怎么处理的呢?

在分析中 Looper.loop() 方法中有这么一句 msg.target.dispatchMessage(msg);

//frameworks/base/core/java/android/os/Handler.java

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg); // 处理 post 消息,稍后再分析
  } else {
    if (mCallback != null) {
      // 回调到 Handler.handleMessage() 方法
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
复制代码

handleCallback() 就是处理 Handler.post() 发送的消息的。咱们接着看,见证奇迹的时刻。

//frameworks/base/core/java/android/os/Handler.java

private static void handleCallback(Message message) {
  message.callback.run();
}
复制代码

如此简单,就是拿到 Runnable 调用了 run() 方法。

Looper 中死循环为何不会致使应用卡死?

这个问题涉及到线程,先来讲下进程与线程相关知识。

进程

首先每一个 App 都是运行在进程中的,进程由 Zygote 进程 fork 出来,进程承载了 App 上运行的个各类组件,如:Activity、Service 等。进程对于上层应用是彻底透明的,大多数状况下一个 App 运行在一个进程中,其余状况暂不讨论。

线程

线程在应用中十分常见,好比下面代码:

new Thread(new Runnable() {
  
  @Override
  public void run() {
  
  }
}).start();
复制代码

每次执行上面代码都会建立一个线程。线程与当前 App 进程之间共享资源,从 Linux 系统角度来讲进程与线程除了是否共享资源外,并无本质的区别,都是一个 task_struct 结构体,在CPU看来进程或线程无非就是一段可执行的代码。

CPU 采用 CFS 调度算法,保证每一个 task 都尽量公平的享有 CPU 时间片。

死循环

对于线程来讲,既然是一段可执行的代码,当可执行的代码执行完后,线程的生命周期就该终止了,线程也就退出。

而对于主线程,咱们是毫不但愿运行一段时间本身就退出的。

那么如何保证能一直存活呢?简单的作法就是让可执行的代码一直执行下去,死循环就能够保证不被退出。例如:loop() 方法中就是采用 for(;;) 死循环的方式。固然这里并不是简单的死循环,无消息时会休眠。

真正卡死的主线程的操做,是在生命周期回调方法 onCreate()、onStart()、onResume() 等中操做时间过长,会致使 UI 渲染掉帧,甚至 ANR。

loop()

若是仅仅使用死循环会一直占用 CPU,致使 CPU 一直处于工做状态。即便不会形成应用卡死,也会十分耗电。而事实上 loop() 中的死循环在没有消息的状况下是处于休眠状态的,并无一直在运行。

//frameworks/base/core/java/android/os/Looper.java

public static void loop() {
  // ...
  for (;;) { // 死循环
    // 不断的取出消息
    Message msg = queue.next();
    // ...
  }
}
复制代码

在 loop() 方法中调用了 MessageQueue.next() 方法,咱们来看下这个方法的具体实现:

//frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
  // ...
  for (;;) {
    // ...
    nativePollOnce(ptr, nextPollTimeoutMillis);
    // ...
  }
}
复制代码

MessageQueue.next() 方法调用了 native 方法 nativePollOnce(),此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生时唤醒主线程。

原来这里采用的是 epoll 机制,消息到达时经过往 pipe 管道写端写入数据来唤醒主线程工做。

任务切换

在介绍 epoll 机制以前先来了解下任务切换,操做系统为了支持多任务,实现了进程调度的功能,会把进程分为「运行」和「等待」等几种状态。

运行状态是进程得到 CPU 使用权,正在执行代码的状态;等待状态是阻塞状态,进程会释放 CPU 使用权,程序会从运行状态变为等待状态,等接收到数据后变回运行状态从新得到 CPU 使用权。

操做系统会分时执行各个运行状态的进程,因为速度很快,看上去就像是同时执行多个任务。

任务切换

如上图,系统内核空间有两个队列,一个是运行队列,一个是等待队列。运行队列存放的是正在执行的进程,等待队列存放的是正在阻塞的进程。当接收到数据时,系统内核会唤醒等待队列中须要执行的进程,将该进程移到运行队列中;同理,当运行中的进程阻塞时,系统内核也会将进程移到等待队列中。

从历史发展角度看,必然会先出现一种不过高效的方法,人们再加以改进,最后留下来的才是最优的方法。只有先理解了不过高效的方法,才可以理解 epoll 的本质。

select 机制

select 机制的设计思路很简单,假设进程 A 中同时监听 socket 1 和 socket 2,那么在调用 select 以后,操做系统会把进程 A 分别加入这两个 socket 的等待队列中。

select 机制

当任何一个 socket 收到数据后,中断程序将唤醒进程 A。将进程 A 从等待队列中移除,加入到工做队列中。当进程 A 被唤醒后,它知道至少有一个 socket 接收了数据。只须要遍历一遍 socket 列表,就能够获得就绪的 socket。

select 机制的缺点就是,每次唤醒进程都须要遍历一遍等待队列才能找到须要唤醒的进程,找到唤醒的进程后还须要遍历一遍 socket 列表才能找到就绪的 socket。为了 性能的考虑 Linux 中将 select 最大的监听数量限制为 1024 个,也就是 fd_set 列表的数量 fd_size 最大为 1024。

poll 机制

因为 select 机制的监听数量最大为 1024,poll 机制进行了升级使用 pollfd 替换 fd_set,pollfd 是链表结构这样就没有了数量限制,可是在数量过大后性能仍是会降低。

poll 机制

epoll 机制

epoll 是在 2.6 内核中提出的,是以前的 select 和 poll 的加强版本。相对于 select 和 poll 来讲,epoll 更加灵活,没有描述符限制。

epoll 机制

如图所示,在使用 epoll 后内核中会建立一个 eventpoll 对象,eventpoll 对象中有 rdlist(就绪列表) 和 wq(等待队列)。

假设内核中运行着进程 A 与进程 B,当进程 A 使用 epoll 机制时,会将进程 A 加入到 eventpoll 对象的 wq 等待队列中。当 rdlist 为空时阻塞等待队列中进程 A,当 rdlist 不为空时唤醒等待队列中进程 A。由于有 rdlist 就序列表,进程 A 被唤醒后也能够知道哪些 socket 发生了变化。

参考资料

个人 Github

github.com/jeanboydev/…

个人公众号

欢迎关注个人公众号,分享各类技术干货,各类学习资料,职业发展和行业动态。

Android 波斯湾

技术交流群

欢迎加入技术交流群,来一块儿交流学习。

QQ 技术交流群

QQ 技术交流群
相关文章
相关标签/搜索