走进源码pipe、epoll,Android面试最常见Handler、Looper、Message问题总结与解答

【转载请注明出处:走进源码,Android面试最常见Handler、Looper、Message问题总结与解答 CSDN 王智博】

今天楼主以面试的角度,走进源码,去探索Handler、Handler、Looper、Message的奥秘。

随着各种网络框架的普及,线程之间的通信再也不需要我们手动的创建Handler,然后handleMessage了,但是当我们想要创建一个主线程延时任务的时候,还是避免不了使用Handler发送一个延时任务,比如下面操作。

如果还没怎么使用过Handler的同学,可以看最简单的Handler、Looper、Message使用指南一(附github源码)

Handler mHandler = new Handler(Looper.getMainLooper());
Handler mHandler2 = new Handler();
mHandler.postDelayed(new Runnable() {
      @Override
      public void run() {
               //延时一秒的任务
      }
  }, 1000);

那么请问1.mHandler和mHandler2的声明区别是什么呢?2.为什么postDelay方法能够实现延时,延时底层是怎么实现的呢?

你看,这是不是我们面试常问的问题吗?

然后我们再看下面的问题,在子线程使用Handler sendMessage发送消息,主线程便能获取数据并且展示。

3.那么为什么Handler发送Message,主线程便能接收到Message并获取数据?4.为什么Handler对象能够子线程和主线程都能访问了?(这是java内存模型的知识,后面我会单独写一篇)

Handler mainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            //主线程处理
            switch (msg.what) {
                case WHAT_MODIFY_TV:
                    tvReciver.setText("接受子线程传递的参数:" + msg.obj);
                    break;
                default:
            }
        }
    };
//子线程发送消息
mainHandler.sendMessage(Message.obtain(mainHandler, WHAT_MODIFY_TV, num++));

问题1 new Handler(Looper.getMainLooper())和new Handler()有区别吗?

我们首先看new Handler(),实际调用Handler(callback,async),可以看到这里会初始化一个Looper,然后判断如果Looper不存在,说明你所在的线程还没有调用Looper的prepare方法。也就是说Looper的prepare方法里面是looper的初始化? 

走进源码pipe、epoll,Android面试最常见Handler、Looper、Message问题总结与解答_第1张图片

我们可以看到Looper的myLooper方法,实际上是获取ThreadLocal的Looper对象

走进源码pipe、epoll,Android面试最常见Handler、Looper、Message问题总结与解答_第2张图片

那么sThreadLocal这个对象又是啥?

static final ThreadLocal sThreadLocal = new ThreadLocal();

实际上是一个ThreadLocal类型的Looper对象,那么它的初始化地方是哪呢?

 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的prepare方法,ThreadLocal的Looper对象就不会创建,所以handler报错说你需要prepare。

所以new handler()和new Handler(Looper.getMainLooper())的区别在于new handler()是自己调用Looper.myLooper()获取到的looper,也就是获取你当前所在线程的Looper,一般我们在主线程new handler()获取到的就是主线程的Looper,也就是主线程的ThreadLocal中get出来的Looper对象。

那么Looper.getMainLooper()为什么就能获取到主线程的Looper,而且就算是在子线程中,我们还是能获取到主线程的Looper。

这里其实还是内存模型的知识,后面会单独介绍下。

首先我们来看下Looper.getMainLooper()方法,可以看到这里是一个类锁,是线程安全的,返回的是sMainLooper这个对象,这是一个静态对象,Looper.getMainLooper是一个静态方法,所以所有的线程都能调用这个方法获取sMainLooper这个对象。

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

那么主线程这个sMainLooper对象又是什么时候初始化的呢? 可以看到是prepareMainLooper这个方法中初始化的,那么我们来看下这个prepareMainLooper的调用时机。

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

其实是在ActivityThread的main主函数里面调用的。

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

小结:

1.new handler()持有的是当前线程的Looper,而new handler(Looper.getMainLooper())持有的是主线程的Looper

2.Looper其实是一个ThreadLocal类型的,myLooper()通过ThreadLocal.get获取当前线程的Looper

3.Looper.getMainLooper()获取的是sMainLooper这个静态对象,这个对象是在ActivityThread主线程的main主函数入口中,调用prepareMainLooper()是初始化的

问题二 为什么Handler对象能够子线程和主线程都能访问了

对象是存在java虚拟机的内存模型中的堆区域,这个区域是所有线程都能够访问的

问题三 为什么Handler发送Message,主线程便能接收到Message并获取数据

 

走进源码pipe、epoll,Android面试最常见Handler、Looper、Message问题总结与解答_第3张图片

我们看上面的图,左边的是主线程,主线程中有一个Looper,Looper里面有一个消息队列MessageQueue。Looper的初始化是在Looper.prepare()里面,然后主线程ActivityThread调用loop()方法,从主线程一直取消息(如果没有消息,就拥塞)。我们看一下loop()方法。精简之后我们可以看到,这是一个for的死循环一直朝队列里面取msg,取出的msg执行target的dispatchMessage方法,其中的target又是什么了?

public static void loop() {
        ...
        for (;;) {
            Message msg = queue.next(); // 可能拥塞
            if (msg == null) {
                // 没有消息的话 就退出 looper.quit就是这个方式
                return;
            }
           ...
            try {
                //msg.target也就是handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
            }
            ...
            //把msg放置到回收池里(队列头部)
            msg.recycleUnchecked();
        }
    }

