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

最近出于工作需要,要在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();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值