1. 程式人生 > >一種比較好的JNI Java和C++相互傳遞引數和返回值的方法

一種比較好的JNI Java和C++相互傳遞引數和返回值的方法

序言

以前在Android上做移動多媒體開發的時候,有很多需要在Java和C++相互傳遞引數的Case,以前因為時間卡的緊,一直沒有去修復這一類的問題,因為能用,沒有出什麼問題,也就沒有想什麼優化方案。最近自己有一些閒暇時間折騰點小玩意兒,也需要從Java和C++之間相互傳遞引數。想到以前曾經有JNI方法20多個引數的情況,於是準備把這塊好好優化一下。

思路

陣列封裝法

我想到的第一種思路是把各種型別的引數用陣列做封裝。比如這樣一個方法

private native void init(int handle, int type, String res, String name, float faceIntensity, float eyeIntensity);

直接寫成這樣

private native void init(int[] intArgs, String[] stringObjs, float[] floatArgs);

這樣確實能把引數的個數減少。但是無疑增加了處理資料的負擔,native層必須寫很多Java陣列操作的相關方法去獲取相關引數。而且引數的語意變得很不明確,維護起來很不方便。

物件封裝法

物件封裝法是我之前從WebRTC裡面看到的,它裡面有一個native_gen.py的指令碼,可以把一個Java物件轉化成一個Native的封裝好的程式碼,可以很方便的去操作Java物件。這樣的模式下我們可以直接把Java物件傳遞給native層,然後在Native層直接XXXObject.from即可獲取一個對Java層物件進行操作的輔助類。大致程式碼演示如下:
Java:

public class Param {
    public int handle;
    public int type;
    public String res;
    public String name;
    public float faceIntensity;
    public float eyeIntensity;
}
public native void init(Param param);

C++:

	Java_XXX_XXX_init(JNIEnv *env, jobject obj, int handle, jobject jparams) {
	 	NativeParam* params = NativeParam::from(jparams);
	 	params->getHandle();
	 	params->getType();
	 	params->getRes();
	 	...
	 	delete params;
	}

這個方法能夠比較好的解決引數過多的問題,但是比較麻煩的是我們需要去依賴指令碼對應到Java的類生成操作的本地方法,這樣無疑會導致庫變大,並且檔案一多也不方便維護。新人來還需要先學習指令碼使用。

鍵值傳遞方法

此時筆者想到在Android層各個頁面之間傳遞方法都是通過Intent裡面的Bundle物件來進行傳遞。而Bundle本身就是在C++層進行的操作,會把值都存放在C++層,方便Binder進行程序間通訊。而Bundle是通過KeyValue的方式進行的多引數維護。雖然Native層的Bundle沒有對我們開放,但是我們為何不自己寫一個類似的東西來進行Java和C++之間進行引數傳遞呢?在Java層,我們可以直接封裝一個物件傳遞的java方法給其他地方使用,然後自己封裝到一個Bundle裡面,再傳遞給C++裡面直接使用。而如果是需要從C++層返回給Java層一些資料,我們也可以很方便的通過返回Bundle給上層來實現多個返回值的返回。使用方法如下:
Java:

public class Param {
    public int type;
    public String res;
    public String name;
    public float faceIntensity;
    public float eyeIntensity;
}
public void init(Param params) {
	NativeBundle bundle = new NativeBundle();
	bundle.putInt("type", params.type);
	bundle.putString("res", params.res);
	bundle.putString("name", params.name);
	bundle.putFloat("faceIntensity", faceIntensity);
	bundle.putFloat("eyeIntensity", eyeIntensity);
	nativeInit(handle, bundle);
}
private native void nativeInit(int handle, Param param);

C++:

	Java_XXX_XXX_init(JNIEnv *env, jobject obj, int handle, jobject jbundle) {
	 	    Bundle *bundle = getBundleFromJObject(jbundle);
			int64_t type = bundle->getInt("type");
			string* res = bundle->getString("res");
			string* name = bundle->getString("name");
			int faceIntensity = bundle->getInt("faceIntensity");
			int eyeIntensity = bundle->getInt("eyeIntensity");
			...
	}

通過鍵值對的對映,我們就可以很簡單的從Java傳任意多個引數到Native層,從而避免參數過多或者語義不清的問題。

具體實現請移步github: nBundle