前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >锦囊篇|一文深入Handler

锦囊篇|一文深入Handler

作者头像
ClericYi
发布2020-06-23 15:20:11
3840
发布2020-06-23 15:20:11
举报
文章被收录于专栏:ClericYi's BlogClericYi's Blog

前言

在日常开发中,我们势必会使用到子线程和UI线程的通信,而起着桥梁作用的就是我们常用的Handler。但是他的内部是怎么运作的?运作的过程中存在什么问题?需要我们注意,本文将会详细讲解。

解析Handler

从图中我们就可以知道了,整个Handler工作组成的包括了HandlerLooperMessageQueueMessage这四个部分。

MessageQueue和Message分别只是一个队列和消息实体类,自然不再多说。而Handler和Looper的具体是怎样的呢?

在我的模拟Handler项目中,已经比较清晰的阐述了整个框架的工作流程,接下里就是结合SDK代码的一份解析了。

整个Handler往简单了来说其实就干了两件事情:

  • 发送消息
  • 处理消息

发送消息

涉及到的三个函数sendMessage()enqueueMessage()Looper.prepareMainLooper()

所有事情的起源要从Looper.prepareMainLooper()开始讲起。这个函数处于ActivityThread中,没有了解过这个类的读者们需要知道,java编程一定是有一个主入口的,但是我们在整个Android编程中,从来没有涉及过main()这个函数,是因为它已经包含在了ActivityThread这个类中,而它已经经过了复杂的封装。

接下来看下这个Looper.prepareMainLooper ()函数。

代码语言:javascript
复制
public static void prepareMainLooper() {
        prepare(false); // 1
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper(); // 2
        }
    }
// 上述注释1对应的函数
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));
    }
// 上述注释2对应的函数
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
代码语言:javascript
复制

两小段代码,里面用到了一个变量sThreadLocal,这个变量是使用static final修饰的,意味这全局唯一。从他主动抛出的异常我们也可以看出Looper这个对象也是一个唯一的变量。这是我们需要掌握的第一个知识点。

接下来是关于sendMessage()函数 这个函数其实是一个泛称,他并不单单指sendMessage(),他还可以是sendMessageAtTime()sendMessageDelayed(),他们都干了一件事情——传递消息。通过一直向下探索,你就能知道他们最后调用的都是enqueueMessage()这个函数,也就是把消息放进了消息队列中。

当然要注意到一个事情,这里我们使用了synchronized的关键词,其实就是为了保障我们的数据同步。

没有很多的操作,就是我们熟悉的链表操作。这里没有做展示,有兴趣的朋友进到源码往下翻一点,马上就能看到了。

就这样很简单,并且很成功的让我们的消息进入了消息队列。

处理消息

接收完消息,我们要干嘛?我们为什么要发消息,因为我们要处理啊。

这里我们要遇到的函数有:Looper.loop()dispatchMessage()handleMessage()。用过Handler的读者们都应该知道我们是需要重写handleMessage()这个函数的,用于对不同的消息作出响应,所以就不再多介绍。所以第一个讲的就是Looper.loop()这个函数。

一共两段代码,也是至关重要的一部分。在这个代码中两个至关重要的点:

(1)首先是问题是这么一个死循环的函数,怎么就没引发ANR呢????

(2)通过dispatchMessage()如何分发这个消息的?target是什么?

先是第一个问题的解答。网上的解答多种多样。但是最关键的点其实是这样的,ANR是围绕loop()这个函数展开的,而ANR的出现也就是loop()的消息没有得到及时的消费。

第二个问题。先说target这个爆红的变量是什么。msg.target也就是说这是Message的一个元素,搜索Message就能找到如下图示。

原来target就是一个Handler,而这个Handler就是我们对应的主动创建Handler。然后就是dispatchMessage()函数了。

代码语言:javascript
复制
代码语言:javascript
复制
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
代码语言:javascript
复制

想来这就很清楚了,他也就是将事件分发给了handleMessage()处理,而handleMessage()又是我们自己来专门写的。msg.callback是一个Runnable对象。

害,原来就是这样啊。。

MessageQueue中消息如果为空,该咋办???

其实他在MessageQueuenext()方法中已经有了对应的解决方案了。

代码语言:javascript
复制
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        // 重点!重点!重点!!!
        // 一个死循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 。。。。。。
                // 判断队列内部是否还有消息
                if (msg != null) {
                    // 。。。。。
                } else {
                    // No more messages.
                    // 不存在更多的信息数据了
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 两种情况调用到IdleHandler
                // 1.消息队列为空;2.下一条消息在将来才执行
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 如果idleHandler都不存在了,那只能让Looper阻塞
                // 继续循环执行
                if (pendingIdleHandlerCount <= 0) {
                    // 出现了一个用于阻塞的判断
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 执行这些IdleHandler,那IdleHandler是干嘛的?
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

上面我们讲述了MessageQueue中的next()想必已经为我们指点迷津了,这里再进行一个梳理。

  1. MessageQueue中存在数据时,就是正常的找到对应的Handler进行处理。
  2. 如果MessageQueue没有数据了,这时候分为两种情况
    1. 寻找mIdleHandlers,也就是一些类似与系统服务了,进行处理。
    2. 连mIdleHandlers都没有了,就进入阻塞状态,等待新的任务进入将其唤醒。

既然知道了IdleHandler的存在,就看看他的具体运作方式是如何的,而最主要的就是他的接口方法queueIdle()

代码语言:javascript
复制
// 在ActivityThread中存在
// 使用了aidl调用了底层服务
// 也就是说在你没事干的时候,他也会自己给自己安排事情做
private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            ActivityClientRecord a = mNewActivities;
            boolean stopProfiling = false;
            if (mBoundApplication != null && mProfiler.profileFd != null
                    && mProfiler.autoStopProfiler) {
                stopProfiling = true;
            }
            if (a != null) {
                mNewActivities = null;
                IActivityTaskManager am = ActivityTaskManager.getService();
                ActivityClientRecord prev;
                do {
                    if (localLOGV) Slog.v(
                        TAG, "Reporting idle of " + a +
                        " finished=" +
                        (a.activity != null && a.activity.mFinished));
                    if (a.activity != null && !a.activity.mFinished) {
                        try {
                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
                            a.createdConfig = null;
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                    prev = a;
                    a = a.nextIdle;
                    prev.nextIdle = null;
                } while (a != null);
            }
            if (stopProfiling) {
                mProfiler.stopProfiling();
            }
            applyPendingProcessState();
            return false;
        }
    }
    
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();//做 GC 操作,VMRuntime.getRuntime().requestConcurrentGC();
        purgePendingResources();//清除悬而未决的资源
        return false;
    }
}

