Handler深入(分析源码,手写一套Handler)

本文作者

14

1-2018

zho007

博客:

https://www.jianshu.com/u/e9cb5fd3aaa8

声明:本文由作者 zho007 授权发布,未经原作者允许请勿转载

 看到上面蓝色字了吗,点下吧

前言

在安卓当中提供了异步消息处理机制的两种方式来解决线程之间的通信,一种是是AsynchTask,另外一种就是现在我们主要分析的Handler。

Handler是Android类库提供的用于接受、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。

简单使用分析

总所周知,安卓中子线程是不能更新UI的,如果在子线程更新,那么程序就会崩溃,那么这时候我们就使用到了handler,子线程操作完成通知主线程更新UI。我们先看下handler机制的分析图,和架构图:


  • Looper有一个MessageQueue消息队列;

  • MessageQueue有一组待处理的Message;

  • Message中有一个用于处理消息的Handler;

  • Handler中有Looper和MessageQueue。

一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该Handler对象。而一个Handler可以生成多个Message,所以说,Handler和Message是一对多的关系。

Android中主线程向子线程发送消息
1. 创建Handler

在安卓的ui线程中创建一个Handler

 Handler handler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
       super.handleMessage(msg);
       Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
       }
   };
2. 开启子线程向主线程的handler发送消息
bt.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               Message obtain = Message.obtain();
               obtain.obj = Thread.currentThread().getName() + ": 发送消息";
               handler.sendMessage(obtain);
           }
       }).start();
   }
});
3. 结果分析

结果:

当前线程: main  消息:Thread-4: 发送消息

结果说明我们在主线程中创建handler,然后点击按钮子线程向主线程发送消息成功

分析:

我们按照刚才的流程图来 分析一遍。

首先我们在主线程创建了一个Handler,那么在主线程中也会对应有一个Looper对象在轮询消息,一个Looper对象有一个MessageQueue,于是主线程中也有一个MessageQueue。我们的流程就是主线程中looper对象在一直轮询消息,如果消息队列中没有任何消息的话,那么当前线程暂时阻塞,直到子线程中获取handler对象发送消息,这时候,handler会对主线程中的MessageQueue中添加消息,当消息添加成功时,将阻塞的线程唤醒。于是looper轮询到有新消息,将新消息返回给handler对象,因为handler对象是在主线程中创建,所以消息将会在主线程中显示。

这个流程和生产者消费者模型有一点相似,一个线程生成消息,一个线程消费消息。所以在MessageQueue中的添加消息,和消费消息都会有一把锁。将这两个方法锁住;首先避免多个线程同时操作消息列队,和避免再写入消息的时候读取消息,导致消息错乱的问题。如下图,MessageQueue源码中锁住当前对象:

  • 也许这里有些难懂,但是没关系,我们继续向下分析

子线程向主线程发送消息

上面我们操作了子线程向主线线程发送消息,接下来我们使用handler主线程向子线程发送消息。

1. 子线程中创建handler对象。
2. 为当前子线程创建一个looper对象。(这里我们使用ThreadLocal来保存Looper副本)
3. 开启子线程looper轮询消息
    new Thread(new Runnable() {
           @Override
           public void run() {
               //对当前线程创建一个looper副本
               Looper.prepare();
               handler = new Handler() {
                   @Override
                   public void handleMessage(Message msg) {
                       super.handleMessage(msg);
                       Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);
                   }
               };
               //开启轮询消息
               Looper.loop();
           }
       }).start();
4. 主线程向子线程发送消息
 Message obtain = Message.obtain();
obtain.obj = Thread.currentThread().getName() + "线程发送的消息";
handler.sendMessage(obtain);
5. 结果分析

结果如下:

当前线程: Thread-4  收到消息:main线程发送的消息

这时候我们就成功重主线程发送了一条消息给子线程

分析:

我们重上面代码注意到相比子线程发送消息给主线程我们主线程发送消息给子线程多了两行代码:

1.Looper.prepare();

我们翻阅源码如下:

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));
}

这里我们使用了一个ThreadLocal来保存每一个接收线程中的Looper对象副本。由于子线程是我们手动开启的线程,所以我们要初始化一个looper副本。由于安卓主线程中,安卓系统自动维护了一个安卓主线程的looper对象副本并让looper轮询着消息。

2.Looper.loop();

