Android Native发布广播Intent步骤和原理解析

Android Native层发布广播Intent步骤和原理解析

  • 一、Native层发送广播的机制和原理
    • 1.1 广播发送接口
    • 1.2 应该如何去发送广播
    • 1.3 调用流程
    • 1.4 IActivityManager.java
  • 二、具体实现
    • 2.1 cpp实现代码
    • 2.2 实现过程遇到的问题
    • 2.3 发送多个值

最近出于工作需要,要在Native层发送广播,参考了网上的一些博客如下
Android C++语言 通过Binder通信调用activity: [android.app.IActivityManager] 服务发广播
Android中在native层对java层应用程序发送广播方法及原理
Android的Native方式广播intent
在Android中使用native程序(非Java)来广播intent
Android native进程间通信实例-binder篇之——HAL层访问JAVA层的服务

其中内容基本大同小异,但是由于Android版本的迭代,目前其中的代码具体来说已经不太适用,笔者这边调试了很久终于通了,本文主要用来记录一下整个调试过程,并且会尽可能说明原理以便读者自己实现。

一、Native层发送广播的机制和原理

一些对于广播最基本的概念此处将不再讲述,网络上有很多资料大家可以自行查阅。

1.1 广播发送接口

目前对于广播的发送应该都是需要通过ActivityManagerService.java下的接口来实现,从上面的博客来看,他们都是通过调用其下的broadcastIntent()接口来进行广播。

虽然目前在写下这篇文章时的Android已经不推荐使用这个接口了,在大部分地方都使用broadcastIntentWithFeature()作为替代,但是broadcastIntent()内部实现依靠的是broadcastIntentWithFeature(),因此本文依旧使用这个broadcastIntent()作为实现例子。

1.2 应该如何去发送广播

本质上我们需要在Native层通过binder调用到JAVA层ActivityManagerService下的broadcastIntent()接口来发送广播。

由我们调用transact()函数,经过一系列的Binder调用,到ActivityManagerService的onTransact,通过code找到需要调用的broadcastIntent()函数来完成广播发送。

1.3 调用流程

我们能在ActivityManagerService.java中找到broadcastIntent()和onTransact()函数,按照上述说明,我们需要合理填充transact()函数中的参数才能让我们填充的Parcel类型的数据通过code正确找到对应的onTransact()函数并且正确调用broadcastIntent()。整体的代码调用流程梳理如下

  • 通过ServiceManager获取到ActivityManger的binder对象,并借由该对象指定code识别码和Parcel传输数据,大致如下所示
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> am = sm->getService(String16("activity"));
status_t ret = am->transact(code, data, &reply);
  • 然后调用至android.os.Binder.execTransact --> android.os.Binder.execTransactInternal --> om.android.server.am.ActivityManagerService.onTransact,调用到ActivityManagerService.java中
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException {try {return super.onTransact(code, data, reply, flags);}
}
  • super.onTransact()会调用到IActivityManager.java中的onTransact()函数,通过code识别出应该调用的ActivityManagerService.java中的哪个函数,并且把对应数据从Parcel中读取出来填充至这个函数的形参中,对于我们来说就是com.android.server.am.ActivityManagerService.broadcastIntent

1.4 IActivityManager.java

这个 IActivityManager.java 较为重要,因为其中涉及到对于code值的定义,还有onTransact中的函数写法让我们能够确定我们应该如何去填充这个Parcel对象来使得数据能够正常通过binder传输并且能够正确找到对应的函数调用。

但是我们会发现在代码路径下找不到这个java文件,因为这个文件是由IActivityManager.aidl编译转换得到的,在Android R之后新增了一个机制,对于aidl文件被标记为hide属性,那么我们将无法显性地找到这个编译得到的java文件。因此我们需要手动将该aidl转换为java文件让我们能够更直观地看到相关接口定义。

具体方法参见:
Android R系统aidl文件怎么对应的java文件找不到了?

借由此我们能够看到很多有用地信息

