Android C++ 呼叫 Java 方法
前言
前段時間在寫一個特性,需要在 native 層將資訊持久化到 dropbox 當中。但是由於在 Android N 上,dropbox 相關的 client 端和 server 端都是由 Java 來實現的,在 native 層並沒有相關的代理,因此我們不能在 native 層藉助普通的 C++ 呼叫來實現這個功能,只能呼叫相關的 Java 方法來實現。
本篇文章就來介紹一下 C++ 如何呼叫 Java 方法,以及上面這個功能如何實現。(基於 Android N)
一、思路
1. DropBoxManager
將資訊新增到 dropbox,需要藉助於 DropBoxManager:
frameworks/base/core/java/android/os/DropBoxManager.java
/**
* Stores human-readable text. The data may be discarded eventually (or even
* immediately) if space is limited, or ignored entirely if the tag has been
* blocked (see {@link #isTagEnabled}).
*
* @param tag describing the type of entry being stored
* @param data value to store
*/
public void addText(String tag, String data) {
try {
mService.add(new Entry(tag, 0, data));
} catch (RemoteException e) {
if (e instanceof TransactionTooLargeException
&& mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data, so it was ignored" , e);
return;
}
throw e.rethrowFromSystemServer();
}
}
如上所示,我們可以用 addText 方法將資訊新增至 dropbox 當中,String data 即為新增的資訊,String tag 為標識,dropbox 會以 tag 作為檔名的一部分生成一個 dropbox 檔案,並存儲在 /data/system/dropbox 目錄下。
2. C++ 呼叫 Java 方法
C++ 呼叫 Java 方法需要藉助於 JNIEnv,呼叫它的 CallXXXMethod 方法來實現,以上面的 addText 為例,需要通過 env->CallVoidMethod
來呼叫 addText 方法,其中的 Void 表示 addText 無返回值。
如果想要探究 C++ 呼叫 Java 方法的原理,請看我的另一篇文章 從 Native 函式呼叫 Java 函式
以上面的 addText 為例,需要通過 env->CallVoidMethod(dropBoxManager, addTextID, tag, data)
來實現;各個引數的意義分別是:
- dropBoxManager:因為 addText 不是 static 的方法,因此 addText 的呼叫必須依賴於具體的物件,dropBoxManager 即為 DropBoxManager 物件的索引,型別為 jobject
- addTextID:addTextID 為 addText 方法對應的 MethodID,其可以通過 env->GetMethodID 方法來獲得,如果方法是 static 的則需要通過 env->GetStaticMethodID 來獲得,其型別為 jmethodID
- tag,data:addText 方法需要的引數,在此處他們的型別為 jstring
因此,在呼叫 addText 方法之前需要先生成一個 DropBoxManager 物件,對此我們可以呼叫 env->NewObject
來實現;先看一下 DropBoxManager 的建構函式:
frameworks/base/core/java/android/os/DropBoxManager.java
/** {@hide} */
public DropBoxManager(Context context, IDropBoxManagerService service) {
mContext = context;
mService = service;
}
由上所示,生成 DropBoxManager 物件需要呼叫 env->NewObject(dropBoxClazz, constructorID, context, service)
,各個引數的作用分別為:
- dropBoxClazz:DropBoxManager 類對應的索引,型別為 jclass,通過 env->FindClass 獲得
- constructorID:DropBoxManager 對應的構造方法,各個 Java 類的構造方法即為 method
<init>
,型別為 jmethodID,通過 env->GetMethodID 獲得 - context,service:Context、IDropBoxManagerService 物件的索引,型別為 jobject
從上面可知,要想建立 DropBoxManager 物件,必須先獲得 Context、IDropBoxManagerService 物件,但是 Context 物件並不容易得到,因此我們只得另闢蹊徑。
通過 addText 的實現可知,DropBoxManager 只是 dropbox 功能的一個封裝,真正的實現是由 IDropBoxManagerService 來完成的,addText 實際是呼叫 IDropBoxManagerService 的 add 方法,即 mService.add(new Entry(tag, 0, data))
因此,我們可以直接由 ServiceManager 獲得 IDropBoxManagerService 物件,然後再呼叫它的 add 方法來達成我們的目的,對應的 Java 實現即為:
IBinder b = ServiceManager.getService(Context.DROPBOX_SERVICE);
IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
service.add(new Entry(tag, 0, data));
二、實現
綜上所述,在 native 層將資訊持久化到 dropbox 當中的實現即為:
void AddTextToDropBox(const char* tag, const char* text) {
// 獲得 JNIEnv 指標 env
Thread* const self = Thread::Current();
JNIEnv* env = self->GetJniEnv();
// 獲得 ServiceManager 類的索引
jclass serviceManagerClazz = env->FindClass("android/os/ServiceManager");
// 獲得 getService 方法的 methodID, getService 為 static 方法
jmethodID getServiceID = env->GetStaticMethodID(serviceManagerClazz, "getService",
"(Ljava/lang/String;)Landroid/os/IBinder;");
// 呼叫方法 getService 得到 IBinder 物件
jobject binder = env->CallStaticObjectMethod(serviceManagerClazz,
getServiceID, env->NewStringUTF("dropbox"));
jclass stubClazz = env->FindClass("com/android/internal/os/IDropBoxManagerService$Stub");
jmethodID asInterfaceID = env->GetStaticMethodID(stubClazz, "asInterface",
"(Landroid/os/IBinder;)Lcom/android/internal/os/IDropBoxManagerService;");
// 呼叫方法 asInterface 得到 IDropBoxManagerService 物件
jobject service = env->CallStaticObjectMethod(stubClazz, asInterfaceID, binder);
jclass entryClazz = env->FindClass("android/os/DropBoxManager$Entry");
jmethodID constructorID = env->GetMethodID(entryClazz, "<init>",
"(Ljava/lang/String;JLjava/lang/String;)V");
// 建立 DropBoxManager$Entry 物件
jobject entry = env->NewObject(entryClazz, constructorID,
env->NewStringUTF(tag), 0, env->NewStringUTF(text));
jclass serviceClazz =
env->FindClass("com/android/internal/os/IDropBoxManagerService");
jmethodID addID = env->GetMethodID(
serviceClazz, "add", "(Landroid/os/DropBoxManager$Entry;)V");
// 最終呼叫 IDropBoxManagerService 的 add 方法來達到我們的目的
env->CallVoidMethod(service, addID, entry);
}
對於上面涉及到的幾個方法的引數的含義,前面已經講過,這裡不再贅述,補充以下幾點:
- FindClass 的引數是個字串,其為對應 class 的全稱(’.’ 轉化為 ‘/’)
- GetMethodID 的三個引數分別為:
- method 所在類的索引,型別為 jclass
- method 的名稱,建構函式的名稱統一為
<init>
- method 的 signature,用於區分相同 method 名的不同 method;其為一個字串,”()” 中是引數的 descriptor,”()” 後是返回值的 descriptor;以 Test.java 為例,可以通過:1. javac Test.java 2. javap -s -p Test.class 獲得相應方法的 signature