Android 消息机制 你了解多少

级别:★★☆☆☆
标签:「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 使用及原理简析
奇舞周刊

你可能感兴趣的:(Android 消息机制 你了解多少)