首先在看之前必須確定你已經部分瞭解廣播intent的原理(從Java層到native層)。如果一竅不通的話,請先百度看完。
進入正題,廣播intent從Java層最終會調用binder機制來觸發native層的發送,即發送訊息BROADCAST_INTENT_TRANSACTION,而這個訊息是通過IActivityManager介面處理的,所以我們在程式中必須先獲得這個介面,即如下:
sp sm = defaultServiceManager(); sp 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(); int i; for (i=0; i
這裡就會調用Parcel的readString,readInt等一系列函數,每次調用後data位置就會後移。
這裡會建立mData,即我們剛剛說的uri地址,如果你給他的是NULL_TYPE_ID,那麼他直接返回null,如果是string類型,那麼他還會readString一遍,所以必須要配對好寫入一個字串,不然如果多讀一次會影響data的位置,導致之後的讀取錯誤。接下來一系列的Parcel讀取就會按照剛剛建立時的順序進行。
完成intent建立後,繼續讀取Parcel中的尾部,即我們最後添加的內容。然後所有這些完成後就會調用broadcastIntent發送及處理。
所以發送成功與否關鍵在於intent建立是否正確。