`

剖析Android消息机制

 
阅读更多
剖析Android消息机制
在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。在学习Android中消息机制之前,我们先了解与消息有关的几个类:

1.Message

消息对象,顾名思义就是记录消息信息的类。这个类有几个比较重要的字段:

a.arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。

b.obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。

c.what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button事件时,通过switch(v.getId())判断是点击了哪个按钮。

在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。

2.MessageQueue

View Code
消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。我们可以通过Looper.myQueue()获取当前线程中的MessageQueue。3.LooperMessageQueue的管理者,在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个MessageQueue对象。在Android系统中,除了主线程有默认的Looper对象,其它线程默认是没有Looper对象。如果想让我们新创建的线程拥有Looper对象时,我们首先应调用Looper.prepare()方法,然后再调用Looper.loop()方法。典型的用法如下:class LooperThread extends Thread{    public Handler mHandler;    public void run()    {        Looper.prepare();        //其它需要处理的操作        Looper.loop();    }}
倘若我们的线程中存在Looper对象,则我们可以通过Looper.myLooper()获取,此外我们还可以通过Looper.getMainLooper()获取当前应用系统中主线程的Looper对象。在这个地方有一点需要注意,假如Looper对象位于应用程序主线程中,则Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。

4.Handler

消息的处理者。通过Handler对象我们可以封装Message对象,然后通过sendMessage(msg)把Message对象添加到MessageQueue中;当MessageQueue循环到该Message时,就会调用该Message对象对应的handler对象的handleMessage()方法对其进行处理。由于是在handleMessage()方法中处理消息,因此我们应该编写一个类继承自Handler,然后在handleMessage()处理我们需要的操作。

下面我们通过跟踪代码分析在Android中是如何处理消息。首先贴上测试代码:

View Code
/** *  * @author coolszy * @blog http://blog.csdn.net/coolszy * */public class MessageService extends Service{    private static final String TAG = "MessageService";    private static final int KUKA = 0;     private Looper looper;    private ServiceHandler handler;     /**     * 由于处理消息是在Handler的handleMessage()方法中,因此我们需要自己编写类     * 继承自Handler类,然后在handleMessage()中编写我们所需要的功能代码     * @author coolszy     *     */    private final class ServiceHandler extends Handler    {        public ServiceHandler(Looper looper)        {            super(looper);        }         @Override        public void handleMessage(Message msg)        {            // 根据what字段判断是哪个消息            switch (msg.what)            {            case KUKA:                //获取msg的obj字段。我们可在此编写我们所需要的功能代码                Log.i(TAG, "The obj field of msg:" + msg.obj);                break;            // other cases            default:                break;            }            // 如果我们Service已完成任务,则停止Service            stopSelf(msg.arg1);        }    }     @Override    public void onCreate()    {        Log.i(TAG, "MessageService-->onCreate()");         // 默认情况下Service是运行在主线程中,而服务一般又十分耗费时间,如果        // 放在主线程中,将会影响程序与用户的交互,因此把Service        // 放在一个单独的线程中执行        HandlerThread thread = new HandlerThread("MessageDemoThread", Process.THREAD_PRIORITY_BACKGROUND);        thread.start();         // 获取当前线程中的looper对象        looper = thread.getLooper();        //创建Handler对象,把looper传递过来使得handler、        //looper和messageQueue三者建立联系        handler = new ServiceHandler(looper);    }     @Override    public int onStartCommand(Intent intent, int flags, int startId)    {        Log.i(TAG, "MessageService-->onStartCommand()");         //从消息池中获取一个Message实例        Message msg = handler.obtainMessage();        // arg1保存线程的ID,在handleMessage()方法中        // 我们可以通过stopSelf(startId)方法,停止服务        msg.arg1 = startId;        // msg的标志        msg.what = KUKA;        // 在这里我创建一个date对象,赋值给obj字段        // 在实际中我们可以通过obj传递我们需要处理的对象        Date date = new Date();        msg.obj = date;        // 把msg添加到MessageQueue中        handler.sendMessage(msg);         return START_STICKY;    }     @Override    public void onDestroy()    {        Log.i(TAG, "MessageService-->onDestroy()");    }     @Override    public IBinder onBind(Intent intent)    {        return null;    }}
运行结果:



注:在测试代码中我们使用了HandlerThread类,该类是Thread的子类,该类运行时将会创建looper对象,使用该类省去了我们自己编写Thread子类并且创建Looper的麻烦。

下面我们分析下程序的运行过程:

1.onCreate()

首先启动服务时将会调用onCreate()方法,在该方法中我们new了一个HandlerThread对象,提供了线程的名字和优先级。

紧接着我们调用了start()方法,执行该方法将会调用HandlerThread对象的run()方法:

View Code
public void run() {        mTid = Process.myTid();        Looper.prepare();        synchronized (this) {            mLooper = Looper.myLooper();            notifyAll();        }        Process.setThreadPriority(mPriority);        onLooperPrepared();        Looper.loop();        mTid = -1;    }
在run()方法中,系统给线程添加的Looper,同时调用了Looper的loop()方法:

View Code
public static final void loop() {        Looper me = myLooper();        MessageQueue queue = me.mQueue;        while (true) {            Message msg = queue.next(); // might block            //if (!me.mRun) {            //    break;            //}            if (msg != null) {                if (msg.target == null) {                    // No target is a magic identifier for the quit message.                    return;                }                if (me.mLogging!= null) me.mLogging.println(                        ">>>>> Dispatching to " + msg.target + " "                        + msg.callback + ": " + msg.what                        );                msg.target.dispatchMessage(msg);                if (me.mLogging!= null) me.mLogging.println(                        "<<<<< Finished to    " + msg.target + " "                        + msg.callback);                msg.recycle();            }        }    }
通过源码我们可以看到loop()方法是个死循环,将会不停的从MessageQueue对象中获取Message对象,如果MessageQueue 对象中不存在Message对象,则结束本次循环,然后继续循环;如果存在Message对象,则执行 msg.target.dispatchMessage(msg),但是这个msg的.target字段的值是什么呢?我们先暂时停止跟踪源码,返回到onCreate()方法中。线程执行完start()方法后,我们可以获取线程的Looper对象,然后new一个ServiceHandler对象,我们把Looper对象传到ServiceHandler构造函数中将使handler、looper和messageQueue三者建立联系。

2.onStartCommand()

执行完onStart()方法后,将执行onStartCommand()方法。首先我们从消息池中获取一个Message实例,然后给Message对象的arg1、what、obj三个字段赋值。紧接着调用sendMessage(msg)方法,我们跟踪源代码,该方法将会调用sendMessageDelayed(msg, 0)方法,而sendMessageDelayed()方法又会调用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在该方法中我们要注意该句代码msg.target = this,msg的target指向了this,而this就是ServiceHandler对象,因此msg的target字段指向了ServiceHandler对象,同时该方法又调用MessageQueue 的enqueueMessage(msg, uptimeMillis)方法:

View Code
final boolean enqueueMessage(Message msg, long when) {        if (msg.when != 0) {            throw new AndroidRuntimeException(msg                    + " This message is already in use.");        }        if (msg.target == null && !mQuitAllowed) {            throw new RuntimeException("Main thread not allowed to quit");        }        synchronized (this) {            if (mQuiting) {                RuntimeException e = new RuntimeException(                    msg.target + " sending message to a Handler on a dead thread");                Log.w("MessageQueue", e.getMessage(), e);                return false;            } else if (msg.target == null) {                mQuiting = true;            }             msg.when = when;            //Log.d("MessageQueue", "Enqueing: " + msg);            Message p = mMessages;            if (p == null || when == 0 || when < p.when) {                msg.next = p;                mMessages = msg;                this.notify();            } else {                Message prev = null;                while (p != null && p.when <= when) {                    prev = p;                    p = p.next;                }                msg.next = prev.next;                prev.next = msg;                this.notify();            }        }        return true;    }
该方法主要的任务就是把Message对象的添加到MessageQueue中(数据结构最基础的东西,自己画图理解下)。

handler.sendMessage()-->handler.sendMessageDelayed()-->handler.sendMessageAtTime()-->msg.target = this;queue.enqueueMessage==>把msg添加到消息队列中

3.handleMessage(msg)

onStartCommand()执行完毕后我们的Service中的方法就执行完毕了,那么handleMessage()是怎么调用的呢?在前面分析的loop()方法中,我们当时不知道msg的target字段代码什么,通过上面分析现在我们知道它代表ServiceHandler对象,msg.target.dispatchMessage(msg);则表示执行ServiceHandler对象中的dispatchMessage()方法:

View Code
public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }
该方法首先判断callback是否为空,我们跟踪的过程中未见给其赋值,因此callback字段为空,所以最终将会执行handleMessage()方法,也就是我们ServiceHandler类中复写的方法。在该方法将根据what字段的值判断执行哪段代码。

至此,我们看到,一个Message经由Handler的发送,MessageQueue的入队,Looper的抽取,又再一次地回到Handler的怀抱中。而绕的这一圈,也正好帮助我们将同步操作变成了异步操作。

分享到:
评论

相关推荐

    称重装置(CAD装配图).rar

    称重装置(CAD装配图).rar

    四轮轮毂电机驱动汽车DYC稳定性控制:基于最优分配理论的横摆力矩与轨迹跟踪控制研究 车辆工程

    内容概要:本文详细探讨了四轮轮毂电机驱动汽车(IEV)的动态横摆力矩控制(DYC),旨在提高车辆在不同附着系数路面上的稳定性。文章介绍了上下两层控制器的设计思路及其协同工作方式。上层控制器负责生成横摆力矩Mz,支持多种控制策略如滑膜控制(SMC)、线性二次调节(LQR)、模型预测控制(MPC),并提供了一段MATLAB代码示例来展示具体的实现方法。下层控制器则专注于将横摆力矩最优地分配到四个车轮,采用二次规划等高级算法确保每个车轮获得合适的驱动力。此外,文中还提到了Simulink与CarSim联合仿真的应用技巧以及一些实用的经验分享,例如如何避免高频抖振、优化参数调整、解决轮胎模型延迟等问题。 适合人群:从事自动驾驶、智能交通系统、电动汽车等领域研究的技术人员,尤其是对车辆动态控制感兴趣的工程师。 使用场景及目标:适用于需要深入理解四轮轮毂电机驱动汽车稳定性控制机制的研究项目,帮助研究人员掌握从理论到实践的具体步骤和技术细节,从而更好地应用于实际产品开发中。 其他说明:文中提供的代码片段和实践经验对于理解和实施复杂的车辆控制系统非常有价值,同时强调了在不同条件下保持车辆稳定性的挑战性和重要性。

    板栗脱蓬机的设计【板栗去毛刺机】【板栗去外壳机】.rar

    板栗脱蓬机的设计【板栗去毛刺机】【板栗去外壳机】.rar

    基于定子磁场定向矢量控制的异步电机磁链观测模型研究与应用 磁链观测

    内容概要:本文深入探讨了基于定子磁场定向矢量控制的异步电机磁链观测模型。首先介绍了磁链观测器的核心代码,包括电压模型和电流模型的关键部分。文中提到实际工程中常见的挑战,如参数漂移、传感器噪声以及温度变化对定子电阻的影响,并提出了相应的解决方案,如在线参数辨识和改进的坐标变换方法。此外,还讨论了不同模型之间的平滑切换策略,特别是带速度前馈的混合观测方法,有效降低了转矩脉动。最后强调了磁链观测在电机控制系统中的重要性和复杂性。 适合人群:从事电机控制领域的工程师和技术人员,尤其是对异步电机磁链观测感兴趣的读者。 使用场景及目标:适用于需要深入了解和解决异步电机磁链观测问题的实际工程项目。目标是提高磁链观测的精度和稳定性,从而提升电机控制系统的性能。 其他说明:本文不仅提供了理论分析,还分享了许多实践经验,对于理解和应对实际工程中的各种问题非常有帮助。

    AP4050DN FIT转FAT 包含固件和所需软件 要用console线 实测OK.zip

    AP4050DN FIT转FAT 包含固件和所需软件 要用console线 实测OK.zip

    综合能源系统规划中多目标粒子群算法MOPSO与多目标免疫算法NNIA的应用研究 粒子群算法

    内容概要:本文探讨了利用多目标粒子群算法(MOPSO)和多目标免疫算法(NNIA)解决综合能源系统的规划问题。文中详细介绍了这两种算法的工作原理及其具体实现方式,如粒子群算法中的粒子更新规则和免疫算法中的克隆操作。同时,文章还讨论了如何通过引入外部存档和拥挤度计算来提高解的多样性和分布均匀性。此外,作者分享了一些实践经验,指出MOPSO适用于快速收敛,而NNIA则在处理复杂环境变化方面表现更好。为了应对实际应用中的挑战,文中提出了一种结合惩罚项的目标函数设计方法,以处理设备启停等约束条件。最后强调了将算法与地理信息系统(GIS)相结合的重要性。 适合人群:从事综合能源系统规划的研究人员和技术人员,尤其是那些希望深入了解多目标优化算法在该领域的应用的人。 使用场景及目标:①需要进行高效、稳定的综合能源系统规划;②希望通过多目标优化算法平衡成本、环境影响和系统稳定性之间的关系;③寻求理论与实践相结合的方法论指导。 其他说明:尽管MOPSO和NNIA各有优势,但在实际项目中往往需要结合两者的特点,发挥各自长处,同时注意与其他工程技术手段相融合。

    室内场景三维重建-基于单目RGBD相机实现的实时室内场景3D重建算法-附项目源码+流程教程-优质项目实战.zip

    室内场景三维重建_基于单目RGBD相机实现的实时室内场景3D重建算法_附项目源码+流程教程_优质项目实战

    清华TH-OCR v9.0免费版

    今天向大家介绍一款非常好用的单机版OCR图文识别软件,它不仅功能多,识别能力强,而且还是免费使用的。OCR软件为什么要使用单机版,懂得都懂,因为如果使用在线识别的OCR软件,用户需要将文档上传互联网服务器的,这样就会导致某些敏感信息暴露在互联网上,导致信息泄露。 软件特色:   1、识别率高、速度快:对于被划分区域内的文字有很高的识别率,而且速度同样很快。   2、导出功能:清华TH-OCR官方版可以将带有表格的文当导出成为RTF格式的文件,从而允许用户在Word等应用程序中继续进行编辑。   3、版面自动分析:对图文混排的文件具有版面自动分析功能,它自动对扫描的版面进行分析,把应识别的文字区域划分出来,之后进行识别。   4、转换图像格式:将扫描进来的图像格式转换成TIFF、BMP或PCZ等格式,具有很大的灵活性。   5、批量识别:可以让用户一次把多页文稿全部扫描之后再进行识别,避免了扫描一页识别一页带来的麻烦,这一版本最多可实现10000页的批量识别。   6、手写体识别:手写的信件或文件就可以扫描到计算机中,识别出来后用电子文档的方式进行保存。   7、自学习:当遇到有生僻字时,可以通过键盘输入进行学习,用户就可以自由地添加一些本来不“认识”的字,大大拓宽了中文OCR系统的识别字符集。   8、排版功能:汉字和英文混排、日文和英文混排、韩文和英文混排同时识别。   9、识别能力:是唯一可以识别2万多汉字的多体文字识别系统,汉字识别国内最优。   10、支持多接口:文通TH-OCR支持WINDOWS环境和GB、BIG5、GBK、JIS、 SHIFT-JIS和KSC等多种内码,适合全球各个地区使用。TH-OCR还具有自学习功能,不论什么生僻字,都可以通过键盘输入进行学习,大大拓宽了OCR系统的识别字符集。

    附件4:大学生创新训练项目计划申请书(1).doc

    附件4:大学生创新训练项目计划申请书(1).doc

    纯DOS批处理脚本:去掉快捷方式箭头

    纯DOS批处理脚本:去掉快捷方式箭头

    图论算法基于Tarjan算法的有向图强连通分量求解及其应用:网络结构与依赖关系分析

    内容概要:本文详细介绍了Tarjan算法及其在求解有向图强连通分量(SCC)中的应用。首先解释了连通性的概念,区分了无向图和有向图的连通分量,重点阐述了有向图中强连通分量的定义及其重要性,包括编译器优化、社交网络分析、电子电路设计和生态系统建模等领域。接着介绍了Tarjan算法的优势,如单次DFS遍历、线性时间复杂度和高效的空间利用。文章深入解析了算法的实现细节,包括发现时间数组、最低访问数组、栈状态标记和栈等数据结构的作用。最后,探讨了基于Tarjan算法的拓展应用,如图的缩点技术和2-SAT问题求解,展示了其在依赖关系分析、路径优化、控制流分析和任务调度等方面的应用。 适合人群:具备一定图论基础和编程经验的计算机科学专业学生、软件工程师以及从事算法研究和开发的技术人员。 使用场景及目标:①理解Tarjan算法的工作原理,掌握其在强连通分量识别中的具体实现;②学习如何通过缩点技术将复杂有向图简化为DAG,以优化路径计算和依赖分析;③掌握2-SAT问题的求解方法,提高对布尔可满足性问题的理解和处理能力。 阅读建议:本文内容较为深入,建议读者先熟悉图论基础知识,特别是深度优先搜索(DFS)的相关概念。在学习过程中,结合具体的例子和代码实现,逐步理解各个数据结构和算法步骤的作用,同时关注Tarjan算法在实际应用中的拓展和变种。

    Python简单用法-回顾速查

    简单代码

    Centos7的安装与相关环境软件部署

    Centos7的安装与相关环境软件部署

    单片式离合器(用于轿车、卡车等)solid edge.rar

    单片式离合器(用于轿车、卡车等)solid edge.rar

    ZH1105气缸盖三面钻组合机床设计.rar

    ZH1105气缸盖三面钻组合机床设计.rar

    六相永磁同步电机矢量控制仿真模型在MATLAB中的实现与优化 SVPWM

    内容概要:本文详细介绍了六相永磁同步电机矢量控制仿真模型在MATLAB中的实现方法及其优化技巧。首先解释了六相电机相较于传统三相电机的优势,特别是在可靠性和容错能力方面的提升。接着深入探讨了矢量控制的核心思想,即通过双重dq坐标系变换将定子电流分解为励磁分量和转矩分量,并提供了具体的Clarke变换Matlab函数实现。文中还讨论了电流环控制中存在的耦合问题以及解决方案,展示了前馈解耦的具体代码。此外,文章讲解了SVPWM模块的实现细节,强调了分层法的应用,并给出了调试过程中常见的错误及解决方法。最后,作者分享了一些实用的调试秘籍,确保仿真模型能够顺利运行并达到预期效果。 适用人群:从事电机控制系统研究和开发的技术人员,特别是对六相永磁同步电机感兴趣的科研工作者和工程师。 使用场景及目标:适用于需要进行六相永磁同步电机矢量控制仿真的项目,旨在帮助研究人员理解和掌握该类电机的控制原理和技术难点,提高仿真实验的成功率。 其他说明:本文不仅提供理论知识,还包括具体代码实现和调试经验,有助于读者快速上手并在实践中不断改进和完善自己的仿真模型。

    永磁同步电机无位置控制策略中旋转高频电压注入法的应用与效果 电力电子技术 旋转高频电压注入法:永磁同步电机无位置控制策略的优秀效果

    内容概要:本文详细介绍了旋转高频电压注入法在永磁同步电机无位置控制策略中的应用及其效果。首先阐述了该方法的基本原理,即通过在电机绕组端注入三相对称的高频电压信号,检测电流响应,经过信号处理获取转子位置与转速信息,从而实现无位置传感器控制。接着,文章结合实际案例,展示了该方法在新能源汽车中的成功应用,证明了其在降低成本、提高可靠性和精确控制方面的显著优势。最后,提供了简化的Python代码实现片段,帮助读者更好地理解和实践这一技术。 适合人群:从事电机控制、电力电子技术的研究人员和技术人员,以及对无位置传感器控制感兴趣的工程技术人员。 使用场景及目标:适用于需要提升永磁同步电机性能的场合,如工业自动化、航空航天、新能源汽车等领域。主要目标是降低系统成本,提高系统的可靠性和精度。 其他说明:本文不仅理论讲解详尽,还附有实际案例和代码实现,有助于读者全面掌握旋转高频电压注入法的技术细节和应用场景。

    【Android系统启动流程】基于init-boot过程的详细解析:涵盖Bootloader、Linux内核、init进程、Zygote及SystemServer启动机制

    内容概要:本文档详细阐述了Android设备从上电到启动完成的整个过程,包括BootROM、Bootloader引导程序、Linux内核、init进程、Zygote创建与启动应用以及SystemServer启动等环节。文档中还特别展示了各阶段的代码流程图,深入解析了init进程解析init.rc文件、创建Zygote进程和服务、Zygote预加载核心类、启动SystemServer以及处理进程间通信等技术细节。; 适合人群:对Android系统底层机制感兴趣的开发者,尤其是具有Linux内核基础知识和Java编程经验的研发人员。; 使用场景及目标:①理解Android系统启动流程中的各个组件及其交互方式;②掌握init进程解析配置文件和创建关键服务的具体实现;③深入了解Zygote进程的作用,如预加载类、启动SystemServer和创建应用进程;④研究SystemServer的启动过程及其提供的各种系统服务。; 其他说明:文档不仅提供了详细的启动流程和技术细节,还附带了丰富的代码片段和流程图,有助于读者更好地理解和跟踪代码执行路径。此外,文档中提到的内容对于优化Android系统启动速度、调试启动相关问题以及开发定制化的Android发行版具有重要的参考价值。

    小学男女儿童自我介绍卡通可爱模板.pptx

    小学男女儿童自我介绍卡通可爱模板

    变速拨叉加工工艺及工装夹具设计.rar

    变速拨叉加工工艺及工装夹具设计.rar

Global site tag (gtag.js) - Google Analytics