android 深入理解Handler机制

389 阅读8分钟

Android的消息机制

提到消息机制大家应该会先想到Handler。对于Android开发者来说想必Handler都并不陌生。

Handler的定义是一个Handler允许你发送和处理跟线程对象关联的消息和Runnable对象。而每个Handler实例关联一个线程和该线程所属的消息队列。通过他可以轻松地将一个任务切换到Handler所在的线程中执行。

我们大多数人使用Handler,常用于在子线程上将更新到UI的操作切换到主线程执行。像如下例子。但本质上说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。那首先一个疑问是Handler是如何实现在子线程上将更新UI的操作切换到主线程执行呢。针对第一个疑问,我们再把问题扩大下,为什么我们的应用程序在打开的时候,没有遇到程序主线程执行完毕然后直接退出的情况,它是如何实现的。看完这篇文章或许你就会有答案。

public class MainActivity extends AppCompatActivity {

    TextView tv;

    class MHandler extends Handler {
        @Override
        public void handleMessage(@NonNull @NotNull Message msg) {
            super.handleMessage(msg);
            tv.setText(msg.obj.toString());
        }
    }

    MHandler mHandler = new MHandler();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        Message message  = new Message();
        message.obj = new String("message");
        new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendMessage(message);
            }
        }).start();
    }
}

Handler,MessageQueue,Looper,Message

说到Handler机制,那就必然会涉及到MessageQueue,Looper,Message这几个类。

首先看如下图,观察他们之间的依赖关系。

我们这里先大致概括下这几个类的作用。

  • Message 用于传递消息的消息类。他是一个单链表结构。

  • MessageQueue 消息队列 他是一个消息Message的管理类,它持有Message 对象,常用于插入消息和取消息。

  • Looper 用于在一个线程中死循环处理消息。

Message

首先我们来看Message类 我们首先看一个Message对象可以携带的参数,如下

public final class Message implements Parcelable {
 
// 用户定义的消息码,以便接收者能够识别消息是什么类型
public int what;
 
// arg1和arg2是消息可选int类型参数
public int arg1;
public int arg2;
 
// 存放需要传递的任意对象
public Object obj;
 
// 消息处理的时间
long when;
 
// 消息携带的bundle类型数据
Bundle data;
 
// 处理消息的handler
Handler target;
 
// 回调方法
Runnable callback;
 
// 指向消息的下一个消息。通过此变量实现链表。
Message next;
 
....

MessageQueue 消息队列的工作原理

MessageQueue持有Message对象,它是管理Message单链表结构对象。 我们先看下MessageQueue类的几个方法。

image.png

boolean enqueueMessage(Message msg, long when) {
    // 从这里可以看出msg.target得不能为null
    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) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 这里的代码块完成消息的插入操作。
            // 首先他将要找到合适插入位置。消息的链表是按when触发时间从小到大排序。也就是
            // 咱们要从当前全局变量是mMessages中找到合适的位置。首先Message是个链表结构,
            // 所以通过next方法可以获取下一个消息的值,prev代表前面的消息,p代表当前消息。
            // 所以这里通过循环执行next方法来更新prev和p的值,然后通过判断语句判断要插入
            // 的消息是否小于当前消息的触发时间来终止循环,这代表找到合适的插入位置。
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

然后我们再看下next方法,next方法是从消息队列从取出满足现在触发时间的消息。下面的代码很长,我们先挑重点看。注意 synchronized (this){...}这里的代码块的注释。

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) {
                // 如果当前时间还没到达当前消息触发的时间点when,则设置nextPollTimeoutMillis值,
                // 该值代表下一次轮询需要执行获取消息超时的时间。也就是距离当前时间多久到达当前消息
                // 触发的时间。然后会在下一次for循环的时候执行
                // nativePollOnce(ptr, nextPollTimeoutMillis);(在synchronized (this)前面的位置)
                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 {
                    // 如果当前时间到达当前消息的触发时间,则获取消息并返回。
                    mBlocked = false;
                    // 这里涉及到同步障碍的情况才导致prevMesg 可能并不为null。同步障碍不是本篇文章重点,
                    // 暂不讨论,之后有机会我会出篇文章单独讨论。
                    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;
            }
               ....
      }
    }
}

从这两个方法,我们可以看出尽管MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表。

Looper的工作原理

然后我们再看看Looper类 Looper对象的作用就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则一直阻塞在那里。我们首先先看下Looper类,android官方给出的典型的使用例子。

  class LooperThread extends Thread {
      public Handler mHandler;

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

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

          Looper.loop();
      }
  }

我们可以看到Looper使用跟prepare和loop方法有关。我们看下prepare代码,可以发现prepare是一个静态方法,它最终会新建Looper对象给成员变量sThreadLocal。而sThreadLocal是ThreadLocal类型的,它提供线程本地变量。对ThreadLocal不了解的建议先去搜索相关文章了解下。

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));
}
public final class Looper {
  
  // sThreadLocal.get() will return null unless you've called prepare().
  @UnsupportedAppUsage
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  @UnsupportedAppUsage
  private static Looper sMainLooper;  // guarded by Looper.class
  private static Observer sObserver;
  
