handler消息机制总结

handler消息机制总结

什么是handler消息机制

handler消息机制:简单的说,其实是android系统为了线程间通讯而设计的一套线程间通讯框架。

对android有一定了解的人员都知道,我们android里只能在主线程中更新UI,那我们子线程中可以更新吗?答案肯定是可以,我们只能通过handler发送消息通知主线程更新UI。那有人可能就会问为什么要这样设计呢?其实这样设计google开发工程师也是考虑到性能以及维护复杂度才这样设计的,如果我们每个线程都能更新UI那势必会增加系统管理UI的复杂度。

从上面对handler消息机制的定义我们可以看出,handler消息机制并不是专门用于更新UI的,而是主要是解决线程间通讯的问题

handler消息机制怎么用

我们接下来看一段简单的使用代码

    private static final Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.i("gjb", msg.obj);
        }
    }

    new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 1;
                msg.obj = "abc";
                handler.sendMessage(msg);
            }
        }).start();

这个是handler的最简单使用,我们在子线程发消息然后在主线程处理消息或更新UI。

那么我们在子线程中怎么使用handler呢?接下来我们看一下子线程中使用handler的代码


 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(){
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        Log.i("gjb", msg.obj);
                    }
                };

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message msg = new Message();
                        msg.what = 1;
                        msg.obj = "abc";
                        handler.sendMessage(msg);
                    }
                }).start();
            }
             Looper.loop();
        }).start();

注意:子线程中使用handler就必须要调用Looper.prepare()和Looper.loop(),否则使用会出现异常,并且Looper.Prepare()这个方法只能调用一次,不能多次调用否则会发生异常。(后面我们会讲到)

看到上面的代码有的人可能就有疑问了,为什么我们在主线程中使用handler时我们也没调用 Looper.prepare()和Looper.loop()呢?我们都知道android应用的程序入口其实是ActivityThread类的main函数,接下来我们就来看一下主线程的Looper.prepare()和Looper.loop()在哪调用的。

ActivityThread类的main函数:

    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();

      	...
        Looper.loop();

      ...
    }

Looper类中的prepareMainLooper函数:

    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

从上面的代码就可以看出不是主线程和子线程使用handler其实是一样的。都需要调用Looper.prepare()和Looper.loop()方法,我们之所以没有调用是因为系统已经帮我们调过了。

关键类说明

**Handler:**这个类主要负责收发消息

Looper: 这个类里内部维护了一个消息队列MessageQueue,并且通过prepare方法来构建Looper对象,然后通过loop方法不断的从消息队列中取出消息交给handler去处理。这个类与我们下面的消息队列MessageQueue是一一对应关系

**Message:**这个类主要负责消息内容的封装。它的内部有一个target属性指向了我们创建的发送和处理消息的handler,所以我们在使用handler的时候才会出现handler既是消息的发送者又是消息的处理者。

**MessageQueue:**这个类主要负责消息的入队、出队工作和消息队列的维护工作。

handler消息机制工作原理

首先我们来看一张handler消息机制的工作示意图

在这里插入图片描述

从上面这个handler的示意图中,我们就会产生一个疑问:内部是怎么保证这么多子线程操作的是同一个消息队列呢?

实际上Looper与MessageQueue是一对一的关系,而Looper内部又维护了消息队列MessageQueue只要我们能获取到正确的Looper对象我们就能获取到对应的消息队列。那怎么才能获取到正确的Looper对象呢?答案是:利用ThreadLocal,ThreadLocal是为解决多线程的并发问题而生的。当我们调用调用Looper.prepare()的时候会把创建的Looper对象放到ThreadLocal中,需要用looper的时候我们就从ThreadLocal中去取,这样我们就能保证我们拿到正确的looper从而拿到正确的MessageQueue。

注意:使用的时候只要我们保证Looper.prepare()与Looper.loop()的调用在同一个线程中那么我们就能保证拿到正确的looper

ThreadLocal是什么?

  • ThreadLocal是一个线程中的成员变量,它是每个线程独有的互不干涉。
  • ThreadLocal是一个类Map结构的东西,只不过它的key是当前线程。
  • 它使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。

handler工作流程图:
在这里插入图片描述

handler源码分析

接下来我们就简单的看一下Handler的代码实现主流程。

在我们使用的时候会调用sendMessage方法,那我们就先看一下这个方法干了什么事情:

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
			...
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
		...
        return queue.enqueueMessage(msg, uptimeMillis);
    }

sendMessage最终会调到handler的enqueueMessage方法,这个方法中有一个关键就是**msg.target = this;**这一句代码,这一句代码就是为什么handler是自己发送消息自己处理的关键,在看Looper的loop方法是我们在讲它是怎么做到的,enqueueMessage()方法其实做的事就是把消息压入队列。

接下来我们看一下handler是怎么拿到消息队列MessageQueue的:

 public Handler(@Nullable Callback callback, boolean async) {
       ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
       ...
    }

从上面的代码可以看出其实队列是通过Looper对象拿到的,looper对象又是通过调用Looper.myLooper方法拿到的,接下来的空判断抛出的异常就是我们为什么不调用Looper.prepare()就会报错的原因。

接下来我们看一下Looper的myLooper方法干了什么事情:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

它是从TreadLocal中获取到Looper的,那这个Looper对象是什么时候set进ThreadLocal的呢?

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

从上面的代码可以看出Looper对象是在我们调用prepare方法的时候创建并set进ThreadLocal对象的。非空判断可以说明我们不能重复调用prepare方法否则程序会崩溃。

接下来我们看一下Looper的构造方法干了啥?

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

我们可以看到Looper的构造方法是私有的,我们只能通过prepare方法拿到。这个构造方法主要是创建了消息队列对象。

从这里我们就可以看出我们在使用handler的时候必须要调用Looper.prepare()方法,如果不调用就不会有Looper也不会有消息队列那么整个handler也就没办法正常工作

接下来我们看一下消息是怎么分发的又怎么回调到handler的handleMessage()方法的,这个关键就是Looper的loop方法:

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
      ...

        final MessageQueue queue = me.mQueue;

 		...

        for (;;) {
            Message msg = queue.next(); // might block
           ...
            msg.target.dispatchMessage(msg);
			...
        }
    }

loop方法的代码比较长我们只关注关键部分,逻辑非常简单首先拿到looper然后通过looper拿到MessageQueue然后从消息队列中取出消息调用handler的dispatchMessage()方法。关键是这个target,这个是msg的一个属性其实就是我们创建的handler,上面在提到发消息的时候会最终调用到handler的enqueueMessage()这个方法对target赋值为本类对象this,所以就做到了handler自己发送消息自己处理。

从这个方法我们看到Looper.prepare()和Looper.loop()必须成对出现否则会出错,还有就是Looper.loop()必须放到方法的结尾处调用,否则Looper.loop()后面的方法将没有机会执行因为Looper.loop()里面是通过一个死循环来不断的从队列中取消息的。

接下来handler的dispatchMessage()方法会回调handler的handleMessage()方法这里就不说了比较简单。

消息自己处理。**

从这个方法我们看到Looper.prepare()和Looper.loop()必须成对出现否则会出错,还有就是Looper.loop()必须放到方法的结尾处调用,否则Looper.loop()后面的方法将没有机会执行因为Looper.loop()里面是通过一个死循环来不断的从队列中取消息的。

接下来handler的dispatchMessage()方法会回调handler的handleMessage()方法这里就不说了比较简单。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值