美文网首页安卓异步处理机制Android知识Android技术知识
Android 学习笔记 Handler机制(源码分析)

Android 学习笔记 Handler机制(源码分析)

作者: maoqitian | 来源:发表于2017-04-14 22:05 被阅读205次

好久没写笔记了,是时候来一发啦。

  • 说起Handler,相信做Android的朋友都应该对它很熟悉,它的本质为Android下的线程间通信的工具。
  • 首先,为什么要有Handler?
    • 我们知道,Handler为线程间的通信工具,为什么线程间需要通信,这是因为在Android系统中,主线程(UI线程)是不能做耗时操作的,则进行耗时操作就要放在子线程中,则子线程做完耗时操作后需要通知主线程我完成了耗时操作,那怎么通知呢,当然就是通过Handler,所以说Handler是线程间通信的工具。
  • 其次,如何使用Handler?
    • 使用Hnadler,这里举例为主线程与子线程通信。首先在需要new 一个Handler对象,重写 handleMessage(Message msg)方法,在该方法中接收到子线程发送来的消息。而开启子线程进行的耗时操作,使用Message对象将需要发送的消息通过Handler发送出去(代码如下)。
 private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //接收子线程发送来的消息
        }
    };

 new Thread(){//开启子线程
            @Override
            public void run() {
                //作为耗时操作,使用Handler发送消息
                Message message=Message.obtain();
                handler.sendMessage(message);

            }
        }.start();
  • 通信的流程:
    子线程发送消息到消息队列(MessageQueue),启动Activity的时候已经自动开启了轮循器,执行的Looper.prepare()和Looper.loop()方法,loop方法执行从消息队列中获取消息,又给到handleMessage方法中进行处理,实现通信。
handler机制示意图.png

相信很多朋友都知道上面所述的流程,但是作为程序猿,只了解表面的操作我认为还是不够的,如果我们能通过解读源码,如果能了解Handler机制的实现原理,这也是提高我们自身的一个过程。接下来,我们就通过阅读Handler的源码来一探究竟。

  • 首先查看Handler源码,要发消息,肯定需要有消息,而获取的消息对象是通过Message.obtain()来获取,而Handler源码中的obtainMessage()方法不管是有参还是无参方法也都是调用了Message.obtain();
Handler中的obtainMessage()方法.png

既然是调用Message.obtain(),我们就去Message类中的obtain()方法,而查看Message中的obtain()方法发现,不管是该方法有多少个参数,最终都是生成的Message对象都是调用无参的构造方法obtain();

Message中的obtain方法(1).png Message中的obtain方法(2).png

所以,最终创建消息的方法为Message中的无参的obtain方法,

创建消息的方法.png

该方法的思路为,保存消息的队列的顺序是使用单链表来维护的,如果消息池里面的第一条消息(sPool)不为空,取出该条消息的数据,然后把第二条消息变成第一条;如果没有,新创建一个;这时候就得到了一个消息。

  • 得到消息,只是第一步,怎么发消息呢,当然是使用Handler来发消息,我们继续看Handler的源码,而Handler不能凭空使用,是需要new出来的,所以我们看Handler的构造方法,而无参构造方法指向有两个参数的构造方法没,所以我们看有两个参数的构造方法;
handler构造方法.png

又图中的红框,我们可以看到,在new Handler()的时候直接得到了Looper对象mLooper,我们知道,每一个线程对应一个Looper,如果是在子线程中new Hnadler(),不创建传入Looper对象,如图中所示肯定会报异常,而我们一般new Handler()都是在主线程中new,而主线程是在什么时候传入的Looper对象呢,接下来我们继续看源码找答案,先看看是如何获取,

获取Looper对象.png

看图中源码,是通过ThreadLocal这个类型的数据来获取的,而ThreadLocal是Java提供给我们的类,他的功能为实现线程中的单例,所以才有我们说的一个线程对应一个Looper,在一个线程中无论你什么时候获取Looper对象都是同一个,不会变;
既然这里是get(),则肯定是又set()的方法,而平时要得到轮循器,一般都是Looper.prepare()方法,我们来看看prepare()方法;

Looper的prepare方法.png

果然,我们看到了set()方法;回到上面的思路,在主线程中是如何传入Looper对象的呢,这时候我们看到Looper中的prepareMainLooper()方法;


获取主线程的Looper对象(1).png

在看Lopper的方法体,可以看出Looper对象和消息队列都是直接new出来的;

Looper构造方法体.png

到这里,Looper对象的来源我们搞清楚了,但是在主线程中new Handler()我们在什么时候调用调用上面提到的prepareMainLooper()方法呢,要找到这个入口我们就需要查看ActivityThread.java的main()方法;

 public static void main(String[] args) {  
       ...
    //创建Looper和MessageQueue
    Looper.prepareMainLooper();
    ...
    //轮询器开始轮询
    Looper.loop();
    ...
    }  

我们可以看到,主线程的入口中保存了主线程的Looper对象和创建了消息队列,接下来开始Looper.loop()方法,该方法是一个死循环,不断的取消息,如果消息队列中没有消息则阻塞,这也就解释了为什么我们Android应用在不进行任何操作的时候也不会挂掉。
Looper.loop()方法的死循环:

