您的位置:首页 > 移动开发 > Android开发

在Android中使用native程序(非Java)来广播intent

2014-02-07 14:02 281 查看
首先在看之前必须确定你已经部分了解广播intent的原理(从Java层到native层)。如果一窍不通的话,请先百度看完。

进入正题,广播intent从Java层最终会调用binder机制来触发native层的发送,即发送消息BROADCAST_INTENT_TRANSACTION,而这个消息是通过IActivityManager接口处理的,所以我们在程序中必须先获得这个接口,即如下:

sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> am = sm->checkService(String16("activity"));


然后我们就得到了am,然后可以继续使用transact函数来通过消息发送intent。

当然这里我们还需要一个intent,但是怎么在native程序中创建intent呢?

首先我们需要创建一个Parcel,这是binder经常使用的一个数据包类。

创建完成后,进行填充,如下所示:

1.	data.writeInterfaceToken(String16("android.app.IActivityManager"));
2.	data.writeStrongBinder(NULL); /* caller */
/* intent */
3.	data.writeString16(String16(action_str)); /* action */
4.	data.writeInt32(URI_TYPE_ID); /* Uri - type */
5.	data.writeString16(String16(uri_str)); /* uri string if URI_TYPE_ID set */
6.	data.writeString16(NULL, 0); /* type */
7.	data.writeInt32(0); /* flags */
8.	data.writeString16(NULL, 0); /* package name */
9.	data.writeString16(NULL, 0); /* ComponentName */
10.	data.writeInt32(0); /* source bound - size */
11.	data.writeInt32(0); /* Categories - size */
12.	data.writeInt32(0); /* selector - size */
13.	data.writeInt32(0); /* ClipData */
14.	data.writeInt32(-1); /* bundle(extras) size */
/* end of intent */


第一行:当发送消息BROADCAST_INTENT_TRANSACTION时,会检查token,如果不是IActivityManager.descriptor则会返回false,当然这里只会打出一段warning信息,不会发送失败。

第二行:这里是发送这个binder请求方。

第三行:这个是需要发送intent的action(android.intent.action.*)

第四行:这里写入了一个int值,可以有四个,其中三个分别对应三种uri类型:

NULL_TYPE_ID:0

StringUri.TYPE_ID:1

OpaqueUri.TYPE_ID:2

HierarchicalUri.TYPE_ID:3

第五行:这一行存在与否依赖于第四行,规则如下:

如果是NULL_TYPE_ID:该行不可存在

如果是StringUri.TYPE_ID:该行必须是writeString16写入uri地址

如果是OpaqueUri.TYPE_ID:可以看Uri.java中OpaqueUri类readFrom函数,由多个part组成。

如果是HierarchicalUri.TYPE_ID:可以看Uri.java中HierarchicalUri类readFrom函数,由多个part组成。

第六行-第十三行:在后面的注释都有解释。

第十四行:这一行是用来确定有多少extras(即映射参数),比如”key”=”1234”,可以使用getString获得。

{ /* Extras */
data.writeInt32(-1); /* length */
data.writeInt32(0x4C444E42); // 'B' 'N' 'D' 'L'
int oldPos = data.dataPosition();
{ /* writeMapInternal */
data.writeInt32(1); /* size */
data.writeInt32(VAL_STRING);
data.writeString16(String16("key"));
data.writeInt32(VAL_STRING);
data.writeString16(String16(“1234”));
}
int newPos = data.dataPosition();
data.setDataPosition(oldPos - 8);
data.writeInt32(newPos - oldPos); /* length */
data.setDataPosition(newPos);
}


这里我们先写了int值来表示有多少extras,但是只是-1,我们会在之后进行计算来重写这个值。然后我们写入了一个magic值(BNDL),这个值在解析extras,即Bundle类中readFromParcelInner函数中会先读取这个magic值,然后才继续处理。这里我们会在写入前保存当前data位置,然后写入map的数量,即一组”key”=”1234”,然后写完后使用新的data位置减去老的位置即获得了表示长度的data位置,然后写入新的长度值。这里关于map表里的描述符(如VAL_STRING)可以在Parcel.java中readValue函数中找到。

OK,填充完这14行后,如果我们还需要添加一些末尾信息:

data.writeString16(NULL, 0); /* resolvedType */
data.writeStrongBinder(NULL); /* resultTo */
data.writeInt32(-1); /* result code */
data.writeString16(NULL, 0); /* result data */
data.writeInt32(-1); /* no result extra */
data.writeString16(NULL, 0); /* permission */
data.writeInt32(false); /* app operation in AppOpsManager */
data.writeInt32(false); /* serialized */
data.writeInt32(false); /* sticky */
data.writeInt32(false); /* userid */


这里包括了一些其他需要的属性,可以看注释了解。

但是为什么会需要这样的顺序进行添加呢?

我们可以看ActivityManagerNative.java中对BROADCAST_INTENT_TRANSACTION的处理:

case BROADCAST_INTENT_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app =
b != null ? ApplicationThreadNative.asInterface(b) : null;
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
b = data.readStrongBinder();
IIntentReceiver resultTo =
b != null ? IIntentReceiver.Stub.asInterface(b) : null;
int resultCode = data.readInt();
String resultData = data.readString();
Bundle resultExtras = data.readBundle();
String perm = data.readString();
int appOp = data.readInt();
boolean serialized = data.readInt() != 0;
boolean sticky = data.readInt() != 0;
int userId = data.readInt();


我们可以看到第一行就是判断刚才说的interface的。

第二行会获得caller即调用者。

之后会创建intent,这里会进入Intent.java中newIntent,继而调用readFromParcel

setAction(in.readString());
mData = Uri.CREATOR.createFromParcel(in);
mType = in.readString();
mFlags = in.readInt();
mPackage = in.readString();
mComponent = ComponentName.readFromParcel(in);

if (in.readInt() != 0) {
mSourceBounds = Rect.CREATOR.createFromParcel(in);
}

int N = in.readInt();
if (N > 0) {
mCategories = new ArraySet<String>();
int i;
for (i=0; i<N; i++) {
mCategories.add(in.readString().intern());
}
} else {
mCategories = null;
}

if (in.readInt() != 0) {
mSelector = new Intent(in);
}

if (in.readInt() != 0) {
mClipData = new ClipData(in);
}

mExtras = in.readBundle();


这里就会调用Parcel的readString,readInt等一系列函数,每次调用后data位置就会后移。

这里会创建mData,即我们刚刚说的uri地址,如果你给他的是NULL_TYPE_ID,那么他直接返回null,如果是string类型,那么他还会readString一遍,所以必须要配对好写入一个字符串,不然如果多读一次会影响data的位置,导致之后的读取错误。接下来一系列的Parcel读取就会按照刚刚创建时的顺序进行。

完成intent创建后,继续读取Parcel中的尾部,即我们最后添加的内容。然后所有这些完成后就会调用broadcastIntent发送及处理。

所以发送成功与否关键在于intent创建是否正确。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