安卓php推送消息机制,深入剖析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

消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。我们可以通过Looper.myQueue()获取当前线程中的MessageQueue。

3.Looper

MessageQueue的管理者,在一个线程中,如果存在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中是如何处理消息。首先贴上测试代码:

/**

*

* @author 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;

}

}

运行结果:

3a88d3910860272b79e6edeaf7f044df.gif

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

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

1.onCreate()

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

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

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()方法:

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)方法:

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()方法:

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的怀抱中。而绕的这一圈,也正好帮助我们将同步操作变成了异步操作。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/340439.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle数据modeling分类,由浅入深 NoSQL的五种主流数据模型

【IT168 技术】本文内容是对《NoSQL Data Modeling Techniques》一文的简单概述&#xff0c;原文对NoSQL的几种数据模型进行了详细深入的讨论。是了解NoSQL数据模型不过错过的全面资料。NoSQL的一些非功能性的特性&#xff0c;比如扩展性、性能以及一致性的讨论&#xff0c;目前…

dederss.php美国与,Dede经验:全站rss/连载和分类首页模板替换

我用的是Dedecms55 utf-8建的站。首先感谢开源工作者们的劳动&#xff0c;其次要感谢使用者写的N多问题与解答。我一个PHP初用者&#xff0c;能在一个月内&#xff0c;一个人把酷猫网http://www.92kcuat.com 建成今天这样&#xff0c;相信很多朋友都可以。当遇到问题时&#xf…

物联网协议之COAP简介及Java实践

目录 前言 一、COAP简介 1、关于COAP 2、COAP特点 3、基于COAP的NB-IoT接入流程 二、CoAP协议JAVA实践 1、californium介绍 2、Java集成 3、Maven 资源引入 4、定义Server端 5、Client调用 6、运行测试 总结 前言 今天平安夜&#xff0c;祝大家圣诞快乐&#xff0c…

bp配置 sap_SAP转储订单之 STO without delivery

在《SAP转储订单STO小结》一文中&#xff0c;介绍了存转储订单(STO)的几种方式&#xff0c;在SAP Library中有相应的描述&#xff1a;1 Stock Transfer Between Plants in One Step2 Stock Transfer Between Plants in TwoSteps3 Stock Transport Order Without Delivery4 Stoc…

[MEGA DEAL] 2020年完整的Java Master Class Bundle(96%)

通过超过62个小时的培训来掌握最流行的编程语言&#xff0c;从而树立良好的开发生涯 嘿&#xff0c;怪胎&#xff0c; 这一周&#xff0c;我们JCG促销专区 &#xff0c;我们有另一个极端的报价 。我们正在提供一个巨大的96&#xff05;off的完整2020 Python编程认证捆绑 。 立…

linux nc命令测试端口,Linux和Windows下的NC(Netcat)命令测试端口连通性

1、Linux OS 环境下(以Centos为例)&#xff0c;使用nc命令分别测试TCP和UDP端口连通性&#xff1a;css安装方法&#xff1a;nginx在客户端和服务器端分别安装nc工具&#xff0c;安装命令以下&#xff1a;webyum install nc1. Linux OS下使用nc命令&#xff0c;实现TCP方式监听服…

javafx css颜色_JavaFX技巧7:使用CSS颜色常量/派生颜色

javafx css颜色在使用FlexCalendarFX时&#xff0c;我不得不定义一组颜色以可视化不同颜色的不同日历的控件。 每个日历不仅提供一种颜色&#xff0c;还提供几种&#xff1a;用于取消选择/选定/悬停状态的背景和文本颜色。 颜色曾在多个地方使用过&#xff0c;但为了简洁起见&…

linux 查看端口 程序,linux开发:Linux下查看端口占用

前段时间有学生问到&#xff0c;怎么查看linux系统中已经被占用的端口&#xff1f;下面就统一给大家解释一下。提到端口&#xff0c;那首先来回顾端口定义&#xff0c;为了区分一台主机接收到的数据包应该转交给哪个任务来进行处理&#xff0c;使用端口号来区别&#xff1b;我们…

十进制小数化为二进制小数的方法是什么_八进制转换成十进制,十进制转换成八进制...

先来看八进制如何转换成十进制。其方法与二进制转换成十进制差不多&#xff1a;按权相加法&#xff0c;即将八进制每位上的数乘以位权&#xff08;如8,64,512….&#xff09;&#xff0c;然后将得出来的数再加在一起。如将72.45转换为十进制。如图1所示来看看十进制转八进制&am…

c++从字符串中提取数字求和_【函数应用】单元格文本内提取数字并求和

本篇的主题是将单元格内一串文本&#xff0c;找出所有数字并求和&#xff0c;如下图。难度较高&#xff0c;新手建议仅了解下&#xff0c;先学会数组运用&#xff0c;再研究此知识点。废话少说&#xff0c;步入正题。重点说明&#xff1a;本篇只针对文本内整数的数字进行提取并…

iphone已停用怎么解锁_两种无密码解锁iPhone锁屏密码的方法

现在很多手机都配备指纹解锁功能&#xff0c;大家平时用惯了指纹解锁&#xff0c;有时候在需要输入锁屏密码的时候反倒记不清密码是什么了。像是手机重启后就需要输入密码解锁&#xff0c;iPhone锁屏密码忘了怎么办&#xff1f;多次输入错误密码还可能导致手机被停用&#xff0…

linux查找postgre进程,postgresql数据库某一个进程占用大量CPU,问题排查详解

postgresql某一个进程占用大量CPU&#xff0c;问题排查&#xff0c;目前服务器cpu为4核&#xff0c;内存8G1.查下是不是我们的业务SQLSELECTprocpid,START,now() - START AS lap,current_queryFROM (SELECTbackendid,pg_stat_get_backend_pid(S.backendid) AS procp…

华硕z9pa u8 bios下载_教程:图文教学,华硕M8H M8R硬破解支持intel 9代处理器

听说2020年&#xff0c;Intel将要上10代U了&#xff0c;10代U Comet Lake-S的参数整理规格如下:赛扬G5900&#xff1a;2核心2线程&#xff0c;3.4GHz奔腾G6600&#xff1a;2核心4线程&#xff0c;4.2GHz酷睿i3-10100&#xff1a;4核心8线程&#xff0c;3.6-?GHz酷睿i3-10100T&…

苹果mp3软件_flac、WAV、m4a等音频格式转成MP3 ,一键搞定!

在工作生活中&#xff0c;有时我们需要处理各种音频格式转换&#xff0c;有些系统或者是软件不支持特殊的音频格式添加。比如说苹果手机录音格式是M4A的&#xff0c;flac、WAV无损音质格式或者au格式&#xff0c;这些都怎么转成常用的MP3格式呢&#xff1f;有一种快捷的方法就是…

linux6.2 网络yum,配置RHEL6.2的YUM源

服务器端&#xff1a;1、 由于yum安装支持三种协议&#xff1a;file://、ftp://和http://&#xff0c;因此如果是使用本地文件作为源&#xff0c;则可直接使用file:\\协议&#xff0c;否则需要先准备好ftp或者http服务。这次测试的是ftp源&#xff0c;因此需要配置好ftp服务…

iphone换机数据迁移_苹果手机换华为、小米怎么同步数据?来了!

将「雷科技Lite」收藏为我的小程序&#xff0c;不再错过精彩内容雷科技数码3C组编辑 | MoFirLee苹果、安卓手机之间同步数据其实不算太难&#xff0c;难的是没有找对方法。当然&#xff0c;游戏数据无法互通&#xff0c;这里面的问题就复杂了&#xff0c;也是不同平台手机转换数…

spyder pyecharts不显示_165Hz+1ms仅需千元左右 优派VX2771HDPRO显示器

VX2771-HD-PRO应该是前些时间比较受欢迎的电竞显示器之一&#xff0c;不足千元的售价(最近价格又涨回千元以上了)就可以拥有一台165Hz的1080P显示器&#xff0c;性价比还真的蛮高的。国庆在家&#xff0c;而且最近游戏大作不断&#xff0c;赶紧入手一台玩一玩。一、外观赏析开箱…

与安装应用签名不同怎么解决_TCL电视下载软件后不能安装怎么办?一招教你解决...

最近,不少TCL用户都遇到了在当贝市场下载软件后不能安装的问题,那么遇到这种问题该怎么解决呢?首先我们先分析下为什么会出现这种情况!原因:这种情况大多数是因为安装包没有下载完毕或安装包破损导致的,也可能是安装包不适合在你的系统版本中安装,需要最新版本的系统。解决方法…

linux 递归创建线程,[linux]二叉树的建立及其递归遍历(C语言实现)

#二叉树的特点&#xff1a;每一个节点最多有两棵子树&#xff0c;所以二叉树中不存在度大于2的节点&#xff0c;注意&#xff0c;是最多有两棵&#xff0c;没有也是可以的 左子树和右子树是有顺序的&#xff0c;次序不能颠倒&#xff0c;这点可以在哈夫曼编码中体现&#xff0c…

delphi image 编辑器_照片拼图编辑器app下载-照片拼图编辑器下载 v1.0.0 安卓版

照片拼图编辑器是一个专业的照片编辑的app&#xff0c;app里面有很多的拼图工具&#xff0c;模板、水印、滤镜、贴图等等随便你用&#xff0c;可以将图片拼接成多种样式&#xff0c;满足你的各种需求。你想要什么样的效果都可以&#xff0c;赶紧来下载吧&#xff01;应用介绍&a…