我要做 Android 之消息机制

简介: Android的消息机制指的是Handler的运行机制,本篇将总结Handler机制的相关知识点:消息机制概述消息机制分析1.消息机制概述a.作用:跨线程通信。

Android的消息机制指的是Handler的运行机制,本篇将总结Handler机制的相关知识点:

  • 消息机制概述
  • 消息机制分析
    1.消息机制概述

a.作用:跨线程通信。

b.常用场景:当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。

系统不建议在子线程访问UI的原因:UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:

  • 上锁会让UI控件变得复杂和低效
  • 上锁后会阻塞某些进程的执行

c.四要素:

  • Message(消息):需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
  • MessageQueue(消息队列):用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
  • Handler(处理者)
    :负责Message的发送及处理。
    • Handler.sendMessage():向消息池发送各种消息事件。
    • Handler.handleMessage() :处理相应的消息事件。
  • Looper(消息泵):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。

Thread(线程):负责调度整个消息循环,即消息循环的执行场所。

存在关系:

  • 一个Thread只能有一个Looper,可以有多个Handler;
  • Looper有一个MessageQueue,可以处理来自多个Handler的Message;
  • MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;
  • Message中记录了负责发送和处理消息的Handler;
  • Handler中有Looper和MessageQueue;
img_b47ceca8d02f74cbb57e4652fc367d82.jpe
01.png
img_5d589363e2ff0560f30cc51a01b40d35.jpe
02.png

d.实现方法:

  • 在主线程实例化一个全局的Handler对象;
  • 在需要执行UI操作的子线程里实例化一个Message并填充必要数据,调用Handler.sendMessage(Message msg)方法发送出去;
  • 复写handleMessage()方法,对不同Message执行相关操作;

2.消息机制分析

a.工作流程:

  • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息;
  • 通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next();
  • 调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。

简单来看,即Handler将Message发送到Looper的成员变量MessageQueue中,之后Looper不断循环遍历MessageQueue从中读取Message,最终回调给Handler处理。如图:

img_6f71a93ba352c04e23c1aeb2d7ffb83b.png
03.png

b.工作原理:

  • (1)Looper的创建:先从应用程序的入口函数ActivityThread.main()看起,在这里(主线程)系统自动创建了Looper,主要方法:
//主线程中不需要自己创建Looper
public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
        ......
        Looper.loop();//开启消息轮询
        ......
        
    }

注意:

  • 子线程的Looper需要手动去创建,标准写法是:
//子线程中需要自己创建一个Looper
new Thread(new Runnable() {
            @Override
            public void run() {
                
                Looper.prepare();//为子线程创建Looper               
                Looper.loop(); //开启消息轮询
            }
        }).start();

  • 无论是主线程还是子线程,Looper只能被创建一次,即一个Thread只有一个Looper。

  • 所创建的Looper会保存在ThreadLocal(线程本地存储区)中,它不是线程,作用是帮助Handler获得当前线程的Looper。更多讲解见ThreadLocal详解

  • (2)MessageQueue的创建:在Looper的构造函数创建了一个MessageQueue:

 private Looper(boolean quitAllowed) {
       
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

(3)Message轮询及处理:有了Looper和MessageQueue之后通过Looper.loop()开启消息轮询:

public static void loop() {
        ......
        for (;;) {//死循环
            Message msg = queue.next(); //用于提取下一条信息,该方法里同样有个for(;;)死循环,当没有可处理该Message的Handler时,会一直阻塞
            if (msg == null) {
                
                return;
            }
         ......   
         try {
                msg.target.dispatchMessage(msg);//如果从MessageQueue中拿到Message,由和它绑定的Handler(msg.target)将它发送到MessageQueue
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......   
    }

现在就剩创建Handler及Message发送了。(4)先看Handler的创建:有两种形式的Handler:

//第一种:send方式的Handler创建
Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //如UI操作
                
            }
        };

//第二种:post方式的Handler创建
Handler handler = new Handler();
  • (5) Message的发送:

对于send方式的Handler:创建好一个Message后,调用Handler的以下几种常见的方法来发送消息:

sendEmptyMessage();           //发送空消息
sendEmptyMessageAtTime();     //发送按照指定时间处理的空消息
sendEmptyMessageDelayed();    //发送延迟指定时间处理的空消息
sendMessage();                //发送一条消息
sendMessageAtTime();          //发送按照指定时间处理的消息
sendMessageDelayed();         //发送延迟指定时间处理的消息
sendMessageAtFrontOfQueue();  //将消息发送到消息队头

对于post方式的Handler,可在子线程直接调用Handler的以下几种常见方法,使得切换到主线程:

post(Runnable r)
postAtFrontOfQueue(Runnable r)
postAtTime(Runnable r, Object token, long uptimeMillis)
postAtTime(Runnable r, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)

//例如,postDelayed()方法
handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //如UI操作
                    }
                },300);