final class PurgeIdler implements MessageQueue.IdleHandler {
    @Override
    public boolean queueIdle() {
        purgePendingResources();//清除悬而未决的资源
        return false;
    }
}

其他讲实话,可能就是处理一系列没有优先级是那种并需要立刻处理的事情。

HandlerThread

作为一个Android 已封装好的轻量级异步类,其他他已经给我们做出给了突破,帮我们做到了系统的那一套消息传递机制,也帮我们省去自己造一个Thread+Handler的模式。

废了这么多话,其实讲实话啊,他就是我上面已经说过的Thread+Handler的模式,但是有一个事情要突破啊,Looper咋办?先放一下,让我们先看看他的使用方法吧。

使用方法

代码语言:javascript
复制
// 创建Handler实例
HandlerThread handlerThread  = new HandlerThread("MainActivity");
// 线程启动
handlerThread.start();
// 基于HandlerThread构建的Looper
Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                Log.e(TAG, msg.what+"");
            }
        };
// 使用handler发送消息
handler.sendEmptyMessage(1);
// 退出Lopper的死循环
handlerThread.quit();

思考

咦,哪里来的Looper呢???

那只好先让我们先看看HandlerThread的家庭里有哪些成员了。

代码语言:javascript
复制
// 一个继承自Thread的类
public class HandlerThread extends Thread {
    int mPriority; // 优先级
    int mTid = -1;
    Looper mLooper; // Looper
    private @Nullable Handler mHandler; // Handler
}

在上文中我们已经讲到过了,Looper指的是在ActivityThread中定义的,也就是一个全局型的Looper,并且他的初始化是在main()这个主入口进入时就已经初始化完毕的。

那我们这儿的Looper是在哪里进行初始化的呢?

代码语言:javascript
复制
@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare(); // 1 --》
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

在HandlerThread中我们能够看到这样的一段代码,从注释1我们慢慢深入,其实最后回到了我们文章最开始的一段代码。

代码语言:javascript
复制
// 上述注释1对应的函数
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)); // 2 -》
    }
// 对当前的线程和MessageQueue进行了存储    
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

温习一下,就是根据当前的Thread,来创建对应的Looper,并把他放到了ThreadLocal中进行存储。

总结

Q1:Handler的内存泄漏实例。

代码语言:javascript
复制
Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                startActivity(new Intent(MainActivity.this, HandlerTestActivity.class));
                return false;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 中断3秒
                SystemClock.sleep(3000);
                handler.sendEmptyMessage(0);
            }
        }).start();
    }

@Override
protected void onDestroy() {
        super.onDestroy();
        Log.e("onDestroy", "已销毁");
        handler.removeCallbacksAndMessages(0); // 2
        handler = null; // 1
    }

如果不加注释1和注释2,这就是一段会内存泄露的代码,看了代码,应该也清楚逻辑十分简单,就是一个跳转。推出程序后,你仍然会看到跳转,这就是Handler的内存泄漏。

Q2: 为什么Handler不能在子线程创建?

这个问题其实有点问题,对于修改过底层的华为的操作系统并不存在这样的问题,但是正常的Android原生系统就不行了,当然这针对的是无参构造函数,如果你通过传入一个Looper来解决,像这样handler = Handler(Looper.getMainLooper(), Callback()),也是没问题的。

代码如下

代码语言:javascript
复制
new Thread(new Runnable() {
            @Override
            public void run() {

                handler = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        return false;
                    }
                });
            }
        }).start();

通过对报错溯源,我们就能发现这样一个问题。

他拿不到Looper,因为他不是UI线程。其实这就是问题所在,我们上文讲过sThreadLocal这个变量,他通过一个get()函数获取了Looper。但是这里存在一个问题,这个get(),他获取的是什么。所以我们也就进去看看好了。

代码语言:javascript
复制
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

原来,他的取法就是从一个Map中进行获取的,而key,就是当前线程,所以当我们在子线程中创建Handler的时候,我们是拿不到Looper的,因为key并不对应。这个时候我们同样明白了Looper也是一个唯一的,因为他不会为我们创建出来的一个子线程再添加一个Looper,而是共用。

当然你要知道使用ThreadLocalMap是一个通过消耗空间来换取时间效率的方案。

Q3:为什么Handler构造方法里面的Looper不是new出来的?

这个问题的性质和问题2有点类似了,唯一性。Looper作为一个事件处理的重要组成部分,想来我们已经看到了,就像多道程序设计技术一样,这是一个不受控制的过程,我们需要疯狂的思考安全性,同步性等问题。这也是唯一性的好处,所以事件统一处理,处理起来也就有序。至少在我们的平时使用中已经证明了这是一个可取的方法。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DevGW 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MessageQueue中消息如果为空,该咋办???
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档