Android Looper 源码分析

类简介

默认情况下,线程没有消息循环;要创建一个消息循环,需要在要运行循环的线程中调用 Looper.prepare(),然后调用 Looper.loop() 来处理消息,直到循环被停止。而Looper 类就是用于运行一个消息循环的。
与消息循环的交互通常是通过 Handler 类来完成的。Handler 类允许你将消息和可运行对象(Runnable)发送到与一个线程的消息循环相关联的消息队列中。
该段文本是关于使用 Looper 线程实现的典型示例,通过分离 prepare 和 loop 来创建一个初始的 Handler 与 Looper 进行通信。

  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler(Looper.myLooper()) {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

Looper 类包含了基于 MessageQueue 的事件循环设置和管理所需的代码。影响队列状态的 API 应该在 MessageQueue 或 Handler 上定义,而不是在 Looper 本身上定义。例如,空闲处理程序和同步屏障在队列上定义,而准备线程、循环和退出在 Looper 上定义。
换句话说,Looper 类主要负责管理事件循环的整体过程,包括准备线程、启动循环和退出循环等操作。而与具体的消息处理、延时任务等相关的操作则应该在 MessageQueue 或 Handler 类上定义,因为它们更直接地与队列和消息处理相关。
通过这种设计,可以将职责划分清楚,使代码更加模块化和可维护。Looper 类专注于事件循环的管理,而 MessageQueue 和 Handler 类则负责具体的消息处理和调度。

变量

//一个线程对应一个Looper对象,sThreadLocal 用来保存各个线程中的Looper对象。
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//sMainLooper用来存储主线程的Looper对象
@UnsupportedAppUsage
private static Looper sMainLooper;  // guarded by Looper.class
//sObserver是Message分发的观察者,在消息dispatch前后以及异常时会触发相关的回调。
private static Observer sObserver;
//mQueue是一个消息队列。一个线程对应一个Looper对象,一个Looper对象对应一个mQueue消息队列。
@UnsupportedAppUsage
final MessageQueue mQueue;
//mThread代表Looper对象所处的线程。
final Thread mThread;
//mInLoop用来记录是否已经执行了消息循环Looper.loop。
private boolean mInLoop;
//mLogging也是一个观察者,与observer不同的是,它是在消息的开始和消息的结束打印相关日志。
@UnsupportedAppUsage
private Printer mLogging;
//mTraceTag是Trace的TAG.
private long mTraceTag;
//消息dispatch的阈值
private long mSlowDispatchThresholdMs;
//dispatch - post称之为delivery,即delivery的阈值
private long mSlowDeliveryThresholdMs;
//是否delivery超过了阈值
private boolean mSlowDeliveryDetected;

方法

prepare()和prepare(boolean quitAllowed)

初始化当前线程的Looper对象,prepare参数为true

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

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

  1. 创建MessageQueue对象,可以看到一个线程创建一个Looper,一个Looper创建一个MessageQueue,这里用mQueue表示。
  2. 获取当前线程的对象,赋值给mThread。
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

prepareMainLooper()

  1. 调用prepare方法,参数为false
  2. 获取当前线程的Looper对象。该方法只会在主线程调用,所以该Looper对象为主线程的Looper对象,因此赋值给sMainLooper,表明自己是主线程的Looper对象
@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

getMainLooper

获取主线程的Looper

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

setObserver

设置该进程中所有Looper的观察者。注意:是所有Looper,即设置所有线程的Looper的观察者。

/**
 * Set the transaction observer for all Loopers in this process.
 *
 * @hide
 */
public static void setObserver(@Nullable Observer observer) {
    sObserver = observer;
}

loopOnce

该方法代码比较多,分段分析。
该方法有三个参数,分别是调用方法执行所在线程的Looper对象,Binder.clearCallingIdentity()的返回值以及消息dispatch和delivery的阈值。

@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride)

从Looper的MessageQueue中取出下一个要执行的Message,如果已经没有消息执行,直接返回false,并不会打印日志。