通过以上各种Handler的发送方法,都会依次调用 Handler.sendMessageDelayed->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage() 最终将Message发送到MessageQueue。

img_26f24c0a65fb71447548874d3d9294f1.png
04.png

Q:Message可以如何创建?哪种效果更好,为什么?

创建Message对象的时候,有三种方式,分别为:
1.Message msg = new Message();
2.Message msg2 = Message.obtain();
3.Message msg1 = handler1.obtainMessage();
这三种方式有什么区别呢?

Message msg = new Message();这种就是直接初始化一个Message对象,没有什么特别的。
2)Message msg2 = Message.obtain();

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

从注释可以得知,从整个Messge池中返回一个新的Message实例,通过obtainMessage能避免重复Message创建对象。

Message msg1 = handler1.obtainMessage();

public final Message obtainMessage()
    {
        return Message.obtain(this);
    }1234
public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

可以看到,第二种跟第三种其实是一样的,都可以避免重复创建Message对象,所以建议用第二种或者第三种任何一个创建Message对象。

Q:主线程中Looper的轮询死循环为何没有阻塞主线程?

正如我们所知,在android中如果主线程中进行耗时操作会引发ANR(Application Not Responding)异常。

造成ANR的原因一般有两种:

  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
  2. 当前的事件正在处理,但没有及时完成

为了避免ANR异常,android使用了Handler消息处理机制。让耗时操作在子线程运行。

因此产生了一个问题,主线程中的Looper.loop()一直无限循环检测消息队列中是否有新消息为什么不会造成ANR?

  while (true) {



       //取出消息队列的消息,可能会阻塞



       Message msg = queue.next(); // might block



       ...



       //解析消息,分发消息



       msg.target.dispatchMessage(msg);



       ...



    }

显而易见的,如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出。

总结:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。

如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。

而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

目录
相关文章
|
7月前
|
前端开发 编译器 Android开发
构建高效Android应用:探究Kotlin协程的异步处理机制
【4月更文挑战第2天】在现代移动应用开发中,提供流畅且响应迅速的用户体验是至关重要的。随着Android平台的发展,Kotlin语言凭借其简洁性和功能性编程的特点成为了主流选择之一。特别地,Kotlin协程作为一种新型的轻量级线程管理机制,为开发者提供了强大的异步处理能力,从而显著提升了应用程序的性能和响应速度。本文将深入探讨Kotlin协程在Android中的应用,分析其原理、实现以及如何通过协程优化应用性能。
|
20天前
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
33 1
|
7月前
|
存储 Java Android开发
Android系统升级的机制概要
Android系统升级的机制概要
129 0
|
25天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
42 2
|
19天前
|
Linux Android开发 iOS开发
深入探索Android与iOS的多任务处理机制
在移动操作系统领域,Android和iOS各有千秋,尤其在多任务处理上展现出不同的设计理念和技术实现。本文将深入剖析两大平台在后台管理、资源分配及用户体验方面的策略差异,揭示它们如何平衡性能与电池寿命,为用户带来流畅而高效的操作体验。通过对比分析,我们不仅能够更好地理解各自系统的工作机制,还能为开发者优化应用提供参考。
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
58 1
|
2月前
|
存储 安全 数据安全/隐私保护
探索安卓与iOS的隐私保护机制####
【10月更文挑战第15天】 本文深入剖析了安卓和iOS两大操作系统在隐私保护方面的策略与技术实现,旨在揭示两者如何通过不同的技术手段来保障用户数据的安全与隐私。文章将逐一探讨各自的隐私控制功能、加密措施以及用户权限管理,为读者提供一个全面而深入的理解。 ####
68 1
|
2月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
53 2