Handler机制实现原理

面试的时候经常会问handler原理啥的,前段时间刚好看了一个老师讲handle机制,老师讲得很仔细清晰,这里我自己也用代码模拟安卓handler实现一个基本线程通信。
说到handler就不得不说消息处理的五大组成部分:Message,Handler,Message Queue,Looper和ThreadLocal。首先简要的了解这些对象的概念;

Message:message就是一个数据模型吧,它的作用仅限于线程之间通信的时候传递消息,他可以携带少量数据,用于线程之间传递信息,常用的四个字段target,what,obj,arg;

target:消息回调后的作用域类,通常是一个handler。
what:是一个区分不同消息的标识符。
obj:这是obj是一个对象类型,可以携带自定义的类。
arg:int类型,携带的参数。
下面贴出Message代码:

public class Message {
    Handler target;
    public int what;
    public Object obj;

    @Override
    public String toString() {
        return obj.toString();
    }
}
handler:它主要用于发送和接收消息,有三个主要方法,这里实现基本功能,不探讨具体细节

sendMessage();
dispatchMessage();
handleMessage();

它主要用于发送和处理消息的发送消息一般使用sendMessage()方法,还有其他的一系列sendXXX的方法,但最终都是调用了sendMessageAtTime方法,把消息压入消息队列中。
而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage方法中。这里我们handleMessage()方法在外部重写,内部实现调用。

public class Handler {

    private MessageQueue mQueue;
    private Looper mLooper;

    // Handler的初始化在主线程中完成
    public Handler(){
        //获取主线程的looper对象
        mLooper = Looper.myLooper();
        this.mQueue = mLooper.mQueue;
    }

    // 发送消息,压入队列
    public void sendMessage(Message msg){
        msg.target = this;
        mQueue.enqueueMessage(msg);
    }

    //内部调用,外部实现
    public void handleMessage(Message msg){

    }

    // 转发
    public void dispatchMessage(Message msg){
        handleMessage(msg);
    }
}
MessageQueue是消息队列,主要存放所有handler发送过来的消息,这些消息会一直存放消息队列中,等待被处理,每一个线程只有一直MessageQueue队列。

这里实现的消息队列里面有一个入栈和出栈函数,这两个函数的关系是一个生产者和消费者的关系,我们在Looper.loop方法中通过while死循环方法不断检测生产者方法,一旦消息队列不为空,立即取出消息并处理,同时消息的总数量也相应需要改变。

public class MessageQueue {

    //通过数组的结构存储message对象
    Message[] items;

    //入队和出队元素索引位置
    int putIndex;
    int takeIndex;

    // 计数器
    int count;


    // synchronized (msg){} 代码块加锁
    // 互斥锁
    Lock lock;
    // 条件变量
    Condition notEmpty;
    Condition notFull;

    public MessageQueue(){
        this.items = new Message[50];

        this.lock = new ReentrantLock();   //这个锁和sychronize还是有些区别的,ReentrantLock 类实现了 Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
        this.notEmpty = lock.newCondition();
        this.notFull = lock.newCondition();
    }