Message msg = me.mQueue.next(); // might block
if (msg == null) {
    // No message indicates that the message queue is quitting.
    return false;
}

logging的观察者如果不为空,则输出当前消息的target、callback以及what值。
把成员变量赋值给局部变量,提高访问效率:将Observer的观察者对象赋值给observer

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " "
            + msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

首先获取Looper对象中的mTraceTag、mSlowDispatchThresholdMs以及mSlowDeliveryThresholdMs,然后检查thresholdOverride是否大于0,如果大于0,就用thresholdOverride的阈值替换Looper的mSlowDispatchThresholdMs和mSlowDeliveryThresholdMs的值。
如果slowDeliveryThresholdMs大于0并且msg.when > 0(msg还没有到dispatch分发的时间),那么logSlowDelivery就为true,如果slowDispatchThresholdMs大于0,logSlowDispatch就为true。
logSlowDelivery和logSlowDispatch有一个为true,就需要记录开始时间,如果logSlowDispatch为true,就需要记录结束时间。

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
    slowDispatchThresholdMs = thresholdOverride;
    slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

校验traceTag,并开始记录Trace

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

如果需要获取开始时间,就获取下目前的开机时间长。

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
  1. 所谓消息的分发,即dispatch,指的是调用消息的target Handler的dispatchMessage方法。
  2. 当消息的分发开始和结束以及异常时,执行observer的回调。
  3. dispatchMessage方法执行完,计算dispatchEnd。
  4. 如果dispatchMessage的过程中有异常,会先捕获,回调observer的异常,然后再throw。
  5. 结束Trace记录。
  6. origWorkSource 以后再说。
Object token = null;
Object msgMonitorToken = null;
if (observer != null) {
    token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
    msg.target.dispatchMessage(msg);
    if (observer != null) {
        observer.messageDispatched(token, msg);
    }
    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
    if (observer != null) {
        observer.dispatchingThrewException(token, msg, exception);
    }
    throw exception;
} finally {
    ThreadLocalWorkSource.restore(origWorkSource);
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

logSlowDelivery意思是是否记录慢速投递,如果需要,那就在此校验是否已经发现了慢速投递的情况。mSlowDeliveryDetected一开始为默认值false,所以此时会执行else逻辑,接着执行showSlowLog方法,showSlowLog方法会去比较dispatchStart和msg.when的差值是否大于阈值slowDeliveryThresholdMs,如果大于,则输出相关msg信息到日志。同时设置me.mSlowDeliveryDetected = true,表示已经发现慢速投递的日志。下个消息处理的时候,如果dispatchStart - msg.when>10,那就不会再次打印日志信息,直到dispatchStart - msg.when) <= 10的时候,再会设置me.mSlowDeliveryDetected = false,这样下个消息处理的时候,又会再次校验是否输出日志。

if (logSlowDelivery) {
    if (me.mSlowDeliveryDetected) {
        if ((dispatchStart - msg.when) <= 10) {
            Slog.w(TAG, "Drained");
            me.mSlowDeliveryDetected = false;
        }
    } else {
        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                msg)) {
            // Once we write a slow delivery log, suppress until the queue drains.
            me.mSlowDeliveryDetected = true;
        }
    }
}
private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
        String what, Message msg) {
    final long actualTime = measureEnd - measureStart;
    if (actualTime < threshold) {
        return false;
    }
    // For slow delivery, the current message isn't really important, but log it anyway.
    Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
            + Thread.currentThread().getName() + " h="
            + msg.target.getClass().getName() + " c=" + msg.callback + " m=" + msg.what);
    return true;
}

logSlowDispatch意思是是否记录慢速分发,如果需要,同样会调用showSlowLog输出相关日志。
logging回调如果不为空,则在消息处理结束后,回调相关信息。

if (logSlowDispatch) {
    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}

if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

