#handler ,Looper的机制,分析源码(三)自己撸一套Looper机制

前言

之前写了文章分析了handler切换线程Looper机制的源码
然后就想,这套东西是不是自己可以实现一套?好奇心重的我呀~~撸起袖子就是干!

分析

首先,这套东西是完全自己实现,要和android之前的那套完全脱离,所以不能引入android sdk,只能建一个java library。

其次,自己撸的这东东,也要能切换线程,也要有消息的循环机制(入队列、出队列、线程阻塞和唤醒)。啧啧,好像功能也挺强大啊哈哈。

根据之前的分析,android源码里有几个角色:Handler、Looper、Message、MessageQueue是java层的,Handler负责往MessageQueue里加Message,Looper负责取;native层的Looper负责线程的阻塞和唤醒,java层MessageQueue为空的时候阻塞,有消息添加的时候唤醒。其实各个角色的职责已经很清楚了。

java层的代码我可以模仿着来一套,native我却不打算和android源码一样玩了,不然不就等于是复制粘贴了一遍嘛,程序员要有梦想的嘛~~。

native层说白了只负责阻塞和唤醒,只是这样我java大法也是搞的定的嘛。这个功能怎么看都像是一个BlockingQueue(阻塞队列)。当队列为空的时候阻塞,队列有内容的时候唤醒,另一头一直在取,取不到(队列为空)就一直等待,这似乎可以替代使用native。源码用native,估计是方便C++ 和java层都能复用吧。

类设计

MessageQueue.java

上面说了,使用BlockingQueue。具体的,我用了DelayQueue,它是BlockingQueue也是PriorityQueue,它是个链表式的阻塞队列,链表结构的好处就是不需要连续的内存空间,而且可以“无限大”,更重要的是,它本身就可以按时间排序,这也符合功能要求,可以不用自己写单链表排序。但是这个Queue 和 android源码中的MessageQueue不一样,它是个真正的“队列”,满足按优先输出时间小的消息的特征。

/**
 *
 * @author yueshaojun988
 * @date 2017/10/24
 * 消息队列类
 */

class MessageQueue {

    private DelayQueue<Message> delayQueue = new DelayQueue<>();
    MessageQueue(){

    }


    /**
     * 取出消息
     * @return
     */
    Message next(){
        Message msg = null;
        try {
             msg = delayQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return msg;

    }

    /**
     * 消息入队列
     * @param message
     */
    void enqueue(Message message){

        enqueueDelay(message,0);

    }
    /**
     * 消息入队列
     * @param message
     * @param delayTime 延迟时间
     */
    void enqueueDelay(Message message, long delayTime){
        if(message == null){
            throw new RuntimeException("you cannot enqueue a null message!");
        }
        long now = now();
        message.when = delayTime + now ;
        delayQueue.offer(message);

    }
    private long now (){
        return System.currentTimeMillis();
    }
}   

这个类包含了一个私有变量DelayQueue,并提供了入队列(enQueue)、延时入队列(enQueueDelay)和消息出队列(next)。delayTime设置为0,也就是enqueue方法。next方法是模仿了android源码,阻塞取,由于DelayQueue 是按照时间由小到大输出消息,取不到时,它会阻塞等待下一个满足时间条件的消息就绪,所以不需要再有额外的判断。

另外,MessageQueue不应该被使用者直接操作,所以都应该是包访问限制的(方法和变量都不加修饰符)

Message.java

由于使用DelayQueue,它里面的item要实现Delayed接口,Delayed接口是继承了Comparable的,所以Nessage类要实现getDelay 和 compareTo,这两个方法是DelayQueue能够按我们设想的以时间由小到大顺序输出的重要依据。另外,Message的实现没有再使用单链表。

    /**
 *
 * @author yueshaojun988
 * @date 2017/10/24
 */

public class Message implements Delayed{
    public int what;
    public Object obj;
    long when;
    Handler target;
    Runnable callback;
    private AtomicLong sequence = new AtomicLong(0);
    private long sequenceNumber ;

    /**
     * 生成Message的方法,绑定handler
     * @param target
     * @return
     */
    public static Message obtain(Handler target){
        return obtain(target,null);
    }

    /**
     * 生成Message的方法,绑定handler和Runnable
     * @param target
     * @param callback
     * @return
     */
    public static Message obtain(Handler target ,Runnable callback){

        Message msg = new Message();
        msg.target = target;
        msg.callback = callback;
        return msg;
    }
    public Message(){
        sequenceNumber = sequence.getAndIncrement();
        when = now();
    }

    /**
     * 发送到指定的Handler
     */
    public void sendToTarget(){
        target.sendMessageDelay(this,0);
    }

    /**
     * 延时发送到指定的handler
     * @param delay
     */
    public void sendToTargetDelay(long delay){
        checkAccess();
        when = System.currentTimeMillis() + delay;
        target.sendMessageDelay(this,delay);
    }
    private void checkAccess(){
        if(target == null){
            throw new RuntimeException("target must not be null !");
        }
    }