while (true) {
    //取出消息队列的消息,可能会阻塞
    Message msg = queue.next(); // might block
    ...
    //解析消息,分发消息
    msg.target.dispatchMessage(msg);
    ...
}

Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两个句柄(引用),一个是读取句柄,一个是写入句柄;主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。

  • 说了这么多,我们还没开始发消息。好吧,接下来开始发送消息,继续看Handler源码,发消息为Handler中的sendMessage()方法,但是看了几个发消息的方法,发现最后他们掉的还是sendMessageAtTime()方法,所以我们看这个方法就可以了,这时候做了两件事,把handler保存到了Message对象中,而Message对象又保存到了MessageQueue中;
sendMessageAtTime方法.png 获取handler和插入消息.png

而核心的还是如何插入消息enqueueMessage()方法;

final boolean enqueueMessage(Message msg, long when) {
    ...
    final boolean needWake;
    synchronized (this) {
       ...
        //对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比
        msg.when = when;

        Message p = mMessages;              
        if (p == null || when == 0 || when < p.when) {
            // 如果消息队列中没有消息,或者当前消息的时候比队列中的消息的时间小,则让当前消息成为队列中的第一条消息
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; // new head, might need to wake up
        } else {        
            // 代码能进入到这里说明消息队列中有消息,且队列中的消息时间比当前消息时间小,说明当前消息不能做为队列中的第一条消息         
            Message prev = null;    // 当前消息要插入到这个prev消息的后面
            // 这个while循环用于找出当前消息(msg)应该插入到消息列表中的哪个消息的后面(应该插入到prev这条消息的后面)
            while (p != null && p.when <= when) {   
                prev = p;
                p = p.next;
            }

            // 下面两行代码
            msg.next = prev.next;
            prev.next = msg;
            needWake = false; // still waiting on head, no need to wake up
        }
    }
    //唤醒主线程
    if (needWake) {
        nativeWake(mPtr);
    }
    return true;
}

具体思路为: 对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比,// 如果消息队列中没有消息,或者当前消息的时候比队列中的消息的时间小,则让当前消息成为队列中的第一条消息;如果消息队列中有消息,且队列中的消息时间比当前消息时间小,说明当前消息不能做为队列中的第一条消息,则通过比较,找到合适的位置插入消息;而如果子线程发送的消息设置了延时,主线程是如何知道的呢,这时候我们看到获取消息队列的消息的这句话Message msg = queue.next();而next方法中执行就是如何取消息的逻辑,所以查看MessageQueue的next()方法源码;

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.
                    // 没有idle handlers 在运行,loop需要等待
                    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.
           // 重置idle handler 的数量为0,这样我们就不会再次运行
            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.
            // 当调用一个空闲的handler时, 一个新的消息可以被分发
            // 因此可以不需要等待,直接查询pending message
            nextPollTimeoutMillis = 0;
        }
    }

通过该方法,所以当我们sendEmptyMessageDelayed发送了消息A,延时为1000ms,这时消息进入队列,触发了nativePollOnce,Looper阻塞,等待下一个消息,或者是Delayed时间结束,自动唤醒; 在当前的前提下我们又发送一个sendEmptyMessage消息B,消息进入队列,但这时A的阻塞时间还没有到,于是把B插入到A的前面,然后调用nativeWake()方法唤醒线程 ,唤醒之后,会重新都取队列,这是B在A前面,有不需要等待,于是直接返回给Looper ,Looper处理完该消息后,会再次调用next()方法,如果发现now大于msg.when则返回A消息,否则计算下一次该等待的时间,也就实现了延时消息处理(@九九叔,谢谢你的提问,这方面问题前面写的时候没说得够清楚)。

  • 在消息队列中插入好了消息,Looper.loop方法中,获取消息,然后分发消息;
//获取消息队列的消息
 Message msg = queue.next(); // might block
 ...
//分发消息,消息由哪个handler对象创建,则由它分发,并由它的handlerMessage处理  
msg.target.dispatchMessage(msg);

Message对象的target属性,用于记录该消息由哪个Handler创建,在obtain方法中赋值,找到Handler中的分发消息的方法,

dispatchMessage和handleMeassage方法.png

Handler中的Callback接口,可通过构造方法或其它方法传入,Message中保存了callback(Runnabe)和target(Handler),也可以调用Message的sendToTarget()方法来发消息,前提是必须已经给Message设置了target对象;而最终handleMeassage()的空实现,只要我们new Handler()的时候覆盖这个方法,系统就会执行我们覆盖的handleMeassage()方法,我们就可以在其中收到消息执行我们需要操作。

最后

通过对Handler源码的分析,一步一步的走下来,我相信我们能够对Android线程间的通信机制Handler有一个全面深入的了解。学习是一个积累的过程,如果有写得不好的地方或者有错,请大家给我指出,大家一起讨论,共同进步。

相关文章

网友评论

    本文标题:Android 学习笔记 Handler机制(源码分析)

    本文链接:https://www.haomeiwen.com/subject/pwowattx.html