这段代码用于确保在消息派发过程中,线程的身份(identity)没有被破坏或更改。
首先,通过调用 Binder.clearCallingIdentity() 方法获取当前线程的身份标识,并将其保存在 newIdent 变量中。
接下来,通过比较 ident 和 newIdent 的值来检查线程的身份是否发生了变化。ident 可能是之前保存的线程身份标识。
如果 ident 和 newIdent 的值不相等,则表示线程的身份发生了改变。这可能是一个异常情况,因为在消息派发过程中,线程的身份不应该发生变化。
在这种情况下,会输出一个严重级别的错误日志,使用 Log.wtf() 方法记录错误信息。错误信息包括线程身份标识从哪个值变为哪个值,以及正在派发消息的目标对象的类名、回调信息和消息的标识。

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
    Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent) + " while dispatching to "
            + msg.target.getClass().getName() + " "
            + msg.callback + " what=" + msg.what);
}

msg分发成功后就进行回收,细节在Message.java源码分析时再说。

msg.recycleUnchecked();

loop

通过loop方法来运行当前线程的消息队列。通过quit方法退出loop,来结束对消息队列中消息的分发。

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */

获取当前线程的Looper对象,标记Looper对象的mInLoop为true,表示已经执行了loop方法,如果再次调用loop方法,会有警告信息:即再次进入循环将导致在当前消息完成之前执行排队的消息。

final Looper me = myLooper();
if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
    Slog.w(TAG, "Loop again would have the queued messages be executed"
            + " before this one completed.");
}

me.mInLoop = true;

希望以本地进程的身份来调用loopOnce方法,并且设计期望方法调用过程中,身份保持不变.
可以使用设置系统属性的方式来修改阈值.
开启无限循环调用loopOnce方法.当loopOnce返回false的时候,停止循环.

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
        SystemProperties.getInt("log.looper."
                + Process.myUid() + "."
                + Thread.currentThread().getName()
                + ".slow", 0);

me.mSlowDeliveryDetected = false;

for (;;) {
    if (!loopOnce(me, ident, thresholdOverride)) {
        return;
    }
}

myLooper

返回当前线程关联的Looper对象

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

myQueue

返回当前线程关联的消息队列对象

/**
 * Return the {@link MessageQueue} object associated with the current
 * thread.  This must be called from a thread running a Looper, or a
 * NullPointerException will be thrown.
 */
public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}

isCurrentThread

返回当前线程是否与Looper关联的线程是一个线程

/**
 * Returns true if the current thread is this looper's thread.
 */
public boolean isCurrentThread() {
    return Thread.currentThread() == mThread;
}

setMessageLogging

设置Printer回调

/**
 * Control logging of messages as they are processed by this Looper.  If
 * enabled, a log message will be written to <var>printer</var>
 * at the beginning and ending of each message dispatch, identifying the
 * target Handler and message contents.
 *
 * @param printer A Printer object that will receive log messages, or
 * null to disable message logging.
 */
public void setMessageLogging(@Nullable Printer printer) {
    mLogging = printer;
}

setTraceTag

设置TraceTag

/** {@hide} */
@UnsupportedAppUsage
public void setTraceTag(long traceTag) {
    mTraceTag = traceTag;
}

setSlowLogThresholdMs

设置消息被分发dispatch(消息被处理的时间)的阈值
设置消息被递送delivered(从send消息到dispatch开始的时间)的阈值

/**
 * Set a thresholds for slow dispatch/delivery log.
 * {@hide}
 */
public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) {
    mSlowDispatchThresholdMs = slowDispatchThresholdMs;
    mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
}

quit

退出loop,即不再循环处理消息队列中的消息.
此时调用sendMessage,会返回false.表示send失败
此方法不是安全的,因为在loop退出之前一些信息可能没有被递送delivered,即如果消息队列中有消息,则不会再继续处理.

/**
 * Quits the looper.
 * <p>
 * Causes the {@link #loop} method to terminate without processing any
 * more messages in the message queue.
 * </p><p>
 * Any attempt to post messages to the queue after the looper is asked to quit will fail.
 * For example, the {@link Handler#sendMessage(Message)} method will return false.
 * </p><p class="note">
 * Using this method may be unsafe because some messages may not be delivered
 * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
 * that all pending work is completed in an orderly manner.
 * </p>
 *
 * @see #quitSafely
 */