    @Override
    public long getDelay(TimeUnit timeUnit) {
        return timeUnit.convert(when - now(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed other) {
        if(this == other){
            return 0;
        }
        if(other instanceof Message){
            Message o = (Message) other;
            long diff = when - o.when;
            if(diff<0){
                return -1;
            }
            if(diff>0){
                return 1;
            }
            if(sequenceNumber < o.sequenceNumber){
                return -1;
            }
        }
        long d = (getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS));
        return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
    }

    private long now(){
        return System.currentTimeMillis();
    }

}

注意,getDelay需要返回具体的延时时间,这个会作为阻塞超时时间(详细请看源码)。compareTo中,如果是同一个消息返回0,并按照message处理的预设时间when排序,如果when相同,则按照sequenceNumber的大小排,小的那个消息是排在前面优先输出的。

/**
 *
 * @author yueshaojun988
 * @date 2017/10/24
 */

public final class Looper {
    MessageQueue messageQueue;
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;
    private Thread mThread;

    /**
     * 绑定Looper和线程、MessageQueue,不能直接new Looper();
     * 需要在使用Handler之前调用,否则会抛出异常。
     */
    public static void prepare(){
        if(sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    private Looper(){
        mThread = Thread.currentThread();
        messageQueue = new MessageQueue();
    }

    /**
     * 类似于prepare(),但是这个方法是用来绑定主线程的。
     */
    public static void prepareMainLooper() {
        prepare();
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = sThreadLocal.get();
        }
    }

    /**
     * 获取主线程的looper
     * @return
     */
    public static Looper mainLooper(){
        return sMainLooper;
    }

    /**
     * 开始循环取消息,必须要在prepare之后
     */
    public static void loop(){
        for(;;) {
            checkAccess();
            Message message =  myLooper().messageQueue.next();

            if (message == null) {
                return;
            }
            System.out.println("looping message " + message.obj);
            message.target.dispatchMessage(message);
        }
    }

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

    private static void checkAccess() {
        if(sThreadLocal.get()==null){
            throw new RuntimeException("no looper!");
        }
    }
}

Looper类基本上是仿照源码Looper写的。不多说。

Handler.java

/**
 *
 * @author yueshaojun988
 * @date 2017/10/24
 */

public class Handler {
    private Looper mLooper;
    protected void dispatchMessage(Message msg){
        if(msg.callback!=null){
            msg.callback.run();
        }
    }
    public Handler(Looper looper){
        mLooper = looper;
    }
    public Handler(){
        mLooper = Looper.myLooper();
    }

    /**
     * post 一个方法
     * @param runnable
     */
    public void post(Runnable runnable){
        checkAccess();
        Message msg = Message.obtain(this,runnable);
        mLooper.messageQueue.enqueue(msg);
    }

    /**
     * 延迟 DelayTime post 一个方法
     * @param runnable
     * @param delayTime
     */
    public void postDelay(Runnable runnable,long delayTime){
        checkAccess();
        Message msg = Message.obtain(this,runnable);
        msg.when = (now()+delayTime);
        mLooper.messageQueue.enqueueDelay(msg,delayTime);
    }

    /**
     * 消息延迟入队列
     * @param message
     * @param delayTime
     */
    public void sendMessageDelay(Message message,long delayTime){
        checkAccess();
        mLooper.messageQueue.enqueueDelay(message,delayTime);
    }

    private void checkAccess(){
        if(mLooper == null){
            throw new RuntimeException("you must call Looper.prepare() first!");
        }
    }

    private long now(){
        return System.currentTimeMillis();
    }

}

按照源码Handler 的功能,提供了入队列的方法。当然,我没有完全按照源码的方法来,省去了很多无关的代码(哈哈哈哈,少写很多代码)。

测试

最后来个测试类:

/**
 *
 * @author yueshaojun988
 * @date 2017/10/25
 */

public class Test {
    public static void main(String[] args){
        long id = Thread.currentThread().getId();
        System.out.println("current thread id = " + id);
        ThreadUtil.bindMainThread(Thread.currentThread());
        Looper.prepareMainLooper();

        final Handler handler = new Handler(){
            @Override
            protected void dispatchMessage(Message msg) {
                System.out.println("receive msg  " + msg.obj);
                super.dispatchMessage(msg);
            }
        };
        Message message1 = Message.obtain(handler);
        message1.obj = "message1";
        Message message2 = Message.obtain(handler);
        message2.obj = "message2";
        Message message3 = Message.obtain(handler);
        message3.obj = "message3";

        message1.sendToTarget();
        message2.sendToTargetDelay(5000);
        message3.sendToTarget();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("before post thread id = " + Thread.currentThread().getId());
                handler.postDelay(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("after post thread id = " + Thread.currentThread().getId());
                    }
                }, 2000);
            }
        }).start();
        Looper.loop();
    }
}

输出:

current thread id = 1
before post thread id = 10
looping message message1
receive msg  message1
looping message message3
receive msg  message3
looping message null
receive msg  null
after post thread id = 1
looping message message2
receive msg  message2

可以看到,message按照时间先后输出,message2的延时时间最久,最后输出,并且线程ID由1-》10-》1完成了切换。至于是不是按照5s和2s,不试着跑一跑怎么知道呢?哈哈,我先给自己一波666~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值