走进Android之Handler 第一小节

Handler是Android开发怎么都绕不过的一个知识点。想在面试中不被问卡壳。我觉得咱们有必要好好去学习它。有没有小伙伴们好奇sendMessage到handleMessage之间究竟经历了哪些过程。不要急。我们这就开始分析。


sendMessage(@NonNull Message msg)

从handler.sendMessage(message);开始,看注释我们就了解了这个函数主要就是就message发送到消息队列中去,如果成功插入了就返回true 失败了就返回false

并且最终由该线程相关的Handler的handleMessage函数中接收。

在方法体中调用了sendMessageDelayed(@NonNull Message msg, long delayMillis)


sendMessageDelayed(@NonNull Message msg, long delayMillis)

嗯,在(当前时间再加上延长时间)之前 将此消息插入到消息队列中,它的位置处于所有将要 处理的消息之后。你将会在该线程所关联上的Handler的hanleMessage函数中接收到该消息。函数体内先是作了一个判断,如果延长的值是个负数,那么就将它置为零。返回值和上一个函数一致,这里就不再赘述了。函数的末尾调用了sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);继续往下看


sendMessageAtTime

函数体中的第一步是将Handler中的变量mQueue赋给新建的局部变量queue,然后是作了个条件判断,如果queue的值为null,就生成个运行时异常,打印log.然后返回false。退出函数

如果queue不为空,那么就会执行enqueueMessage(queue, msg, uptimeMillis);

所以queue到底为不为null,成为了这消息能不能被插入的关键点。queue为不为null又取决于mQueue这个成员变量了,所以我们看下有没有给mQueue初始化。通过查看源码


Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)

我们可以知道mQueue的值在我们实例化Hanlder的子类的时候就已经给它赋值了。给它赋值的对象是looper.mQueue,看了下注释 使用参数中的looper去代替默认的looper,采用callback回调接口来处理消息,也可以设置handler是否为异步。我们看下调用链,是什么时候调用的该三参构造函数。就会发现,使用默认的构造函数会调用二参的构造函数,使用二参的构造函数最终会调用上图的三参函数。我们先看二参构造函数


Handler(@Nullable Callback callback, boolean async)

注释说 使用当前线程的looper 与指定回调接口 并且是否设置handler为异步,handler默认是同步的,除非在构造函数中指定了该参数为异步。那么有没有指定。我们看下默认的构造函数就清楚了

Handler

可以清楚的看到。默认的构造函数中 将调用了二参构造函数,并分别将Callback赋为null  ,async赋为false.这时我们可以确定我们的handler没有使用callback接口,并且是同步的。此时我们再返回到二参构造函数中,我们挑相关的重点看。Hanler中的mLooper是由 Looper.myLooper();函数的返回值赋予的。我们点进函数中,看看干了什么。


myLooper()

注释说。返回当前线程关联的Looper对象 ,如果调用myLooper函数的线程没有与Looper进行关联,那么就返回false.到底返回looper还是返回null。还要接着看 sThreadLocal.get();的返回值


sThreadLocal

注释告诉我们除非你已经调用了 prepare()函数。不然 sThreadLocal.get()将会返回null。

看到这里。我们就需要看看咱们之前有没有调用prepare()函数。我相信各位大概率会说。平常工作中并没有调用到。那肯定是返回null了。如果返回值是null,将会抛出一个运行时异常。消息为 "Can't create handler inside thread " + Thread.currentThread()that has not called Looper.prepare() 意思是。不能在没有调用Looper.prepare()的情况下。在名为Thread.currentThread()的线程中创建handler。looper要是为null了。那么mLooper.mQueue肯定也为null,mQueue为null的话。咱们的消息就无法插入到消息队列中。handleMessage中也接收不到信息了。此时小伙伴们肯定有疑问了。我平时写hanlder的时候。并没写Lopper.prepare()呀。为什么可以使用hanldeMessage去处理发送的消息呢。这个问题你问我就对了。我来告诉你为什么。那是因为你平常中是在主线程中创建的Handler.回头看看。我说的没错吧。这里我们可以反推一下。你可以处理信息。那么必定是将信息插入到了消息队列。能过插入到消息队列,那么消息队列就一定不为null.消息队列不为null,就意味着looper不为null,就意味着Looper.myLooper()不为null,意味着sThreadLocal.get();不为null。意味着我们调用了prepare()。可我们明明没有调用啊。啊啊啊啊。问题出在哪里了呢。别急。要想sThreadLocal.get();不为null。那么我们接着看代码,看看它被实例化后经过了哪些操作。


prepare

啊。对它赋值也是在prepare中进行的。这个函数的意思是如果sThreadLocal.get()不为null,就会抛出一个异常。每个线程只能创建一个Looper.  

如果为null,就会创建一个Looper,并将它存进sThreadLocal中。此时我们换个思路想想。我们既然没有手动进行prepare()的调用,那么会不会有其他地方作了sThreadLocal.set(new Looper(quitAllowed));这个操作。带着这个疑问。我们看下都有谁调用了sThreadLocal.set(new Looper(quitAllowed))。结果发现很失望,只有这一个地方。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));

    }

不过我们再看看,哪些地方调用了prepare(boolean quitAllowed) 。看看会不会有新发现。果然,还真有个地方。那就是这里 public static void prepareMainLooper() {。


prepareMainLooper

通过注释。我们知道了这个函数的功能是给当前线程初始化个Looper,并将它标记为应用程序的主looper。应用程序的主looper由安卓环境进行创建,所以你应该永远也不需要手动调用它。函数体中的内容我们再分析下

sMainLooper不为空的情况下 就抛出一个异常。非法状态异常。主Looper已经准备好了。为空的情况下,就会将sThreadLocal.get()获取到的looper赋值给sMainLooper。

我们现在要弄明白sMainLooper为不为null


sMainLooper

初始值为空,我们再看看是哪里调用了 public static void prepareMainLooper() {

我们发现在ActivityThread的这个类中的main()入口函数这里对prepareMainLooper()进行了调用


main

看到这里我们就豁然开朗了。因为main()中调用了Looper.prepareMainLooper();

prepareMainLooper()中调用了 prepare(false);这样就完成了 在我们没有手动调用prepare函数的情况下去实例化了一个Looper,并将它存储进sTheadLocal中。这样一来我们后续获取到的sTheadLocal.get()就不为null,looper不为null.looper.queue不为null.这样我们同过sendMesage()发送的消息就能够插入到消息队列中去。也会被hanlder中的hanleMessage函数中接收到。我们也就能够在代码中进行我们的业务逻辑处理了。下一节,我们再讲解一下消息是怎么插入到消息队列中去的这个详细过程


prepareMainLooper



prepare

你可能感兴趣的:(走进Android之Handler 第一小节)