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 and
           broadcastIntent function in IActivityManager.Java for this 
           section. The following content may become unsuitable depending
           on 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 type
        data.writeString8(NULL, 0); // type
        data.writeString8(NULL, 0); // identify
        data.writeInt32(0); // flags
        // data.writeString8(NULL, 0); // package = null, appOp should be -1
        data.writeString8(String8(package)); // we need to specify the package
        data.writeString16(NULL, 0); // component
        data.writeInt32(0); // source bound = null
        data.writeInt32(0); // categories = null
        data.writeInt32(0); // selector = null
        data.writeInt32(0); // clipData = null
        data.writeInt32(-2); // contentUserHint: -2 -> UserHandle.USER_CURRENT

        // write Bundle start
        int lengthPos = data.dataPosition();
        data.writeInt32(-1); // bundle extras length
        data.writeInt32(BUNDLE_MAGIC); // 'B' 'N' 'D' 'L'

        int startPos = data.dataPosition();
        data.writeInt32(1);  // size
        data.writeString16(String16("value"));
        data.writeInt32(1); // VAL_INTEGER
        data.writeInt32(value); // 1~10
        int endPos = data.dataPosition();
    
        data.setDataPosition(lengthPos);
        data.writeInt32(endPos - startPos);
        data.setDataPosition(endPos);
        // write Bundle end
        /*** intent end ***/

        data.writeString16(NULL, 0);  // resolvedType
        data.writeStrongBinder(NULL); // resultTo
        data.writeInt32(0); // resultCode
        data.writeString16(NULL, 0); // resultData
        data.writeInt32(0); // map = null
        data.writeString16(NULL, 0); // permission
        data.writeInt32(-1); // appOp = APP_OP_NONE
        data.writeInt32(0); // option = null
        data.writeInt32(1); // serialized: != 0 -> ordered
        data.writeInt32(0); // sticky
        data.writeInt32(-2); // userId: -2 -> UserHandle.USER_CURRENT

        status_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);  // size
        data.writeString16(String16("value"));
        data.writeInt32(1); // VAL_INTEGER
        data.writeInt32(value); 
        int endPos = data.dataPosition();

如发送两个值就这么改

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图像处理之SVD检测显示屏缺陷(C++)

图像处理之SVD检测显示屏缺陷&#xff08;C&#xff09; 文章目录 图像处理之SVD检测显示屏缺陷&#xff08;C&#xff09;前言一、SVD算法简介二、代码实现总结 前言 显示屏缺陷检测是机器视觉领域的一处较广泛的应用场景&#xff0c;显示屏主要有LCD和OLED&#xff0c;缺陷类…

爬虫:爬取豆瓣电影

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 上篇我们将到如何利用xpath的规则&#xff0c;那么这一次&#xff0c;我们将通过案例来告诉读者如何使用Xpath来定位到我们需要的数据&#xff0c;就算你不懂H5代码是怎么个嵌套或者十分复…

cesium 雷达遮罩(电弧球效果)

cesium 雷达遮罩(电弧球效果) 以下为源码直接复制可用 1、实现思路 通过修改“material”材质来实现轨迹球效果 2、代码示例 2.1 index.html <!DOCTYPE html> <html lang="en"><head><!

Python | Leetcode Python题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; class Solution:def climbStairs(self, n: int) -> int:a, b 1, 1for _ in range(n - 1):a, b b, a breturn b

C#winfrom三层架构实现简单课程管理系统管理系统,三层架构实现增删改查

1. 项目展示 1.1登录展示 1.2添加课程信息展示 1.3课程信息管理-查询-修改-删除 1.4修改登录密码 2.项目功能介绍&#xff08;图&#xff09; 3.数据库设计 3.1 教师表设计 3.2 课程分类表 3.3 课程信息表 4. 创建样式界面 winfrom 超详细UI创建过程 实现双色球选号器UI界面…

在国企分公司做信息宣传新闻投稿的经验分享

作为一名国企分公司的信息宣传工作者,我亲历了从传统投稿方式到数字化转型的全过程,这段经历既充满了挑战,也收获了成长。回首最初的日子,那些用邮箱投稿的时光,至今仍让我感慨万千。 初尝辛酸,邮箱投稿的艰难岁月 刚接手信息宣传工作时,我满腔热情,却很快被现实的冷水浇了个透…

Blender材质,纹理,UV

