手把手带你实现Handler

在开始实现之前我们需要一个Handler模型进行推导

handler.png

ThreadLocal:

呵呵呵,没有我你们能干啥?

synchronized:笑而不语

Lock:楼上sb?还没被开除?

Condition:2楼sb

Handler:气氛Hi起来!动次打次!

Message:朋友圈帮我集个赞!

Looper:.......

MessageQueue:退群了

旁白:我们通过上图的模型可以推导出,要想实现Handler机制少不了以下的一些队友的配合

1.Message

TA就是一个对象,封装了一些信息,使用者可以将自己想要的信息传到里面去!里面只有几个成员变量,没别的!

private int what;//用过Handler的肯定经常用吧

private Object;//跟上面的成员变量一个意思

private Hander target;//这是个重点,为什么要封装一个Handler对象呢?因为最终处理消息的是Handler啊.如果没有这个对象,我们如何(调用)把消息传递给Handler的handleMessage方法呢?

2.MessageQueue

a.MessageQueue 是一个链表结构的消息队列(数组实现)

b.主要提供存,取,对存取的线程进行锁控制,避免多线程并发访问的问题.

c.MessageQueue可提供的功能列表:

void enqueueMessage(Message message);//将消息对象添加到链表中

Message next();//从链表中取出消息对象

boolean hasNext();//消息队列中的数据是否为空

3.Looper

死循环!死循环!死循环!死循环!死循环!死循环!死循环!死循环!

Looper中可提空的功能列表:

void prepare();//初始化Looper,将Looper绑定到ThreadLocal

void loop();//开始死循环的从MessageQueue中取出消息对象.

Looper myLooper();//获取ThreadLocal中存储的Looper对象

4.Handler

负责发送消息,处理消息

可提供的功能列表:

void handleMessage(Message message);

void sendMessage(Message message);//发送消息就是将你的Message对象放到MessageQueue中去

void dispatchMessage(Message message);//没别的,就是调用了一下handleMessage,之所以会有这个方法是因为我们更合理的进行封装.当Looper.loop方法循环取消息时会调用这个方法.

5.ThreadLocal

在线程中扮演了很重要的角色

引:ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性.

在Handler中使用ThreadLocal来进行数据隔离,目的就是更好的隔离Looper对象,毕竟我们的Looper是存储在了ThreadLocal 的map中.

6.Lock,Condition

为了解决线程锁,队列为空,或者满员的问题,当队列为空或者队列满员的情况我们要通过Lock去阻塞(消息不能被添加到队列中,取出消息的时候也同理),直到队列中有消息被消费了(处理了).毕竟我们得队列长度是有限的嘛,要不然就内存溢出了嘛,老天爷会惩罚你的嘛.

Condition:与Lock配对使用,我们这里之所以用Condition而不用synchronized的原因就是Condition 与Lock配合,可以指定"谁"来解锁.显而易见的用synchronized来实现是非常麻烦的,Lock和Condition 更适合复杂的并发场景.

OK!我们经过了上面的小图片的推导,大体的结构就已经出来了

首先Handler中的代码:

public class Handler {

    private MessageQueue mMessageQueue;
    private Looper myLooper;
    public void handleMessage(Message message){

    }
    //handler 在主线程中初始化
    public Handler(){
        //拿到Looper
        myLooper = Looper.myLooper();
        //拿到Looper中的消息队列

        mMessageQueue = myLooper.messageQueue;

    }
    public void dispathMessage(Message message){
        handleMessage(message);
    }
    public void sendMessage(Message message){
        //发送消息就是将消息实体入队到消息队列中
        message.target = this;
        mMessageQueue.enqueueMessage(message);

    }

}

Looper中的代码:

public class Looper {


    MessageQueue messageQueue;
    public static void prepare(){
        if (mThreadLocal.get() != null){
            throw new RuntimeException("Only on Looper may be created per thread");
        }
        mThreadLocal.set(new Looper());
    }
    public Looper(){
        messageQueue = new MessageQueue();
    }
    private static final ThreadLocal<Looper> mThreadLocal = new ThreadLocal<>();

