Android Looper 深度解析

01.Activity如何自动绑定Looper

  • 主线程如何自动调用Looper.prepare()

  • ActivityThread,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环。那么这个死循环会不会导致应用卡死,即使不会的话,它会慢慢的消耗越来越多的资源吗?

    • 对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。
    • 例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
    • 可以看到Looper.prepare()方法在这里调用,所以在主线程中可以直接初始化Handler了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //ActivityThread类中的main方法中重点代码
    //注意:这里省略了许多代码
    public static void main(String[] args) {
    ……
    //创建Looper和MessageQueue对象,用于处理主线程的消息
    Looper.prepareMainLooper();
    //创建ActivityThread对象
    ActivityThread thread = new ActivityThread();
    //建立Binder通道 (创建新线程)
    thread.attach(false);
    ……
    //消息循环运行
    Looper.loop();
    //如果能执行下面方法,说明应用崩溃或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  • 并且可以看到还调用了:Looper.loop()方法,可以知道一个Handler的标准写法其实是这样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Looper.prepare();
    Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    if (msg.what == 101) {
    Log.i(TAG, "在子线程中定义Handler,并接收到消息");
    }
    }
    };
    Looper.loop();

02.Looper.prepare()方法源码分析

  • 源码如下所示

    • 可以看到Looper中有一个ThreadLocal成员变量,熟悉JDK的应该知道,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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));
    }

03.Looper.prepare()能否调用多次

  • 思考:Looper.prepare()能否调用两次或者多次

    • 如果运行,则会报错,并提示prepare中的Excetion信息。由此可以得出在每个线程中Looper.prepare()能且只能调用一次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //这里Looper.prepare()方法调用了两次
    Looper.prepare();
    Looper.prepare();
    Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    if (msg.what == 1) {
    Log.i(TAG, "在子线程中定义Handler,并接收到消息。。。");
    }
    }
    };
    Looper.loop();

04.Looper中用什么存储消息

  • 先看一下下面得源代码

    • 看Looper对象的构造方法,可以看到在其构造方法中初始化了一个MessageQueue对象。MessageQueue也称之为消息队列,特点是先进先出,底层实现是单链表数据结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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));
    }

    private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
    }
  • 得出结论

    • Looper.prepare()方法初始话了一个Looper对象并关联在一个MessageQueue对象,并且一个线程中只有一个Looper对象,只有一个MessageQueue对象。

