Android消息机制

************消息机制原理基本上算是面试必备问题了,之前看源码,看别人文章博客,以为自己搞明白了,也写了点笔记,但是到真正说出来的时候总是不顺畅,感觉不光是表达能力问题,整个体系其实也没有一个完整认识,所以写篇博客,按自己的思路来分析一下**********

1.概念

总的来说就是通过Handler 和Looper 实现线程间消息传递。作用呢,最多的当然是将子线程耗时处理的结果传递到主线程,都知道的是Android的主线程要避免耗时超做,像最多的网络请求,循环遍历操作等,而操作结束后又会根据结果对主线程做一些改动,但是子线程是禁止UI操作的,这时候就需要Handler来登场,将操作结果发送到主线程。

2.原理及源码分析

我们从功能开始分析,一步步解析消息从发送,处理,转发,最终到主线程我们能够获取到的过程

首先是Handler,从字面意思就是消息发送者,当子线程处理完得到结果时,我们通过Handler对象将消息发送出去,调用sendMessage() 等方法,然后就会在我们创建的Handler中收到消息。那么我们先看Handler的创建,Handler提供了几种构造方法,传入不同参数,可以 根据需要来创建

public Handler(Callback callback, boolean async) {
    
        ......

        mLooper = Looper.myLooper();
        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对象,Looper中的MessageQueue对象,这里要知道的是,Looper对象是根据线程创建,不同线程有自己的Looper,如果你创建Handler时没有指定Looper, 那么你在哪个线程创建的Handler,你最终sendMessage后消息就会发送到对应线程

   private  Handler mHandler;
    class LooperThread extends Thread {
        @Override
        public void run() {
           Looper.prepare();//子线程不会自动创建Looper对象,需要prepare以后Looper.myLooper才能获取到Looper对象
            mHandler  =  new Handler() {
//            mHandler  =  new Handler(looper) {
                @Override
                public void handleMessage(Message msg) {
                    LogUtil.LogE("LooperThread name:"+currentThread().getName());
                }
            };
            Looper.loop();
        }
    }
com.yh.mohudaily E/hiall: LooperThread name:Thread-4267
********在这里我创建了一个子线程类,内部初始化Handler,然后在主线程sendMessage  接收到消息时打印当前线程名称,可以看到,handleMessage时的线程还是子线程。但是当我指定Looper为主线程Looper对象创建Handler时,handlerMessage的线程就会变成主线程。所以Handler对象创建时要主要所在线程。******
创建完Handler后我们继续看发送消息的方法。
 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);
    }
所有send方法最终都会进入到这个方法,在这里获取构造方法中取出的消息队列MessageQueue ,然后进入enqueueMessage方法
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
在这里将handler对象放入msg中(很重要的一步),然后让消息进入消息队列。
在messageQueue中
boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            ......
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {//判断当前队列是否为空,when=0时,when比队列最上面一条消息小时 都要讲消息插入到队列最前
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {	//否则就遍历消息,比较各个消息的when值 按从小到大顺序插入到队列
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            ......
        }
        return true;
    }
消息队列主要是两个方法,插入消息(enqueueMessage)和取出消息(next);具体插入方法如上,消息进入队列后handler的处理暂时走完(为什么说暂时,很明显msg携带了handler对象,后面还要用到)。
在handler中将消息插入队列,那么怎么取出消息呢?我们是从Looper对象中得到消息队列的,现在进入到Looper对象中去
Looper没有提供构造方法,只有myLooper()静态方法获取Looper对象,Looper对象的创建是在Looper.prepare() 中完成,私有化构造方法中初始化了一个消息队列,指定当前线程。(同时将Looper对象存入到ThreadLocal类中,关于这个类原理后面在解释,他的作用就是对线程Looper对象的存取)
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
创建完后调用Looper.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;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }
在这个无限循环里面会不断调用消息队列的取出方法,获取队列里面的消息对象。消息队列的next方法本身也是一个无限循环,不断从队列中获取消息,队列为空时next方法会阻塞等待,直到有消息进入队列时就会取出消息返回消息对象(需要注意的是当Looper的quit方法调用时,消息队列会退出,这时候next才会返回null),然后通过msg中携带的Handler对象将消息发送出去,dispatchMessage方法其实就是调用了一个空实现方法handlerMessage 也就是我们需要实现的处理消息的方法。到这里其实我们需要处理的结果在被封装到Message中后终于回到了主线程。
3 补充
关于Looper对象的获取,我们验证当前线程是否是主线程时会根据线面这行代码来判断
 return Looper.myLooper() == Looper.getMainLooper();
 
其实getMainLooper 返回的sMainLooper 也是通过myLooper方法获取,也就是从ThreadLocal中拿到,那这两个方法拿到的Looper对象为什么会不同呢?sMainLooper 对象的赋值是在prepareMainLooper方法中,这个方法在Activity创建时就被调用,指定了创建的Looper线程为主线程,而myLooper 获取时就是以当前线程去从ThreadLocal类中拿的Looper,所以才可能出现两个Looper对象不相等从而验证线程。
关于ThreadLocal 他能根据不同线程存储线程数据,每个线程互不影响,具体实现我也不是十分明白,这里就不做解释了,有兴趣的自己去看看吧。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值