  @UnsupportedAppUsage
  final MessageQueue mQueue;
  final Thread mThread;
  ...
}

我们可以看到构造方法,会初始化Looper类的消息队列常量mQueue和赋值当前线程给mThread。Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要用于给主线程创建Looper使用的。


private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Looper还有一个重要的方法及时loop方法。只有调用了loop后,消息循环系统才开始起作用。这里我省略了部分代码,保留了核心的代码。

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;
     ....

    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);
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;

        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        ...

        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);
            }
        }
                msg.recycleUnchecked();
            }
        }
      }

通过代码观察我们可以看到,loop方法是一个死循环,终止循环的唯一方式是MessageQueue的next方法返回了null。当Looper的quit(消息队列退出方法)方法被调用时,它的next就会返回null。

loop方法会不停地调用MessageQueue的next方法来获取消息新消息。而next方法是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里。如果从MessageQueue的next方法获取了新消息,则处理这条消息:msg.target.dispatchMessage(msg);这里的msg.target是发送了这条消息的Handler对象那个。这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。注意这里不同的是Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定中的线程中执行了。 现在如果看不懂这句话,没关系后面讲解到Handler的工作原理的时候,你就会明白了。这里先有个这个概念。

Handler的工作原理

Handler的主要工作时发送和接收消息。我们常用的发送消息方法就是sendMessage(msg)。对于接收消息我们常常时实现Handler类的handleMessage()方法。handleMessage在Handler类是一个空实现的方法。

# Handler
public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback) {
    this(callback, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
# Looper
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

观察Handler的构造方法,我们可以看到首先会调用Looper类的静态方法myLooper方法获取当前线程的Looper对象并赋值给成员变量mLooper。如果mLooper则带代表当前线程并没有创建Looper对象,也就是当前线程并没有执行Looper.prepare()故抛出异常。如果有则mQueue = mLooper.mQueue;获取消息队列。这里的Callback如下,它也是用于接收处理消息对象的。

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}

目前我们可以得出Handler的创建依赖Looper对象。而Looper则对MessageQueue有依赖。如下图: image.png

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

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

public boolean sendMessageAtTime(@NonNull 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);
}

// 将消息插入操作由queue.enqueueMessage(msg, uptimeMillis)代理完成。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

我们可以看到平常我们使用的sendMessage方法,其实最终会调用MessageQueue的对象enqueueMessage(msg, uptimeMillis)方法往消息队列插入消息。然后该方法又交由queue.enqueueMessage(msg, uptimeMillis)代理完成。我们知道调用enqueueMessage传入的uptimeMillis的参数是设置消息的触发时间也就是msg.when。然后在我们调用sendMessage(msg)的时候,方法内部会调用 sendMessageDelayed(msg, 0);来帮我们计算Message对象的触发时间when。

接下来我们再看下接收消息并处理的方法dispatchMessage(Message msg)。

# Handler
public void dispatchMessage(@NonNull 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();
}

image.png

我们可以看到我们平常继承实现Handler类实现的handleMessage,就是在这一步被回调。同时我们也可以看到实现接收处理消息,还可以对Message对象,指定callback(这里的callback是runnable类型)来实现,如下代码。或者在创建Handler对象的时候,使用Handler(@Nullable Callback callback)这个构造方法创建,传递Callback对象给它。

Message msg = Message.obtain(mHandler, new Runnable() {
    @Override
    public void run() {
        System.out.println("run");
    }
});
new Thread(new Runnable() {
    @Override
    public void run() {
        mHandler.sendMessage(msg);
    }
}).start();

现在我们再回到Handler的构造方法。

public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

主线程的消息循环

我们可以发现如果当前线程没有Looper的话,就会抛出 "Can't create handler inside thread that has not called Looper.prepare()"的异常,这也解释了在没有Looper的子线程创建Handler会引发程序异常的原因。但我们平常在主线程创建Handler的时候,并没有调用过Looper.preare()方法。但是又没有抛出异常。而且我们也没有执行loop方法,但又能正常运转。这又是为啥?

image.png

其实是因为的android的主线程就是ActivityThread帮我们把这个工作做了。主线程的入口方法为main()

# ActivityThread
public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    // Install selective syscall interception
    AndroidOs.install();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

你看方法里执行了Looper.preapareMainLooper()方法来创建主线程的Looper。设置主线程的looper。然后在main最后执行looper的loop方法。

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

大致流程如图: image.png

总结

首先回到开头的疑问是Handler是如何实现在子线程上将更新UI的操作切换到主线程执行呢。其实就是子线程往与主线程共享的消息队列(MessageQueue)插入消息。然后主线程创建Looper对象,然后执行loop方法,不停的循环执行next方法来获取其他线程往消息队列(MessageQueue)插入的消息,然后将消息分发给他对应的handler来处理。

然后第二个问题是为什么我们的应用程序在打开的时候,没有遇到程序主线程执行完毕然后直接退出的情况,它是如何实现的。这是因为主线程不停循环执行某个代码块。而那个代码块,就是我们在主线程创建Looper对象中执行的loop方法。也就是ActivityThread的main方法中...


public static void main(String[] args) {
     ....
     Looper.prepareMainLooper();
     ...
     Looper.loop();
     ...
   }