IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android消息机制——Handler -> 正文阅读

[移动开发]Android消息机制——Handler


不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!

Handler机制

参考博客:

Android消息机制学习笔记

Android Handler消息机制原理最全解读(持续补充中)

Android消息机制全面解析

作用

主要是用于不同线程之间的通信。通常在子线程做完耗时操作后,通过handler切换为主线程执行UI操作。

对象

  • Handler

消息的发送和处理对象的控制器

  • Message

消息的载体

  • MessageQueue

存储消息的优先级队列

  • Looper

控制消息队列的循环

工作流程

  • app启动时,在ActivityThread的main方法中会调用Looper.prepare()方法,初始化looper并绑定主线程。looper对象内部还维护一个MessageQueue。
  • 刚开始,通过handler的sendMessage方法发送消息,然后调用MessageQueue.enqueueMessage方法向消息队列中加入消息。
  • 主线程调用Looper.loop方法遍历队列,用MessageQueue.next()取出消息。
  • 取出的消息不为空,那么调用msg.target.dispatchMessage()来分发消息。目标handler收到消息后,通过handler.handlerMessage()处理消息。

源码分析

  • 从子线程的sendMessage方法开始
//新建一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟耗时操作
                SystemClock.sleep(3000);
                //处理完耗时操作后,发送消息给主线程
                handler.sendMessage(new Message());
            }
        }).start();

Handler中的方法

  • 后面会发现,最终都调用了sendMessageDelayed方法
 public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }


    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        //从消息池取出消息,节省内存分配
        Message msg = Message.obtain();
        //设置消息的标识
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
  • 进入sendMessageDelayed方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        //推迟时间不合理
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

发现其调用了sendMessageAtTime方法,这个方法将延迟多少时间执行,变成了一个准确的执行时刻。

  • 进入sendMessageAtTime方法
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);
    }

拿到与handler关联的Looper中的消息队列,然后调用enqueueMessage方法将消息放入消息队列。

  • 进入enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将消息的目标指向handler
        //消息与handler关联
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //将消息插入队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }

将消息与它的目标handler关联,然后执行MessageQueue中的enqueueMessage方法,将消息插入队列。

MessageQueue中的方法

  • 基本只需要看一个方法,就是将消息插入消息队列
//向MessageQueue中插入这条消息
    //根据when的优先级队列,单链表数据结构
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            //消息是否有目标handler
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                //当前消息是否被使用
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) {
                //Handler所在线程是否存活
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //链表为空,或者时间是0,立刻执行,或者执行时间比表头执行时间早
            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; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在消息队列中加入消息前,需要判断:

  1. 这条消息是否有关联的handler。
  2. 这条消息是否已经被使用。
  3. 这条消息所关联的handler所在的线程是否存活。

当这三个条件都满足的时候,才可以插入消息,否则报错。

插入消息也有两种情况:

  1. 在表头插入消息

当这个消息队列为空;或者when等于0,意味着立刻执行;或者需要插入的消息的when小于表头消息的when,那么就在表头插入这条消息。

  1. 在适当位置插入消息

适当位置插入,那么就得先找到这个适当位置。从头开始,死循环遍历消息队列,当到了消息队列的最后或者找到了一个消息的when大于这条消息的when,那么就在这个消息的前面插入即可。

过渡

通过handler发送了消息,并且也通过MessageQueue插入消息到消息队列中了,接下来,就该从队列中取出消息进行处理了,所以需要循环消息队列,需要Looper的loop方法。

Looper中的方法

  • Looper的作用就是循环消息队列,看loop方法
//开启Looper
    public static void loop() {
        //返回当前线程的Looper
        final Looper me = myLooper();
        //当前线程没有looper,报错
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        //返回和当前线程关联的looper中维护的消息队列
        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        //无限循环,死循环
        for (;;) {
            //不断从队列取消息
            Message msg = queue.next(); // might block
            //到了末尾,队列中没有消息需要处理了
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }private Handler handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    textView.setText("对UI进行操作");
                }
            };
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            
            //msg.target的dispatchMessage传递消息,即就是handler的这个方法
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
  • 由于loop方法太长了,截取一些核心代码进行查看
//开启Looper
    public static void loop() {
        //返回当前线程的Looper
        final Looper me = myLooper();
        //当前线程没有looper,报错
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        ......
        //返回和当前线程关联的looper中维护的消息队列
        final MessageQueue queue = me.mQueue;
        ......
        //无限循环,死循环
        for (;;) {
            //不断从队列取消息
            Message msg = queue.next(); 
            //到了末尾,队列中没有消息需要处理了
            if (msg == null) {
                return;
            }
            ......
            //msg.target的dispatchMessage传递消息,即就是handler的这个方法
            try {
                //msg.target就是指向与这个消息关联的handler,所以通过Handler的dispatchMessage方法进行消息传递
                msg.target.dispatchMessage(msg);
                ......
            //将处理后需要回收的消息放入消息池缓存
            msg.recycleUnchecked();
        }
    }

回到Handler中

  • 通过Handler.dispatchMessage去传递消息
//传递消息
    public void dispatchMessage(@NonNull Message msg) {
        //若消息的callback不空
        if (msg.callback != null) {
            //调用这个方法
            handleCallback(msg);
        } else {
            //自己的callback是否空
            if (mCallback != null) {
                //不空,传给自己的callback
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //实在不行,调用自己的handleMessage方法
            handleMessage(msg);
        }
    }

从源码中可以看出,若是有Callback接口,那么就会直接回调handleCallback方法。没有的话,只能调用自定义的handleMessage方法去处理消息了。

补充

Handler中的内存泄露问题

出现原因

  1. 非静态内部类导致的内存泄漏。
  2. MessageQueue中还有没有处理完的message,message的target即handler对Activity仍然持有引用,会导致内存泄漏。

解决方案

  1. 使用静态内部类 + 弱引用解决。
  2. 在销毁Activity时,清除其绑定的looper中维护的MessageQueue中的所有消息。

Handler中的Message对象的获取问题

问题:在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?

答:使用obtain创建对象是从消息池中取出Message对象,而new一个新对象,需要为其单独分配一块新的内存,这样可以节省内存的开销。

注意点

  • 一个线程只能有一个Looper,内部维护着一个唯一的MessageQueue,但是可以有多个Handler。

  • 创建的Looper都会保存在ThreadLocal(线程本地存储区)中。它不是线程,是一个内部存储类。作用是获取当前线程的looper。它可以在不同线程间互不干扰的存储和提供数据。

  • MessageQueue是一个时间优先级队列,但其内部是使用单链表的形式存储,根据时间,将消息插入队列中,表头的先出。

  • 通过Looper取出消息,然后通过handler分发消息到handler所在的线程,即进行了不同的线程间通信。

面试问题

  1. 为什么一个线程只有一个Looper、只有一个MessageQueue?

  2. 如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)

  3. 是不是任何线程都可以实例化Handler?有没有什么约束条件?

  4. Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

  5. 消息循环(死循环)的必要性:

  6. looper.looper()阻塞会不会消耗大量的cpu资源

  7. Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

  8. sendMessageDelayed是如何实现延时发送消息的?

  9. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-06 11:16:16  更:2021-09-06 11:17:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:41:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码