可以看到其实就是handler,所以就是执行handler的dispatchMessage方法。我们再看一下handler的 dispatchMessage。

Handler target;
public void dispatchMessage(Message msg) {
        //Runnable postRunnable
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //如果handler设置的callback返回true,表示消费了事件,不会调用handleMessage
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

可以看到,如果是消息中设置了callback,则首先执行callback,其实这个callback就是runnnable类型,也就是我们常用的postRunnable,然后是判断Handler的mCallback回调的handleMessage方法的返回值是否为true,true的话表示消息被消费了,否则才调用handler的handleMessage方法。

小结:handler把message发送到looper的messageQueue里面,messageQueue的next方法获取消息,然后调用message的dispatchMessage方法,然后找到其中的handler的handleMessage方法。

思考:messageQueue的next方法是拥塞的,请问没有消息的时候主线程会被卡死吗?

其实比如通过applicationThread这个匿名binder,AMS会调用其schedulePauseActivity方法,这个时候就会向主线程放入处理Activity Pause的消息。

messageQueue的next方法拥塞,又是怎么实现的呢?

Looper.loop -> MessageQueue.nativePollOnce

epoll_create()   epoll_ctl() 注册事件的回调

looper.pollInner() -> epoll_wait() 等待接受事件唤醒的回调

 MessageQueue.enqueueMessage(Message msg, long when)    ->  MessageQueue.nativeWake(long ptr)

问题四 为什么postDelay方法能够实现延时,延时底层是怎么实现的呢?pipe、epoll又是怎么使用的呢?

我们首先看一下postDelay的代码,第4行首先会调用getPostMessage(r),然后第7行从Message缓存池里面obtain一个Message,然后把Runnable赋值到它的callback中,然后在第4行调用sendMessageDelay()方法,可以看到实际上最后调用了第17行的sendMessageAtTime,可以看到这个方法的第二个参数是当前时间(SystemClock.uptimeMillis)加上延时时间(delayMillis)。这里我们简单聊看一下消息的回收池。

// 1.1
public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

// 1.2
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

// 1.3
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        ....
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

可以看到我们通过obtain方法获取sPool静态池子里面的获取链表头的Message 。好了扯远了,我们继续回到sendMessageAtTime方法。

//同步的对象
private static final Object sPoolSync = new Object();
//Message回收池 全局的静态对象  链表
private static Message sPool;

//从全局的池子里面获取一个Message,避免创建Message重新分配内存
public static Message obtain() {
        //线程安全
        synchronized (sPoolSync) {
            if (sPool != null) {
                //取出当前链表头元素 这个就是我们获取的Message
                Message m = sPool;
                //把链表的头结点设置给下一个元素
                sPool = m.next;
                //把message的next指针置空
                m.next = null;
                m.flags = 0; // clear in-use flag
                //池子的数量减1
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ....
        return enqueueMessage(queue, msg, uptimeMillis);
    }

 可以看到第4行调用了MessageQueue的enqueueMessage方法。然后我们再来看下面这样图是不是就更能理解了,Handler的sendMessage方法,实际上最后是调用MessageQueue的enqueueMessage方法。接下来我们分析这个方法。

走进源码pipe、epoll,Android面试最常见Handler、Looper、Message问题总结与解答_第4张图片

step4.1  MessageQueue.enqueueMessage(Message msg, long when)

boolean enqueueMessage(Message msg, long when) {
       ....
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //如果当前事件的时间<队列头的事件时间,说明当前事件优先级更高,把当前事件放在队列头
            if (p == null || when == 0 || when < p.when) {
                //把msg作为新的链表头,如果队列拥塞的话,唤醒它
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //头结点是障碍物并且是异步的
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                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;
            }

            // 需要唤醒事件队列
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

我们可以看到第10行,如果当前的Message的时间<链表头的时间,就把当前Message放到最前面,第24行如果当前的消息遇到了事件比他大的,然后第 32行,就把Message插入到这个消息的前面。总之就是一句话,链表按照when字段排序。

 step4.2  MessageQueue.next()

Message next() {
        ....
        int nextPollTimeoutMillis = 0;
        for (;;) {
           ....
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                if (msg != null) {
                    if (now < msg.when) {
                       //下一个消息还没有准备好,设置一个时间去唤醒它
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        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 {
                    nextPollTimeoutMillis = -1;
                }
        }
    }

第10行我们看到只有msg.when<=now的消息,才会取出来。那么msg的when字段是什么呢?其实就是msg创建的时间+postdelay的时间,所以如果当前时间戳没有到msg预期的postDelay的时间,第12行,会去取msg.when和当前的差值作为nextPollTimeoutMillis的值,然后去调用nativePollOnce(ptr, nextPollTimeoutMillis)。

所以说每个Message会携带一个期望执行时间(when),MessageQueue取消息的时候,会去比较这个期望执行时间(when)是不是已经到了(<=when),如果到了的话,才会去取消息,否则等一段时间去唤醒它。

 

完整Android学习路径 请戳我的Android学习之旅(持续更新中...)

 

你可能感兴趣的:(Android面试,Android)