05.Looper.loop()方法源码分析

  • 看看里面得源码,如下所示

    • 看到Looper.loop()方法里起了一个死循环,不断的判断MessageQueue中的消息是否为空,如果为空则直接return掉,然后执行queue.next()方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
    }
    // 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);
    }
    final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    final long traceTag = me.mTraceTag;
    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    }
    final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
    final long end;
    try {
    msg.target.dispatchMessage(msg);
    end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
    } finally {
    if (traceTag != 0) {
    Trace.traceEnd(traceTag);
    }
    }
    if (slowDispatchThresholdMs > 0) {
    final long time = end - start;
    if (time > slowDispatchThresholdMs) {
    Slog.w(TAG, "Dispatch took " + time + "ms on "
    + Thread.currentThread().getName() + ", h=" +
    msg.target + " cb=" + msg.callback + " msg=" + msg.what);
    }
    }
    if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }
    // 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.recycleUnchecked();
    }
    }
  • 看queue.next()方法源码

    • 大概的实现逻辑就是Message的出栈操作,里面可能对线程,并发控制做了一些限制等。获取到栈顶的Message对象之后开始执行:msg.target.dispatchMessage(msg)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
    return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
    if (nextPollTimeoutMillis != 0) {
    Binder.flushPendingCommands();
    }

    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {
    // Try to retrieve the next message. Return if found.
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null && msg.target == null) {
    // Stalled by a barrier. Find the next asynchronous message in the queue.
    do {
    prevMsg = msg;
    msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
    }
    if (msg != null) {
    if (now < msg.when) {
    // Next message is not ready. Set a timeout to wake up when it is ready.
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    } else {
    // Got a message.
    mBlocked = false;
    if (prevMsg != null) {
    prevMsg.next = msg.next;
    } else {
    mMessages = msg.next;
    }
    msg.next = null;
    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    msg.markInUse();
    return msg;
    }
    } else {
    // No more messages.
    nextPollTimeoutMillis = -1;
    }

    // Process the quit message now that all pending messages have been handled.
    if (mQuitting) {
    dispose();
    return null;
    }

    // If first time idle, then get the number of idlers to run.
    // Idle handles only run if the queue is empty or if the first message
    // in the queue (possibly a barrier) is due to be handled in the future.
    if (pendingIdleHandlerCount < 0
    && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
    }
    if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run. Loop and wait some more.
    mBlocked = true;
    continue;
    }

    if (mPendingIdleHandlers == null) {
    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
    }
    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }

    // Run the idle handlers.
    // We only ever reach this code block during the first iteration.
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    mPendingIdleHandlers[i] = null; // release the reference to the handler

    boolean keep = false;
    try {
    keep = idler.queueIdle();
    } catch (Throwable t) {
    Log.wtf(TAG, "IdleHandler threw exception", t);
    }

    if (!keep) {
    synchronized (this) {
    mIdleHandlers.remove(idler);
    }
    }
    }

    // Reset the idle handler count to 0 so we do not run them again.
    pendingIdleHandlerCount = 0;

    // While calling an idle handler, a new message could have been delivered
    // so go back and look again for a pending message without waiting.
    nextPollTimeoutMillis = 0;
    }
    }
  • 那么msg.target是什么呢?通过追踪可以知道就是定义的Handler对象,然后查看一下Handler类的dispatchMessage方法:

    • 可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法
    • 在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    }

    private static void handleCallback(Message message) {
    message.callback.run();
    }

06.Activity生命周期依赖Looper

  • Activity的生命周期都是依靠主线程的Looper.loop

    • 当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出呢。从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //在FragmentActivity中
    @Override
    protected void onPause() {
    super.onPause();
    if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
    mHandler.removeMessages(MSG_RESUME_PENDING);
    }
    }


    @Override
    protected void onResume() {
    super.onResume();
    mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
    }

    @Override
    protected void onStart() {
    super.onStart();
    mHandler.removeMessages(MSG_REALLY_STOPPED);
    }

    @Override
    protected void onStop() {
    super.onStop();
    mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
    }
  • ActivityThread的动力是什么

    • 进程
      • 每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。
    • 线程
      • 线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。
    • 其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。

07.Looper死循环为何不阻塞应用卡死

  • Looper死循环为什么不会导致应用卡死,会消耗大量资源吗?

    • 线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
  • 创建一个子线程,并且死循环,然后做更新UI操作,看看会不会卡死应用

    • 先看看代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    new Thread(new Runnable() {
    @Override
    public void run() {
    Log.e("yc", "yc 0 ");
    Looper.prepare();
    Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();
    Log.e("yc", "yc 1 ");
    Looper.loop();
    Log.e("yc", "yc 2 ");
    }
    }).start();
    • 然后再来看看Looper.loop()源代码,可以直接参考前面分析,Looper.loop();里面维护了一个死循环方法,所以按照理论,上述代码执行的应该是yc 0 –>yc 1也就是说循环在Looper.prepare();与Looper.loop();之间。所以不会卡死应用。
    • 在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待(阻塞)状态,而如果退出Looper以后,这个线程就会立刻(执行所有方法并)终止,因此建议不需要的时候终止Looper。
  • 主线程的死循环一直运行是不是特别消耗CPU资源呢?

    • 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

08.一个Looper只有一个MessageQueue

  • 为什么一个线程只有一个Looper、只有一个MessageQueue,可以有多个Handler?

  • 注意:一个Thread只能有一个Looper,可以有多个Handler

    • Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue。
  • 为什么一个线程只有一个Looper?

    • 需使用Looper的prepare方法,Looper.prepare()。可以看下源代码,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。
    • 所以一个线程只有一个Looper
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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));
    }