1.材质Material&#xff0c;用于描述物体的表面性质&#xff0c;包含以下基本属性 -基础色 -金属/非金属 -粗糙度 -透光度 -凹凸细节 添加材质步骤&#xff1a; 1&#xff09;切换到材质预览模式 2&#xff09;打开材质面板 3&#xff09;添加一个材质&#xff0c;包括材…

node.js对数据库mysql的连接与操作(增、删、改、查、五种SQL语法)

前提&#xff1a;先在vscode终端下载安装mysql&#xff1a;npm install mysql -save 步骤总结&#xff1a; (1)建立与数据库的连接 (2)做出请求&#xff1a; 实际上就是操作mysql里的数据。增删改查 insert、delete、updata、select (3)通过回调函数获取结果 一、什么是SQ…

spring高级篇(七)

1、异常处理 在DispatcherServlet中&#xff0c;doDispatch(HttpServletRequest request, HttpServletResponse response) 方法用于进行任务处理&#xff1a; 在捕获到异常后没有立刻进行处理&#xff0c;而是先用一个局部变量dispatchException进行记录&#xff0c;然后统一由…

ssm+vue的私人健身和教练预约管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的私人健身和教练预约管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通…

字体设计_西文字体设计(英文字体设计)

一 西文字体设计基础知识 设计目标和历史成因 设计目标&#xff1a;让眼睛看着舒服的字体 那什么样的字体让眼睛看着舒服呢&#xff1f; 让眼睛看着舒服的字体造型其实是我们记忆里的手写体、自然造型。 所以就能理解西文字体为什么同一笔画&#xff0c;有的地方粗有的地方…

flowable一对并发网关跳转的分析

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

数字孪生项目的开发

数字孪生项目开发涉及多学科知识和技术&#xff0c;因此存在以下技术难点&#xff0c;数字孪生项目开发是一项复杂的工程&#xff0c;需要攻克多项技术难关。随着技术的不断发展&#xff0c;数字孪生技术将得到更加广泛的应用&#xff0c;并在各行各业发挥更大的作用。北京木奇…

C语言 函数的嵌套与递归 调用

本文 我们来说函数的嵌套调用和递归调用 在很多大型项目中 我们肯定不可能将所有逻辑都写在一个函数中 肯定要按功能拆解成多个特定的功能函数 函数并不允许嵌套调用&#xff0c;但是 允许在逻辑代码中嵌套调用 所谓函数嵌套调用 就是在一个函数中调用另一个函数&#xff0c;而…

双向BFS算法学习

双向BFS算法学习 推荐练习题 力扣“127”题&#xff1a;单词接龙 “752”题&#xff1a;打开轮盘锁 这里推荐一篇力扣题解 双向BFS 这里使用打开轮盘锁的题干进行举例&#xff1a; 你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字&#xff1a; ‘0’, ‘1’, ‘2’,…

Django项目中的Nginx+uWSGI

Django项目中的NginxuWSGI部署 配合另一篇博客共同饮用Django项目服务器部署&#xff08;2024最新&#xff09; 一&#xff1a;Nginx uWSGI部署框架 用户浏览器向nginx发送请求&#xff0c;nginx判断请求是动态海事静态&#xff0c;如果是静态请求&#xff0c;则直接返回静态…

Redis系列-1 Redis介绍

背景&#xff1a; 本文介绍Redis相关知识&#xff0c;包括Redis的使用、单线程机制、事务、内存过期和淘汰机制。后续将在《Redis系列-2 Redis持久化机制》中介绍Redis基于RDB和AOF的持久化机制&#xff1b;在《Redis系列-3 Redis缓存问题》中介绍缓存击穿、缓存穿透、缓存雪崩…

快速排序(java细节实现)

目录 快速排序: Hoare版: 挖坑法 快速排序的优化 快速排序的非递归实现 小结 从小到大排序 快速排序: 基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&…

C++:AVL树

概念&#xff1a; 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 找元素相当于在顺序表中搜索元素&#xff0c;效率低下。 如图所示&#xff0c;搜索二叉树不能面对右边的树&#xff0c;这种极端的情况&#xf…

[iOS]从拾遗到Runtime(上)

[iOS]从拾遗到Runtime(上) 文章目录 [iOS]从拾遗到Runtime(上)写在前面名词介绍instance 实例对象class 类对象meta-class 元类对象为什么要有元类&#xff1f; runtimeMethod(objc_method)SEL(objc_selector)IMP 类缓存(objc_cache)Category(objc_category) 消息传递消息传递的…
最新文章