Android Handler线程间通信原理分析

2021-03-01  本文已影响0人  Gray_s

Android的Handler线程间通信作为面试必问,重要性不言而喻。作为开发者如何理解和利用进程间通信就变得尤为关键。本文将分三个部分剖析:使用方式、原理分析,如何利用。

使用方式

Handler的使用方式很简单

//处理接受到的message
    var handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {

        }
    }
//或
    var handler1 = Handler(Looper.getMainLooper(), object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true
        }

    })

代码中可以看到主要有两种方式,一种是重写handleMessage()方法,一种是实现Handler.Callback接口。这两种方式在优先级上有所不同,当实现Handler.callback接口时将优先执行接口的方法,当返回flase时才会执行重写的handleMessage()方法。两种方式中共有的参数,是表明使用的时哪个线程的Looper,最后就会在哪个线程执行方法。

        val obtainMessage = handler.obtainMessage()
        //  设置传入的数据
        obtainMessage.what = 1
        handler.sendMessage(obtainMessage)
//或
        handler.post(object : Runnable {
            override fun run() {
            }
        })

发送消息同样有两种方式,一种是获取message,传入数据然后发送,还有一种是直接post传入一个Runnable对象。就这两种方式而言,本质是一样的,第二种方式依然会转换成一个Message对象。但区别在于,如果handle重写了handleMessage()方法或者实现了Handler.Callback接口,那么将不会执行,只会执行postrun()方法。

原理分析

组成部分

从使用的代码来看,线程间通信功能的实现至少有:

但是只有以上的组件,并不能完成这个功能。从逻辑上来看:1、线程2的Looper分发线程1Messagehandler中去处理。2、在handler发送线程1的Message。在这两条逻辑上似乎缺少东西联系在一起。Looper从哪里获取到Message,handlerMessage发送到哪里去。这时就需要第4个组件:

线程间通信流程

具体实现

以主线程为例,在我们的App启动时最先执行的时启动Looper

//android.app.ActivityThread#main
        Looper.prepareMainLooper();
        ...
        ActivityThread thread = new ActivityThread();
        ...

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");

上面就是启动Loop的主要方法,同时会创建ActivityThread对象,在这个对象中就有Handler对象。

代码中的prepareMainLooper()是一个关键代码。

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

这段代码会先判断是否已有主线程的Loop,没有则会通过myLooper()创建所在线程的Looper,然年后再把Loop赋值给静态变量sMainLooper ,通过这个静态变量就可以跨线程获取主线程的Looper。而myLooper()只能获取当前线程的方法。原因就在于prepare(),和ThreadLocal对象。

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //  把当前线程创建的Looper放入ThreadLocal中
        //  这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程
        //(通过其get或set方法)都有自己独立初始化的变量副本
        sThreadLocal.set(new Looper(quitAllowed));
    }

Handle中主要的方法为handleMessage()dispatchMessage()以及各种发送message方法。

    public void handleMessage(@NonNull Message msg) {
    }
    
    //表明 了消息处理的顺序
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

MessageQueue中主要有5类方法

组件间关系

知道了组件间的实现,那么组件间的是怎么调用的?
依然从使用来看,首先handler要求传入Looper对象,handler会获取Message对象。所以我们会认为,handler持有Message、Looper对象。但当我MessageQueue对象却无法直接看到。从前面的分析中可以知道,MessageQueue是容器,Looper分发Message,所以Handler持有MessageQueue对象才更为合理,而Looper同样也应该持有MessageQueue对象,才能从其中获取、分发Message,Message同样应持有Handler对象,这个Looper才能知道该把Message发给哪里的Handler。
在查看源码后确实如此(为了方便就不放出源码)


关系图

如何利用

首先,线程间通信的主要功能就是跨线程。其次,在实现上来说,会利用到队列。针对这两个特点,我们可以加以利用。例如,高频发送的命令式异步任务,这个任务需,高频的发送命令,同时命令会有延时操作,而且新的命令会覆盖到已经进入队列但暂为执行的任务。这样的任务就适合直接利用Handler的结构进行解决,而不用自己维护一个队列。

上一篇下一篇

猜你喜欢

热点阅读