级别:★★☆☆☆
标签:「Handler」「Android」「消息机制」「内存泄漏」
作者: Zoyp晨
审校: aTaller团队

Handler 原理

Handler基本使用

在Android中使用Handler来切换线程。Android不允许在子线程进行UI操作,也不允许在UI线程(主线程)进行网络请求。在开发中一个非常常见的场景是:子线程中进行网络请求,当网络请求完毕,将数据回调回主线程,这中间用到的角色就是Handler。

  • 为什么Android的主线程不能进行网络请求?
    用户体验不佳,视图展示卡顿
  • 为什么Android的子线程不能进行UI操作?
    Android的UI控件线程不安全
  • 如果要进行子线程切换到主线程的操作,一定要在主线程创建handler吗?
  • 为什么使用Handler有时会IDE会提示可能发生内存泄漏?

相信大家看完本文之后一定可以知道上面几个问题的答案

Handler源码浅析

首先介绍一下Handler体系中的四大组件:

  1. MessageQueue: 是一种元素为Message的单链表的数据结构,线程单例
  2. Looper:MessageQueue是Looper的一个成员变量,线程单例
  3. Message:消息的载体
  4. Handler:处理消息

还有一个很重要的类:ThreadLocal,Looper就是凭借着TheadLocal这个类的能力来实现线程单例。

Handler的发送消息的流程

先从最熟悉的Handler.sengMessage(Message msg)方法看起:

public final boolean sendMessage(@NonNull Message msg) {        return sendMessageDelayed(msg, 0);    }
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {        if (delayMillis < 0) {            delayMillis = 0;        }        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;        if (queue == null) {            RuntimeException e = new RuntimeException(                    this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);            return false;        }        return enqueueMessage(queue, msg, uptimeMillis);    }

上面三个方法的调用链依次为

sendMessage -> sendMessageDelayed -> sendMessageAtTime

sendMessage方法去调用sendMessageDelayed方法,将delaytime设置为0,让Handler立即从Message队列中取出这条消息.sendMessageAtTime方法将相对时间转换为绝对时间。最后通过enqueueMessage方法来将消息入队,这里的队列就是线程单例的MessageQueue。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,            long uptimeMillis) {        msg.target = this;  //标重点        msg.workSourceUid = ThreadLocalWorkSource.getUid();        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }
为什么使用匿名内部类来创建Handler的方法会有内存泄漏的风险?

就是因为上面那一句话

msg.target = this;

Handler引用链

构成上面的引用链的原因:

  • 匿名内部类实现的方法隐式的持有外部类的引用,通常也就是Activity
  • Message的targer字段引用着Handler
  • 发送的是一条延时消息,message在Messagequeue中一段事件得不到处理

接着往下看:

    boolean enqueueMessage(Message msg, long when) {        synchronized (this) {                       msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p;                prev.next = msg;            }                        if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

enqueueMessage方法按照延迟时间的从小到大顺序将 Message插入队列中的适当。

到这里MessageQueue的入队操作就分析完毕了

主线程开启Looper循环的流程

app的启动流程
  • Zygote进程通过轮训,查询当前是否有启动app的请求消息。
  • 当我们从Laucher(桌面)启动一个app时,SystemServer进程发送消息到Zygote。
  • Zygote通过fork为app创建子进程
  • 子进程通过反射调用android.app.ActivityThread.main()

可以简单理解为 ActivithTread这个类是安卓中的应用程序的入口

main函数
public static void main(String[] args) {        ...        Looper.prepareMainLooper();        ...        ActivityThread thread = new ActivityThread();        thread.attach(false, startSeq);        ...        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        Looper.loop();        throw new RuntimeException("Main thread loop unexpectedly exited");    }

其中与Handler机制相关的两行代码,我们挨着分析:

  1. prepareMainLooper
Looper.prepareMainLooper()

为主线程创建了Looper实例,并通过Looper创建了MessageQueue实例,其中MessageQueueLooper的成员变量,Looper通过Threadlocal实现了线程单例,MessagQueue当然也成为线程单例。

  1. loop
    为主线程开启消息循环
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            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }                ...            try {                    //  这里通过msg.target  将取出的消息回调给发送消息的那个handler                msg.target.dispatchMessage(msg);               ...            } catch (Exception exception) {                if (observer != null) {                    observer.dispatchingThrewException(token, msg, exception);                }                throw exception;            } finally {                ThreadLocalWorkSource.restore(origWorkSource);                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }                        ...            msg.recycleUnchecked();        }    }

通过msg.target字段,将取出的消息回调给我们在主线程创建的Handler,这就是为什么前面在发送消息的流程中,我们需要Message去持有Hadnler的引用,这里就将逻辑转移到了Handler的dispatchMessage方法中,开启分发消息的流程。

Observer

还有一个平常没有注意到的东西:Observer

public static void setObserver(@Nullable Observer observer) {        sObserver = observer;    }
 public interface Observer {                Object messageDispatchStarting();               void messageDispatched(Object token, Message msg);               void dispatchingThrewException(Object token, Message msg, Exception exception);    }

我们可以通过looper注册观察者,来观察到从looper中取出后、分发前的这个时机的Message,对此进行一些打印日志或者一些其他的操作

Handler分发消息的流程

    public void dispatchMessage(@NonNull Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }

绕了一大圈,终于看到 handleMessage这个方法了。dispatchMessage方法中对与message的分发有三种结果,分别是 message携带的callback字段handler本身的callback字段handleMessage方法,其中hadnleMessage方法应该大家平常最常用。而msg的callback字段,和handler的callback字段也是可以对消息进行处理,读者有兴趣的话可以自己去敲代码实验一下如何用前两种callback的方法对消息进行处理。

总结

我们通过对源码的分析,基于Message的传递路线绕了一圈,终于有头有尾的走了一遍下来。我们再来回顾一下:通过在主线程创建了Handler,然后在子线程中通过主线程创建的Handler实例将携带信息的message发送到handler绑定的messagequeue中,跑在主线程的LoopermessagemessageQueue取出来,通过messagetarget字段对message调用dispatchMessage()进行分发,最终将消息回调给我们刚开始重写的handleMessage方法

关注我们的途径有:
aTaller()
aTaller(掘金)
aTaller(微信公众号)

推荐文章:
Flutte 开发小技巧
Flutter 常用 Widget 介绍
Flutter 图片加载
Flutter 混合栈复用原理
Flutter Platform Channel 使用与源码分析
Flutter Platform View 使用及原理简析
奇舞周刊

更多相关文章

  1. Android中的线程模型
  2. Android轮播图控件CustomBanner的使用讲解
  3. Android(安卓)Java 中Thread与Runnable的区别
  4. Android自定义属性时TypedArray的使用方法
  5. Android之Activity概述
  6. java版android Handler机制模型
  7. Android直播系统平台搭建之图片实现阴影效果的方法小结
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. Android(安卓)Touch事件传递机制解析
  2. Android 5.1 open data flow 数据开启流
  3. Android selector(背景选择器) , shape(
  4. 【Android】Android 开机广播的使用
  5. Android里merge和include标签的使用
  6. Android开发贴士集合(Part 1~4)
  7. Android修改自己程序字体
  8. Android Studio下添加assets目录
  9. Android菜单详解
  10. 跟大家分享下Android布局文件layout.xml