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

博客:android

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

声明:本文由做者 zho007 受权发布,未经原做者容许请勿转载面试

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

640?wx_fmt=png&wxfrom=5&wx_lazy=1

前言

640?wx_fmt=png&wxfrom=5&wx_lazy=1

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

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

简单使用分析

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

0?wx_fmt=jpeg
0?wx_fmt=jpeg

  • Looper有一个MessageQueue消息队列;oop

  • MessageQueue有一组待处理的Message;测试

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

  • 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源码中锁住当前对象:

0?wx_fmt=png

0?wx_fmt=png

  • 也许这里有些难懂,可是不要紧,咱们继续向下分析

子线程向主线程发送消息

上面咱们操做了子线程向主线线程发送消息,接下来咱们使用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

相关文章
相关标签/搜索