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();