从源码看Handler消息机制

handler简介

为什么需要handler
在介绍handler之前还得说说主线程。主线程运行所有的UI界面,设备会将用户所有操作转换为消息并放入消息队列中,然后主线程就位于一个循环中,在消息队列取消息来一个个完成,每条消息处理用时不能超过5s,否则将ANR异常。所以对于超5s的任务都应该在子线程去完成,而子线程又没有办法对UI界面的内容进行操作,不然就报CalledFromWrongThreadException,于是就有了handler消息传递机制罗。那我们就可以在子线程进行耗时操作,然后把界面上需要的数据通过handler发送给主线程,然后主线程接受并处理,这样就可以解决那些问题了。
再总结一下,handler是用来在各个线程之间发送数据,任何线程只要获得另一个线程的handler,就可以通过handler向那个线程发送数据。

常用类

我们先大概了解一下他都设计到哪些常用的类,并且是负责做什么。
- Message:消息。其中包含了消息ID,消息处理对象以及处理的数据等,由Handler处理。
- Handler:处理者。使用sendMessage()方法进行message的发送,最终由handleMessage()方法进行message处理。
- MessageQueue:消息队列。用来存放Handler发送过来的消息,并按照FIFO规则执行。这里的存放只是将Message串联起来的,好让Looper进行抽取。
- Looper:消息泵。使用Loop.loop()不断地从MessageQueue中抽取Message执行。
- Thread:线程。负责整个消息循环的执行场所。
一个线程只会有一个MessageQueue和一个Looper,但可以有多个handler。

Handler主要成员变量

    final Looper mLooper; //若构造方法没传此值,则Handler在哪个线程创建就与该线程的Looper绑定;若传了就是传进来的值
    final MessageQueue mQueue; //mLooper的MessageQueue
    final Callback mCallback; //内部接口,用来处理消息的
    final boolean mAsynchronous;

Loop主要成员变量

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one
 * thread do not affect the other threads. The implementation supports {@code null} values.
 * 这个是ThreadLocal类的官方解释,大意就是它实现一个线程本地存储变量,能让每个线程拥有自己value。所有的线程可以共用同一个  ThreadLocal对象,并且每个线程可以通过这个对象获取自己的value,而且在不影响其它线程的情况下修改value。
 */
//注意到这里是static,则无论有多少Looper对象也只会有一个ThreadLocal对象,就像上面讲的所有线程共用同一个ThreadLocal对象。通过sThreadLocal.get()得到各个线程的Looper对象(对应value),若子线程未调用Looper.prepare()就会Looper对象为空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // 主线程的Looper对象,android自动创建。
final MessageQueue mQueue; //与Looper对象关联的消息队列
final Thread mThread; //与Looper对象关联的线程

Message的主要成员变量

long when; //消息发送的时间,也是根据它排队进入消息队列的,最早排最前
Handler target;//用来绑定发消息的handler对象,确保消息的执行也是此对象
Runnable callback; //若他不为空,此消息分发成功就是处理callback任务,而不会让handler处理了
Message next;//用来实现队列这种链表结构

大体流程

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler() {
                handleMessage(){}
                };
                Looper.loop();
            }
        }) ;
然后在另一个线程里发消息。

详细讲解

  1. 第一步Looper.prepare();
 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));//为每个线程设置自己的Looper对象
    }
 private Looper(boolean quitAllowed) {
        //初始化成员变量
        mQueue = new MessageQueue(quitAllowed);//为每个Looper对象关联一个MessageQueue对象
        mThread = Thread.currentThread();//关联此Looper对象所在线程
    }

其实这个方法就是创建了个Looper对象,给Looper做了一些初始化操作,主要就是将Looper对象和所在线程绑定,初始化自己的MessageQueue。
2. 第二步创建handler对象
他有7个构造方法,我就只写重点并只留构造方法里的几个主要地方吧。

 public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper(); //没指定Looper的情况就会取Handler创建所在线程的这个Looper,在另一个构造方法可以指定Looper,那就绑定给定Looper的所在线程,
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
 //Looper.myLooper()方法
 public static Looper myLooper() {
        return sThreadLocal.get();//sThreadLocal前面有讲,理解成单例吧,它能得到所在线程自己的value
    }

其实这里也就是给Handler对象进行初始化,绑定相应的Looper,也就相当于绑定了相应的MessageQueue和自己在的那个线程。
3. 第三步使用Handler对象发消息
有如下可发消息的方法