public void quit() {
    mQueue.quit(false);
}

quitSafely

此时调用sendMessage,会返回false.表示send失败
处理已到期的消息:所有已经到达处理时机的消息,将在 loop 终止前被处理完毕。
忽略未来的延迟消息:那些设定了未来处理时间的延迟消息不会被处理

/**
 * Quits the looper safely.
 * <p>
 * Causes the {@link #loop} method to terminate as soon as all remaining messages
 * in the message queue that are already due to be delivered have been handled.
 * However pending delayed messages with due times in the future will not be
 * delivered before the loop terminates.
 * </p><p>
 * Any attempt to post messages to the queue after the looper is asked to quit will fail.
 * For example, the {@link Handler#sendMessage(Message)} method will return false.
 * </p>
 */
public void quitSafely() {
    mQueue.quit(true);
}

getThread

获取与Looper关联的线程

/**
 * Gets the Thread associated with this Looper.
 *
 * @return The looper's thread.
 */
public @NonNull Thread getThread() {
    return mThread;
}

getQueue

获取当前Looper关联的消息队列

/**
 * Gets this looper's message queue.
 *
 * @return The looper's message queue.
 */
public @NonNull MessageQueue getQueue() {
    return mQueue;
}

dump

将looper中的状态dump到Printer的println方法中,以及将消息队列mQueue中的内容也回调到Printer的println方法中
mQueue的dump方法的第三个参数为null,表示会dump所有关联的Handler的消息,不为null,就表示只dump与该Handler相关的消息.具体的会在MessageQueue源码分析中讲解.

/**
 * Dumps the state of the looper for debugging purposes.
 *
 * @param pw A printer to receive the contents of the dump.
 * @param prefix A prefix to prepend to each line which is printed.
 */
public void dump(@NonNull Printer pw, @NonNull String prefix) {
    pw.println(prefix + toString());
    mQueue.dump(pw, prefix + "  ", null);
}
/**
 * Dumps the state of the looper for debugging purposes.
 *
 * @param pw A printer to receive the contents of the dump.
 * @param prefix A prefix to prepend to each line which is printed.
 * @param handler Only dump messages for this Handler.
 * @hide
 */
public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
    pw.println(prefix + toString());
    mQueue.dump(pw, prefix + "  ", handler);
}

toString

输出线程名称,线程ID,Looper对象的hashcode.

@Override
public String toString() {
    return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
            + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
}

Observer

定义了Observer接口类,回调方法分别代表消息分发开始,消息分发结束,消息分发异常.

/** {@hide} */
public interface Observer {
    /**
     * Called right before a message is dispatched.
     *
     * <p> The token type is not specified to allow the implementation to specify its own type.
     *
     * @return a token used for collecting telemetry when dispatching a single message.
     *         The token token must be passed back exactly once to either
     *         {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
     *         and must not be reused again.
     *
     */
    Object messageDispatchStarting();

    /**
     * Called when a message was processed by a Handler.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched.
     */
    void messageDispatched(Object token, Message msg);

    /**
     * Called when an exception was thrown while processing a message.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched and caused an exception.
     * @param exception The exception that was thrown.
     */
    void dispatchingThrewException(Object token, Message msg, Exception exception);
}

dumpDebug

将线程名称,线程ID,以及消息队列中的消息写到ProtoOutputStream中

/** @hide */
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
    final long looperToken = proto.start(fieldId);
    proto.write(LooperProto.THREAD_NAME, mThread.getName());
    proto.write(LooperProto.THREAD_ID, mThread.getId());
    if (mQueue != null) {
        mQueue.dumpDebug(proto, LooperProto.QUEUE);
    }
    proto.end(looperToken);
}

Looper和MessageQueue的关系

当Looper创建的时候,就会创建一个MessageQueue,所以Looper和MessageQueue是一一对应的关系。

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

推荐阅读更多精彩内容