我们翻阅源码如下:

 public static void loop() {
    //获取looper对象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取messageQueue对象
    final MessageQueue queue = me.mQueue;
                  ...............
    //轮询消息
    for (;;) {
        //轮询messageQueue中的消息,没有消息就再这里阻塞。
        Message msg = queue.next();
        if (msg == null) {
            // No message indicates that the message queue is
            return;
        }
                  ...........
        try {
            //发送消息
            msg.target.dispatchMessage(msg);
                   ............
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
                   ............
    }
}

这里安卓系统也是在主线程中轮询着消息。

手写一套handler。

我们经过上面的使用和简单分析了以后,也许还是有一些懵逼。所以下面我们自己通过生产者/消费者模型来模仿安卓Handler手写一套。代码如下:

Handler
public class Handler {
   private Looper mLooper;
   private MessageQueue mQueue;
   public Handler() {
       //获取当前线程的looper
       mLooper = Looper.myLooper();
       //获取当前线程的消息列队
       mQueue = mLooper.messageQuene;
   }
   /**
    * 发送消息
    * @param message
    */

   public void sendMessage(Message message) {
       message.target = this;
       mQueue.enqueueMessage(message);
   }
   /**
    * 处理消息
    * @param message
    */

   public void handleMessage(Message message) {
   }
   /**
    * 分发消息
    * @param message
    */

   public void dispatchMessage(Message message) {
       handleMessage(message);
   }
}
Looper
public class Looper {
   final MessageQueue messageQuene;
   private static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();
   public Looper() {
       messageQuene = new MessageQueue();
   }
   /**
    * 为当前线程初始化一个looper副本对象
    */

   public static void prepare() {
       if (threadLocal.get() != null) {
           throw new RuntimeException("Only one Looper may be created per thread");
       }
       threadLocal.set(new Looper());
       System.out.println("looper初始化");
   }
   /**
    * 获取当前线程的looper副本对象
    *
    * @return
    */

   public static Looper myLooper() {
       return threadLocal.get();
   }
   /**
    * 轮询消息
    */

   public static void loop() {
       //获取当前线程的looper对象
       Looper me = myLooper();
       Message msg;
       //开始轮询消息
       for (; ; ) {
           //轮询消息,没有消息就阻塞
           msg = me.messageQuene.next();
           if (msg == null || msg.target == null) {
               System.out.println("Looper:" + "空消息");
               continue;
           }
           System.out.println("Looper:" + "looper轮询到了消息,发送消息");
           //轮询到了消息分发消息
           msg.target.dispatchMessage(msg);
       }
   }
}
Message
public class Message {
   //发送的消息
   public Object obj;
   //目标Handler
   public Handler target;
   @Override
   public String toString() {
       return obj.toString();
   }
}
MessageQueue

要实现生产者/消费者模型,首先的有锁,这里使用ReentrantLock
主要考虑的重写入,它可以根据设定的变量来唤醒不同类型的锁,也就是说当我们队列有数据时,我们需要唤醒read锁;当队列有空间时,我们需要唤醒写锁。

public class MessageQueue {
   Message[] mItems;
   int mPutIndex;
   //队列中消息数
   private int mCount;
   private int mTakeIndex;
   //锁
   Lock mLock;
   //唤醒,沉睡某个线程操作
   Condition getCondition;//可取
   Condition addCondition;//可添加
   public MessageQueue() {
       mItems = new Message[50];
       mLock = new ReentrantLock();
       getCondition = mLock.newCondition();
       addCondition = mLock.newCondition();
   }
   /**
    * 消息队列取消息 出队
    *
    * @return
    */

   Message next() {
       Message msg = null;
       try {
           mLock.lock();
           //检查队列是否空了
           while (mCount <= 0) {
               //阻塞
               System.out.println("MessageQueue:" + "队列空了,读锁阻塞");
               getCondition.await();
           }
           msg = mItems[mTakeIndex];//可能空
           //消息被处理后,置空数组中该项
           mItems[mTakeIndex] = null;
           //处理越界,index大于数组容量时,取第一个item
           mTakeIndex = (++mTakeIndex >= mItems.length) ? 0 : mTakeIndex;
           mCount--;
           //通知生产者生产
           addCondition.signalAll();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           mLock.unlock();
       }
       return msg;
   }
   /**
    * 添加消息进队列
    *
    * @param message
    */

   public void enqueueMessage(Message message) {
       try {
           mLock.lock();
           //检查队列是否满了
           while (mCount >= mItems.length) {
               //阻塞
               System.out.println("MessageQueue:" + "队列空了,写锁阻塞");
               addCondition.await();
           }
           mItems[mPutIndex] = message;
           //处理越界,index大于数组容量时,替换第一个item
           mPutIndex = (++mPutIndex >= mItems.length) ? 0 : mPutIndex;
           mCount++;
           //通知消费者消费
           getCondition.signalAll();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           mLock.unlock();
       }
   }
}
测试:
public class Test {
   public static Handler handler;
   public static void main(String[] args) throws InterruptedException {
       new Thread(new Runnable() {
           @Override
           public void run() {
               Looper.prepare();
               handler = new Handler() {
                   @Override
                   public void handleMessage(Message message) {
                       super.handleMessage(message);
                       System.out.println("Test:" + Thread.currentThread().getName() + "线程接收到:" + message.obj);
                   }
               };
               Looper.loop();
           }
       }).start();
       //睡0.5s,保证上面的线程中looper初始化好了
       Thread.sleep(500);
       new Thread(() -> {
           Message message = new Message();
           message.obj = Thread.currentThread().getName() + "发送的消息 ";
           handler.sendMessage(message);
       }).start();
       new Thread(() -> {
           Message message = new Message();
           message.obj = Thread.currentThread().getName() + "发送的消息 ";
           handler.sendMessage(message);
       }).start();
   }
}
结果分析

结果:

looper初始化
MessageQueue:队列空了,读锁阻塞
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-1发送的消息
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-2发送的消息
MessageQueue:队列空了,读锁阻塞

分析:

到这里我们的手写的一套Handler就完成了。自己手写一次handler消息处理机制,再回过头来看看handler是不是很简单了,再也不怕面试中被问到。当然android源码中的handler处理机制移值到C层处理了.

demo地址https://gitee.com/zhongjiabao/Demo.git



刘某人程序员

个人微信:Android_LiuGuiLin

新浪微博:@刘某人程序员

看看我的分享

  长按二维码关注


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值