post(); postDelayed(); //把Runnable任务作为消息加入到消息队列
postAtTime();
sendEmptyMessage(); sendMessage(); sendMessageDelayed(); 
sendMessageAtTime();
这些方法最终都是运行sendMessageAtTime //指定时间为uptimeMillis发送消息
public boolean sendMessageAtTime(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); 
    }
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
******* msg.target = this;  //this是当前handler对象,要发送的消息msg与发送此msg的handler对象绑定了
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    //MessageQueue的enqueueMessage方法,根据when把消息加入消息队列,只复制了重要代码。
 boolean enqueueMessage(Message msg, long when) {
            msg.markInUse();
            msg.when = when; //此消息要发送的时间
            Message p = mMessages; //mMessages属性代表MessageQueue的第一条消息
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { 
            //如果MessageQueue里没有消息,或者此消息执行时间小于等于0,则都将此消息设为头指针,也就是作为第一条消息
                msg.next = p; 
                mMessages = msg;
                needWake = mBlocked; 
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) { //将消息根据时间when早晚找到此消息需要插入队列的位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // 消息插入
                prev.next = msg;
            }
        }
        return true;
    }

不管handler以何种方式发消息,最终都会把这些消息根据msg.when,运行MessageQueue的enqueueMessage()存入消息队列,这个消息队列是在与此handler绑定的Looper对象里面。
4.第四步Looper.loop()取消息;

public static void loop() {
        final Looper me = myLooper(); //获取所在线程的Looper对象
        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) { // 没有消息就代表消息队列已经取完了.
                return;
            }
 ***********msg.target.dispatchMessage(msg);//把从队列取出的消息分发出去
            msg.recycleUnchecked();//回收消息
        }
    }
//msg.target.dispatchMessage(msg)方法,Handler的方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); //如果msg的callback不为空就代表这个消息有自己的任务,于是运行此方法,此方法实际就是msg.callback.run();
        } else { //msg.callback为空那就终于可以运行handleMessage了,你可以自己重写它实现自己的需求
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

从此线程的Looper里的MessageQueue里取消息,取出一条就发送一条msg.target.dispatchMessage(msg),然后就可以相应的处理这些消息了。

常见问题

1.主线程中为什么没有看到Looper对象?
Looper对象就是用来绑定所在线程,为线程开启一个消息循环,从而操作MessageQueue。默认情况下系统自动为主线程创建Looper对象,开启消息循环。因此主线程我们只会看到new Handler。而子线程就不行了。

2.handlerA发送的消息可以handlerB来处理吗?
不可以。
不知你会不会有这样疑问,不是一个线程只会有一个Looper对象一个MessagerQueue,可以有多个handler嘛,那说明多个handler共用一个MessagerQueue,这样的话就随便哪个handler发送的消息都在一个队列里罗,对的这都没错,那么handlerA发的消息在这个队列,handlerB用的也是这个队列,那么就能处理罗。其实若真正搞懂了这个机制也不会有这个问题,看看我的第三步发消息和第四步取消息里在前面加了一串 * 的代码,handlerA发消息时msg.target已经是handlerA了,而取消息时`msg.target.dispatchMessage(msg);则运行handlerA的dispatchMessage()方法,所以消息最终只会调用handlerA的相应处理消息的方法,(当然也可能会是消息本身处理掉)不管怎样都跟handlerB没关系。

3.使用handler在子线程发消息,主线程处理消息,为什么子线程不需要Loope对象呢?
其实只要确保取消息和存消息在同一个线程完成就行。表面上发消息是在子线程处理的,但实际发出的消息放入的队列是根据此handler对象在创建时的线程决定的,而不是发送消息时的线程决定,那么发消息时,给存放消息的队列是主线程,而取消息也是主线程,主线程默认执行Loop.loop。简单的说就是Handler对象在哪个线程创建哪个线程就得有Loop对象,除了主线程,它是默认创建了的。

4.主线程运行handler.post()这个方法里面传Runnable对象,里面的run()方法是在哪个线程运行?
这是昨天我跟别人吹牛时他问的我一个问题,我当时觉得问的就有问题,于是补充了句handler是否是在主线程创建的,(其实准确说应该是handler创建时绑定的Looper对象是否是主线程的)若是那run()就在主线程运行。因为Runnable都是当作一条消息放进队列的嘛,并由与之相关的handler来处理,那消息队列是在哪个线程,Looper就在哪个线程,而handler又与他们绑定了,那run方法就运行在哪个线程罗。所以就是主线程。但没有底气回答,总觉得哪里有点不对,那就再来看看源码吧。
运行handler.post(r);

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//这里就是以前说的把它作为一条消息发出去吗,重点我们再看看getPostMessage(r)
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;//原来message的callBack属性在这种情况赋值的。
        return m;
    }

再重点看看handler消息分发方法的这几行代码

if (msg.callback != null) {//看到没,前面我post(r)时把这个callBack赋值了,所以不为空,那就运行下一句了
            handleCallback(msg); //此方法实际就是msg.callback.run();终于看到run()方法运行了,那这个方法不就是运行于与handler绑定的那个线程,因为这都是在Loop.loop()里执行的操作。

所以之前我的大致想法是对的,run()方法是由与之绑定的handler处理,但当时不知道他最终是会去调用messaged的callBack的,现在算是对这条属性也有进一步理解了。

最后,你若有关于handler的其它疑问,欢迎在评论里咨询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值