    // 加入队列
    // 生产
    public void enqueueMessage(Message msg){
        System.out.println("加入队列");

        try {
            lock.lock();

            //消息队列满了,子线程停止发送消息,阻塞
            while (count == items.length){
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            items[putIndex] = msg;
            //循环取值
            putIndex = (++ putIndex == items.length)?0:putIndex;
            count ++;

            //生产出来新的message对象,通知主线程
            notEmpty.signalAll();

        }finally {
            lock.unlock();
        }
    }

    // 出队列
    // 消费
    public Message next(){

        //消息队列空了,子线程停止发送消息,阻塞
        Message msg = null;
        try {
            lock.lock();

            while (count == 0){
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            msg = items[takeIndex];
            items[takeIndex] = null;   //元素重置空
            takeIndex = (++takeIndex == items.length) ? 0 : takeIndex;
            count --;

            //使用列一个message对象,通知子线程,可以继续生产
            notFull.signalAll();

        }finally {
            lock.unlock();
        }

        return msg;
    }
}
Looper:每个线程通过Handler发送的消息都保存在,MessageQueue中,Looper通过调用loop()的方法,就会进入到一个无限循环当中,然后每当发现Message Queue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

/**
 *  一个线程对应一个looper对象,一个looper对应一个消息队列
 */

public final class Looper {

    //每一个主线程都有一个looper对象
    //Looper 对象保存在threadlocal中,保证列线程数据的隔离
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    // 一个looper对象 对应一个消息队列
    MessageQueue mQueue;

    private Looper(){
        mQueue = new MessageQueue();
    }

    //Looper对象初始化
    public static void prepare(){
        if (sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    //获取当前线程的looper对象
    public static Looper myLooper(){
        return sThreadLocal.get();
    }

    //轮询消息队列
    public static void loop(){
        Looper me = myLooper();
        if (me == null){
            throw new RuntimeException("not Looper; Looper.prepare() wait call");
        }
        MessageQueue queue = me.mQueue;
        for (;;){
            Message msg = queue.next();
            if (msg == null){
                continue;
            }
            // 转发给handler
            msg.target.dispatchMessage(msg);
        }
    }
}
在Main方法中写个测试例子
    public void test(){
        //轮询器初始化
        Looper.prepare();

        // 主线程当中
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                System.out.println(Thread.currentThread().getName() + ",receive:"+msg.toString());
            }
        };

        for (int i=0;i<10;i++){
            new Thread(){
                @Override
                public void run() {
                    while (true){
                        Message msg = new Message();
                        msg.what = 1;
                        synchronized (UUID.class){
                            msg.obj = Thread.currentThread().getName()+",send message"+ UUID.randomUUID().toString();
                        }
                        System.out.println(msg);
                        handler.sendMessage(msg);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }

        //开启轮询
        Looper.loop();
    }

打印结果如下:

// 08-18 19:13:28.149 8051-8129/com.dcw.handler I/System.out: Thread-5,send message41e677cd-0eb0-467b-a633-36a2e9c0cdc0
// 08-18 19:13:28.149 8051-8129/com.dcw.handler I/System.out: 加入队列
// 08-18 19:13:28.149 8051-8136/com.dcw.handler I/System.out: Thread-12,send messageebd27bf8-b157-466a-bab5-8d6057334ed2
// 08-18 19:13:28.150 8051-8136/com.dcw.handler I/System.out: 加入队列
// 08-18 19:13:28.150 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-5,send message41e677cd-0eb0-467b-a633-36a2e9c0cdc0
// 08-18 19:13:28.150 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-12,send messageebd27bf8-b157-466a-bab5-8d6057334ed2
// 08-18 19:13:28.154 8051-8137/com.dcw.handler I/System.out: Thread-13,send message3c1ae78b-8940-4a12-bb60-815442917edc
// 08-18 19:13:28.155 8051-8137/com.dcw.handler I/System.out: 加入队列
// 08-18 19:13:28.155 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-13,send message3c1ae78b-8940-4a12-bb60-815442917edc
// 08-18 19:13:28.159 8051-8134/com.dcw.handler I/System.out: Thread-10,send messagef6beb349-975c-4e17-a475-d3cc7c5bd94f
// 08-18 19:13:28.159 8051-8134/com.dcw.handler I/System.out: 加入队列
// 08-18 19:13:28.159 8051-8135/com.dcw.handler I/System.out: Thread-11,send message8d8ecf6e-3c76-4bad-b7af-dc916cb96b39
// 08-18 19:13:28.159 8051-8135/com.dcw.handler I/System.out: 加入队列
// 08-18 19:13:28.160 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-10,send messagef6beb349-975c-4e17-a475-d3cc7c5bd94f
// 08-18 19:13:28.160 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-11,send message8d8ecf6e-3c76-4bad-b7af-dc916cb96b39
// 08-18 19:13:29.149 8051-8130/com.dcw.handler I/System.out: Thread-6,send messagec7afaa49-e6b0-49a9

结果分析可以看到:只要子线程中一加入消息,那么looper就会轮询从massagequeue中取出消息并且通过dispatchMessage发送到主线程中取执行。我个人觉得所谓主线程,只不过比子线程中多了一个looper,我们的UI线程在只能在主线中刷新,就是应为线程的loop方法不断轮询绘制的原因,子线程之所有不能刷新UI,是因为子线程没有loop方法,如果我们把子线程中设置一个looper,那么子线程也是可以刷新绘制UI的。
以上是我个人见解,欢迎大家斧正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 【Android Handler 消息机制】 前言 在Android开发中,我们都知道不能在主线程中执行耗时的任务...
    Rtia阅读 4,834评论 1 28
  • 预热 在写这篇文章前我不止一次的问自己,网上分析Handler机制原理的文章那么多,为啥还要画蛇添足啊?不是说前人...
    吴七禁阅读 2,525评论 7 23
  • 去密云的古北水镇看看,是公司作为整个忙忙碌碌的五月工作的一次嘉奖,也是一次在计划之外的旅行。我与同事五人一行,开开...
    缱绻东吴丶阅读 250评论 0 1
  • 有人来就有人走,有人走就有人来,这就是部队的纪律,无论干部战士,都有面临这一天的到来。算不上建议,也是这些年和退...
    cc96b6dab081阅读 386评论 0 1
  • 七个音符带给我们的东西太美妙了,可惜很多歌我都已经听过,现在不知道听什么了,歌慌,真的。 流行乐,听多了,都会觉得...
    嵘书阅读 130评论 0 0