    public static void loop(){
       Looper me =  myLooper();
       if (me ==null){
           throw  new RuntimeException("thread looper is null.call Looper.prepare()");
       }
       //获取到loop中的消息队列
        MessageQueue messageQ = me.messageQueue;
        for (;;){

            if (!messageQ.hasNext()){
                continue;
            }else {
                Message message = messageQ.next();
                //如果消息为null,继续处理下一个
                if (message ==null){
                    continue;
                }
                //拿到消息,将消息发送给Handler
                message.target.dispathMessage(message);
            }
        }
    }
    public static Looper myLooper(){
        if (mThreadLocal.get() ==null){

            throw new RuntimeException("not call Looper.prepare()");
        }
        return mThreadLocal.get();
    }
}

Message中的代码:

public class Message {
     Handler target;//消息的处理对象
    public int what;
    public Object object;

    @Override
    public String toString() {
        return object.toString()+":what:"+what;
    }
}

MessageQueue中的代码

public class MessageQueue {

    /**
     * 到底能不能再子线程中更新UI?
     * Android 通过判断当前线程是否是ViewRootImpl的创建线程来检查是否可以更新UI
     * 调用View.addView的时候会调用初始化ViewRootImpl,也就是说如果我在子线程中addView,
     * 那么我就可以在子线程中更新View.
     */
    private int MAX_QUEUE = 50;
    /**
     *  消息队列应该有上限,否则会造成内存溢出
        当队列没有消息的时候需要阻塞,直到有消息进来
        当队列消息满的时候需要阻塞,直到有消息被处理
     */
    Message[] messageQueue;

    private Lock mLock;//锁对象,为了可以控制消息队列什么时候可以继续出队或者入队
    private Condition popCondition;//可以选择性的解锁
    private Condition pushCondition;

    private int messageQueueCout;//队列中的消息的当前是数量
    private int pushIndex;//入队时的索引
    private int popIndex;//出队时的索引
    //出队消息:运行在子线程中

    public void enqueueMessage(Message message){
        try {
            //加锁类似于synchronized (),因为Condition可以选择性解锁
            mLock.lock();
            //当消息队列满了,子线程停止发送消息,阻塞
            //为了避免多个子线程同级唤醒,导致的单次判断问题.使用while 不断进行检查.
            while (messageQueueCout == MAX_QUEUE){
                pushCondition.await();
            }
            //将消息推进数组中
            messageQueue[pushIndex] = message;
            pushIndex++;
            if (pushIndex == MAX_QUEUE){
                pushIndex = 0;
            }
            messageQueueCout++;
            //如果有产品被生产,通知消费者可以继续消费Message
            popCondition.signalAll();//??
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }

    }
    //出队消息:运行在主线程中(消费队列中的消息)
    public Message next(){
        Message message = null;
        try {
            //当消息队列为空的时候阻塞消费者,让消费者不能继续消费消息
            mLock.lock();
            while (messageQueueCout ==0){
                popCondition.await();
            }

            message = messageQueue[popIndex];
            messageQueue[popIndex] = null;//当一个数据被出队时,释放内存
            popIndex++;
            if (popIndex == MAX_QUEUE){
                popIndex=0;
            }
            //当队列数量不满时通知子线程可以继续入队消息
            messageQueueCout--;

            //通知生产者可以继续生产Message
            pushCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }

        return message;
    }
    public boolean hasNext(){
        return messageQueue.length!=0;
    }
    public MessageQueue(){
        //初始化消息队列
        messageQueue = new Message[MAX_QUEUE];
        mLock = new ReentrantLock();//初始化锁对象
        this.pushCondition = mLock.newCondition();
        this.popCondition = mLock.newCondition();

    }

}

好的!我们来写个Main方法调用一下!

public class Main {

    public static void main(String[] args) {

        Looper.prepare();
        Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message message) {
                System.out.println(message);
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message m = new Message();
                m.object="hello";
                mHandler.sendMessage(m);
            }
        }).start();
        Looper.loop();
    }
}

这就是为什么在Activity main方法中会调用 Looper.prepare()...Looper.loop();
因为你要将自己持久化到内存中去嘛!要不然就死掉了嘛!
通过一个死循环+Handler
当我们在子线程中创建Handler的时候,也要模仿Activity的Handler创建方式才对嘛!
在哪个线程中创建了Handler 对于Handler本身来说谁就是主线程.因为对于Handler来说线程是相对关系的!

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

推荐阅读更多精彩内容