如对于想要调用地函数地code定义

    static final int TRANSACTION_broadcastIntent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 14);

还有我们应该如何去填充想要传递地Parcel对象

      /** @deprecated Use {@link #broadcastIntentWithFeature} instead */@Override public int broadcastIntent(android.app.IApplicationThread caller, android.content.Intent intent, java.lang.String resolvedType, android.content.IIntentReceiver resultTo, int resultCode, java.lang.String resultData, android.os.Bundle map, java.lang.String[] requiredPermissions, int appOp, android.os.Bundle options, boolean serialized, boolean sticky, int userId) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();int _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeStrongBinder((((caller!=null))?(caller.asBinder()):(null)));if ((intent!=null)) {_data.writeInt(1);intent.writeToParcel(_data, 0);}else {_data.writeInt(0);}_data.writeString(resolvedType);_data.writeStrongBinder((((resultTo!=null))?(resultTo.asBinder()):(null)));_data.writeInt(resultCode);_data.writeString(resultData);if ((map!=null)) {_data.writeInt(1);map.writeToParcel(_data, 0);}else {_data.writeInt(0);}_data.writeStringArray(requiredPermissions);_data.writeInt(appOp);if ((options!=null)) {_data.writeInt(1);options.writeToParcel(_data, 0);}else {_data.writeInt(0);}_data.writeInt(((serialized)?(1):(0)));_data.writeInt(((sticky)?(1):(0)));_data.writeInt(userId);boolean _status = mRemote.transact(Stub.TRANSACTION_broadcastIntent, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {return getDefaultImpl().broadcastIntent(caller, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermissions, appOp, options, serialized, sticky, userId);}}_reply.readException();_result = _reply.readInt();}finally {_reply.recycle();_data.recycle();}return _result;}
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{java.lang.String descriptor = DESCRIPTOR;switch (code){case INTERFACE_TRANSACTION:{reply.writeString(descriptor);return true;}}switch (code){case TRANSACTION_broadcastIntent:{data.enforceInterface(descriptor);android.app.IApplicationThread _arg0;_arg0 = android.app.IApplicationThread.Stub.asInterface(data.readStrongBinder());android.content.Intent _arg1;if ((0!=data.readInt())) {_arg1 = android.content.Intent.CREATOR.createFromParcel(data);}else {_arg1 = null;}java.lang.String _arg2;_arg2 = data.readString();android.content.IIntentReceiver _arg3;_arg3 = android.content.IIntentReceiver.Stub.asInterface(data.readStrongBinder());int _arg4;_arg4 = data.readInt();java.lang.String _arg5;_arg5 = data.readString();android.os.Bundle _arg6;if ((0!=data.readInt())) {_arg6 = android.os.Bundle.CREATOR.createFromParcel(data);}else {_arg6 = null;}java.lang.String[] _arg7;_arg7 = data.createStringArray();int _arg8;_arg8 = data.readInt();android.os.Bundle _arg9;if ((0!=data.readInt())) {_arg9 = android.os.Bundle.CREATOR.createFromParcel(data);}else {_arg9 = null;}boolean _arg10;_arg10 = (0!=data.readInt());boolean _arg11;_arg11 = (0!=data.readInt());int _arg12;_arg12 = data.readInt();int _result = this.broadcastIntent(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8, _arg9, _arg10, _arg11, _arg12);reply.writeNoException();reply.writeInt(_result);return true;}

二、具体实现

2.1 cpp实现代码

上述我们得到的IActivityManager.java中的代码,我们查看对照着Parcel.java和Parcel.cpp来重写各个函数的调用即可,如下所示我们实现了Intent的底层重写和封装将其在Parcel中打包之后通过transact函数发送出去最终调用至broadcasIntent()函数

bool sendBroadcastMessage(const char* package, const char* action, int value) {ALOGI("%s: package = %s, action = %s, value = %d", __func__, package, action, value);sp<IServiceManager> sm = defaultServiceManager();if (sm == NULL) ALOGE("%s: fail to get ServiceManager", __func__);sp<IBinder> am = sm->getService(String16("activity"));if (am != NULL) {/* We need to follow the writing of the onTransact function andbroadcastIntent function in IActivityManager.Java for this section. The following content may become unsuitable dependingon different Android versions. */Parcel data, reply;data.writeInterfaceToken(String16("android.app.IActivityManager"));data.writeStrongBinder(NULL);/*** intent begin ***/data.writeInt32(1); // intent != null// the following content belongs to intent.writeToParcel(_data, 0)data.writeString8(String8(action));data.writeInt32(0); // URI data typedata.writeString8(NULL, 0); // typedata.writeString8(NULL, 0); // identifydata.writeInt32(0); // flags// data.writeString8(NULL, 0); // package = null, appOp should be -1data.writeString8(String8(package)); // we need to specify the packagedata.writeString16(NULL, 0); // componentdata.writeInt32(0); // source bound = nulldata.writeInt32(0); // categories = nulldata.writeInt32(0); // selector = nulldata.writeInt32(0); // clipData = nulldata.writeInt32(-2); // contentUserHint: -2 -> UserHandle.USER_CURRENT// write Bundle startint lengthPos = data.dataPosition();data.writeInt32(-1); // bundle extras lengthdata.writeInt32(BUNDLE_MAGIC); // 'B' 'N' 'D' 'L'int startPos = data.dataPosition();data.writeInt32(1);  // sizedata.writeString16(String16("value"));data.writeInt32(1); // VAL_INTEGERdata.writeInt32(value); // 1~10int endPos = data.dataPosition();data.setDataPosition(lengthPos);data.writeInt32(endPos - startPos);data.setDataPosition(endPos);// write Bundle end/*** intent end ***/data.writeString16(NULL, 0);  // resolvedTypedata.writeStrongBinder(NULL); // resultTodata.writeInt32(0); // resultCodedata.writeString16(NULL, 0); // resultDatadata.writeInt32(0); // map = nulldata.writeString16(NULL, 0); // permissiondata.writeInt32(-1); // appOp = APP_OP_NONEdata.writeInt32(0); // option = nulldata.writeInt32(1); // serialized: != 0 -> ordereddata.writeInt32(0); // stickydata.writeInt32(-2); // userId: -2 -> UserHandle.USER_CURRENTstatus_t ret = am->transact(TRANSACTION_broadcastIntent, data, &reply);if (ret == NO_ERROR) {int exceptionCode = reply.readExceptionCode();if (exceptionCode) {ALOGE("%s: transact(%s) caught exception %d", __func__, action, exceptionCode);return false;}} else {ALOGE("%s: transact fail %d", __func__, ret);return false;}} else {ALOGE("%s: getService func couldn't find activity service!", __func__);return false;}ALOGI("sendBroadcastMessage success!");return true;
}

如果不想指定package定向发送的话,就直接填null就行,这样就是全局发送

2.2 实现过程遇到的问题

需要注意的是必须严格按照Parcel和Intent的格式和顺序填充整个我们需要传输的Parcel对象,有一点错误都会导致transact的binder调用过程失败,譬如一定要好好对照一下Parcel.java下的writeString在Parcel.cpp下对应write的究竟是String8还是String16,还有就是在java下面可能只是简单的写入需要的值,但是在cpp下面的write可能还写了一些校验值啥的一定也要加进去,有兴趣的可以自己去看一下Parcel的封装和读取过程。

对于code的值一定要明确,因为会影响到具体调用的函数,因此对于不同的Android版本一定要自己手动去编译一下aidl文件确定实际的code值。

另外踩了一个坑就是appOp这个一定要根据当前的Android版本确定到底应该写啥值,之前笔者按照网络上的示例写了0进去,最后发现一定要指明pacakge才能发送,后来查看了代码改为-1,即APP_OP_NONE之后,就可以不用指定package就可以任意发送了。

最后就是这个方法是不支持HAL发送广播的,究其原因就是HAL跟Framework是两个进程,用的底层的/dev/binder设备都不一样。

2.3 发送多个值

修改这部分即可

        int startPos = data.dataPosition();data.writeInt32(1);  // sizedata.writeString16(String16("value"));data.writeInt32(1); // VAL_INTEGERdata.writeInt32(value); int endPos = data.dataPosition();

如发送两个值就这么改

        int startPos = data.dataPosition();data.writeInt32(2);  // sizedata.writeString16(String16("value1"));data.writeInt32(1); // VAL_INTEGERdata.writeInt32(value1); data.writeString16(String16("value2"));data.writeInt32(1); // VAL_INTEGERdata.writeInt32(value2); int endPos = data.dataPosition();

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

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

相关文章

REFORMER: 更高效的TRANSFORMER模型

大型Transformer模型通常在许多任务上都能达到最先进的结果&#xff0c;但是训练这些模型的成本可能会非常高昂&#xff0c;特别是在处理长序列时。我们引入了两种技术来提高Transformer的效率。首先&#xff0c;我们用一种使用局部敏感哈希的点积注意力替换了原来的点积注意力…

验证码生成--kaptcha

验证码生成与点击重新获取验证码 如图所示&#xff0c;本文档仅展示了验证码的生成和刷新显示。 1. 概述 系统通过生成随机验证码图像和文本。 2. 代码分析 2.1. Maven依赖 <dependency><groupId>com.github.penggle</groupId><artifactId>kaptch…

JAVA基础--File

文件 为什么要知道文件 在计算机磁盘中&#xff0c;文件是一个非常重要的存在&#xff0c;大家知道我们数据都是保存在计算机磁盘&#xff0c;而磁盘以文件为载体&#xff0c;将信息存储在磁盘文件中&#xff0c;接下来我们学习的IO流也主要是对文件中的数据进行读写操作&…

第四百九十九回

文章目录 1. 概念介绍2. 使用方法2.1 固定样式2.2 自定义样式 3. 示例代码4. 内容总结 我们在上一章回中介绍了"GetMaterialApp组件"相关的内容&#xff0c;本章回中将介绍使用get显示SnackBar.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在介…

毕业论文凑字数——关于IVR自动语音应答交互式电话导航自动总机等等概念的一些剖析

目录 IVR毕业论文的讨巧思路IVR自动语音应答IVR的使用流程IVR的各种应用IVR的基本配置 一个小朋友的毕业论文要凑字数&#xff0c;所以推荐她讲一讲IVR&#xff0c;因为IVR可以翻译的名字很多&#xff0c;比如交互式语音应答&#xff0c;自动语音应答&#xff0c;自动语音服务&…

C语言例题36、判断一个数是否是回文数

题目要求&#xff1a;输入一个5位数&#xff0c;判断它是不是回文数。即12321是回文数 #include <stdio.h>int main() {int x;int ge, shi, qian, wan;printf("请输入一个5位数&#xff1a;");scanf("%d", &x);ge x % 10; //个sh…

Xshell连接提示“SSH服务器拒绝了密码”

原因1&#xff1a;数字锁没有打开 没有打开NumLock&#xff08;数字小键盘上面有一个【Num】按键&#xff09;&#xff0c;需要按键开启。 注意要检查NumLock灯是否亮起。 或者改成用字母键上面的数字键输入就好了。 原因2&#xff1a;root密码设置错误&#xff08;这个是比较常…

fatal error: ros/ros.h: 没有那个文件或目录

解决方法&#xff1a; 在出错的文件的包下的CMakeLists.txt文件里&#xff0c;加上 find_package(catkin REQUIRED COMPONENTSroscpp )include_directories(include ${catkin_INCLUDE_DIRS} )【ROS-解决问题】 fatal error: ros/ros.h: 没有那个文件或目录-CSDN博客

GO语言核心30讲 实战与应用 (第三​部分)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、io包中的接口和工具

设计模式-09 - 享元模式 flyweight pattern

设计模式-09 - 享元模式 flyweight pattern 1.定义 享元模式是一种设计模式&#xff0c;它使用共享对象来减少内存使用和提高性能。它通过存储共享的对象实例池来实现&#xff0c;这些实例可以被多个客户端同时使用。 享元模式定义了一个接口&#xff0c;使客户端可以访问共…

安全关闭Tcp连接

close与shutdwon int close(int sockfd);关闭sokcet&#xff0c;这里注意&#xff1a;当程序调用close关闭socket的时候,如果缓冲区中仍然有数据的话,协议栈会发送RST包代替FIN包&#xff0c;丢弃缓冲的数据&#xff0c;强行关闭连接 int shutdown(int sockfd, int howto);该…

【Rollup】用rollup从0到1开发一个js插件并发布到npm

Rollup 是一个 JavaScript 模块打包器&#xff0c;专注于打包 ES6 模块将其编译回多种模块化格式&#xff0c;尤其适合打包库和框架&#xff0c;因为它可以生成更小、更高效的代码&#xff0c;并且特别适合将代码打包成可在浏览器中使用的库。 从0到1开发js插件 1.创建文件夹…

es终止快照恢复进程的方法

方法1、删除索引可以终止&#xff0c;恢复进程。 DELETE index_* // 按通配符删除以index_开头的索引 DELETE _all // 删除全部索引 POST *,-.*/_close 关闭索引 POST *,-.*/_open 打开索引 DELETE *,-.* 删除全部索引方法2、强制重启es 集群也可也终…

安全狗入选福建省网信系统2024年度网络安全技术支撑单位

近日&#xff0c;福建省委网信办会同国家互联网应急中心福建分中心确定并公示了福建省网信系统2024年度网络安全技术支撑单位名单。 作为国内云原生安全领导厂商&#xff0c;安全狗也成功入选。 据悉&#xff0c;此次遴选工作依据《中华人民共和国网络安全法》《国家网络安全事…

如何通过AI技术实现员工培训的革命性变革

AI个性化培训&#xff1a;开启员工潜力的新篇章 在当今这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术的影响力已经渗透到社会的各个层面&#xff0c;包括教育与培训行业。AI技术正在彻底改变我们获取知识与技能的方式&#xff0c;特别是在员工培训领域…

IDEA使用技巧2—发布web项目

作者&#xff1a;私语茶馆 1.War包类型 发布Web项目有两种模式&#xff1a;war和war exploded&#xff0c; war模式&#xff1a;将WEB工程以包的形式上传到服务器 &#xff1b;war exploded模式&#xff1a;将WEB工程以当前文件夹的位置关系上传到服务器&#xff1b; war ex…

已解决java.lang.NoClassDefFoundError: 找不到类定义错误的正确解决方法,亲测有效!!!

已解决java.lang.NoClassDefFoundError: 找不到类定义错误的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 报错原因 解决思路 解决方法 检查类路径 确认类文件和库的位置 检查类加载器配置 确认依赖库的存在和版本 总结 问题…

解决NullPointerException at org.apache.hive.hcatalog.data.schema.HCatSchema.get问题

解决在org.apache.hive.hcatalog.data.schema.HCatSchema.get遇到NullPointerException 在使用hive sqoop import过程中遇到NullPointerExcetption at org.apache.hive.hcatalog.data.schema.HCatSchema.get 这大概率是导入hive的目标表结构与同步的column选项不匹配造成的。 …

c# 针对internal的类 如何写测试类

在C#中&#xff0c;internal 关键字意味着一个类型或成员只能在定义它的程序集中访问。这通常用于封装那些不应该被外部程序集直接访问的实现细节。但是&#xff0c;在编写单元测试时&#xff0c;我们可能希望测试这些internal类和方法。有几种方法可以实现这一点&#xff1a; …

用于视频识别的快慢网络

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;用于视频识别的快慢网络1、文献摘要2、提出方法2.1、SlowFast模型2.2、SlowFast 提出思想 3、相关方法3.1、时空间卷积3.2、基于光…