背景
在最近的開發中遇到了這樣的一個場景,使用ffmpeg同時解碼多路h264流,之前解碼一路視頻時,可以直接在jni文件中定義一個包裝了ffmpeg解碼功能的c++類的對象,如果繼續採取這種寫法必須在jni中定義多個對象,使得程序很不靈活。如果能把一個java類直接和c++類建立關係,則可以在多路解碼時分別創建java對象,在使用完畢後由java進行gc,這樣代碼就靈活了許多。下面來研究一下java類與c++類之間如何建立關係。
實現
jni文件爲我們提供了java層和c/c++層通信的接口,但是這些接口都是c函數的形式,也就是正常情況下要想調用一個c++類的方法,我們需要在jni中建立一個全局的c++對象。如果想要直接使用c++類,必須建立一個c++對象,如果建立全局對象肯定不符合我們的要求,這時可以提供一個jni函數,用來在堆中創建一個c++對象。
正常情況下我們是用一個指針指向堆中創建的c++對象,由於指針本身是一個地址值,所以可以將其當作一個數值。把這個地址值當作數字返回給java層,java就拿到了這個c++對象的句柄,便可以對這個對象進行操作。
java包裝類實現
之所以稱其爲包裝類,是因爲其實這個java類是包裝了c++類,其功能實現是由c++實現。下面我們看下這個java包裝的實現。
package com.vonchenchen.jnitest;
import android.util.Log;
import static android.content.ContentValues.TAG;
/**
* Created by lidechen on 6/5/17.
*/
public class JavaClassDemoWrapper {
private static final String TAG = "JavaClassDemoWrapper";
static {
System.loadLibrary("demo");
}
private int mCppObjWapper;
public JavaClassDemoWrapper(){
mCppObjWapper = getCppObjWrapper();
}
public void setTag(String tag){
setTag(tag, mCppObjWapper);
}
public String getTag(){
return getTag(mCppObjWapper);
}
@Override
protected void finalize() throws Throwable {
try {
Log.e(TAG, "finalize()");
if (mCppObjWapper != 0) {
release(mCppObjWapper);
mCppObjWapper = 0;
}
}finally {
super.finalize();
}
}
//獲取cpp對象指針
public native int getCppObjWrapper();
//調用cpp對象中對應的方法
public native void setTag(String tag, int cppObjWapper);
public native String getTag(int cppObjWapper);
//釋放cpp對象
public native void release(int cppObjWapper);
}
這個類主要包含以下幾個功能
1.getCppObjWrapper() 這個方法是一個jni接口,用來返回jni中創建的c++對象的指針,並由java類記錄。
2.jni 中具體實現功能的方法,這些方法都需要傳入c++對象的句柄,用來標識執行哪個對象的方法。
3.release()方法用來釋放c++中的資源,該類實現finalize()方法,也就是在虛擬機gc的時候單獨將這個類放入一個列表,分別執行這個方法,用來釋放資源,這時我們就把c++中相關的內容一起釋放。
jni接口實現
直接上代碼
#include <jni.h>
/* Header for class com_vonchenchen_jnitest_JavaClassDemoWrapper */
#ifndef _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#define _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#include "CppClassDemo.h"
#include "log.h"
#ifdef __cplusplus
extern "C" {
#endif
//對象包裝類 此處包含被包裝的功能類CppClassDemo對象指針和ctx指針
// ctx用於保存一些中間變量
class CppClassWrapper{
public:
CppClassDemo *obj;
void *ctx;
};
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: getCppObj
* Signature: ()I
*
* JNIEXPORT jint JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
* 修改返回值類型爲CppClassWrapper *
*/
JNIEXPORT CppClassWrapper * JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
(JNIEnv *env, jobject obj){
CppClassWrapper *wrapper = new CppClassWrapper();
wrapper->obj = new CppClassDemo();
return wrapper;
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: setTag
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_setTag
(JNIEnv *env, jobject obj, jstring tag, CppClassWrapper *wrapper){
char *cTag = env->GetStringUTFChars(tag, JNI_FALSE);
wrapper->obj->setTag(cTag);
env->ReleaseStringUTFChars(tag, cTag);
env->DeleteLocalRef(tag);
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: getTag
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getTag
(JNIEnv *env, jobject obj, CppClassWrapper *wrapper){
char *tag = wrapper->obj->getTag();
return env->NewStringUTF(tag);
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: release
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_release
(JNIEnv *env, jobject obj, CppClassWrapper *wrapper){
if(wrapper != NULL){
wrapper->obj->release();
delete wrapper;
wrapper = NULL;
}
}
#ifdef __cplusplus
}
#endif
#endif
這裏注意我們並沒有直接創建需要調用的c++對象,而是把這個對象進行了一層包裝,因爲在實際開發中發現有許多中間變量需要存儲,但是我們不能將這些變量定義爲全局,否則多個java對象就會共用這些全局變量。這裏使用CppClassWrapper封裝,增加了一個void *ctx指針,我們可以根據需求定義其他存儲結構,用ctx指針指向這些結構。
c++對象
#include "CppClassDemo.h"
char* CppClassDemo::getTag() {
return mTag;
}
void CppClassDemo::setTag(char *tag) {
if(mTag != NULL){
free(mTag);
mTag = NULL;
}
int len = strlen(tag);
mTag = (char *)malloc(sizeof(char) * len + 1);
strcpy(mTag, tag);
}
void CppClassDemo::release() {
if(mTag != NULL){
free(mTag);
mTag = NULL;
}
}
簡單實現了一個字符串存儲的功能。
調用示例
JavaClassDemoWrapper wrapper1 = new JavaClassDemoWrapper();
JavaClassDemoWrapper wrapper2 = new JavaClassDemoWrapper();
JavaClassDemoWrapper wrapper3 = new JavaClassDemoWrapper();
wrapper1.setTag("I am wrapper1");
wrapper2.setTag("I am wrapper2");
wrapper3.setTag("I am wrapper3");
Log.i(TAG, "wrapper1 "+wrapper1.getTag());
Log.i(TAG, "wrapper2 "+wrapper2.getTag());
Log.i(TAG, "wrapper3 "+wrapper3.getTag());
可以發現此時我們定義的三個java對象中存儲了不同的字符串。我們可以在release中打印標示信息,手動觸發gc,可以發現在垃圾回收的時候release方法會被調用。
代碼鏈接:http://download.csdn.net/detail/lidec/9861699
勘誤
代碼在一部分機器上運作直接崩潰,經過排查發現在32位的arm機器上可以正常運作,但是換成x86或者64位arm都會崩潰。這裏打印地址我們會發現64位機器返回c++對象的地址數值比較大,會超出32位,也就是java的int類型不可以用來保存c++對象的地址。此處必須使用long類型接收c++對象的地址。
修改
在JavaClassDemoWrapper類中,需要做如下修改,創建對象後直接返回一個long類型,同時填入對象參數也是long類型
//用long類型保存c++類對象的地址
private long mCppObjWapper;
......
//獲取cpp對象指針
public native long getCppObjWrapper();
//調用cpp對象中對應的方法
public native void setTag(String tag, long cppObjWapper);
public native String getTag(long cppObjWapper);
//釋放cpp對象
public native void release(long cppObjWapper);
jni文件也需要將對應的int型改爲long型
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_vonchenchen_jnitest_JavaClassDemoWrapper */
#ifndef _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#define _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#include "CppClassDemo.h"
#include "log.h"
#ifdef __cplusplus
extern "C" {
#endif
//對象包裝類 此處包含被包裝的功能類CppClassDemo對象指針和ctx指針
// ctx用於保存一些中間變量
class CppClassWrapper{
public:
CppClassDemo *obj;
void *ctx;
};
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: getCppObj
* Signature: ()I
*
* JNIEXPORT jint JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
* 修改返回值類型爲CppClassWrapper *
*/
JNIEXPORT jlong JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
(JNIEnv *env, jobject obj){
CppClassWrapper *wrapper = new CppClassWrapper();
wrapper->obj = new CppClassDemo();
LOGE("getCppObjWrapper addr %p", wrapper);
return wrapper;
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: setTag
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_setTag
//(JNIEnv *env, jobject obj, jstring tag, CppClassWrapper *wrapper){
(JNIEnv *env, jobject obj, jstring tag, long handle){
LOGE("setTag addr %p", handle);
//int real_handle = (int)handle;
long real_handle = handle;
CppClassWrapper *wrapper = real_handle;
char *cTag = env->GetStringUTFChars(tag, JNI_FALSE);
wrapper->obj->setTag(cTag);
env->ReleaseStringUTFChars(tag, cTag);
env->DeleteLocalRef(tag);
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: getTag
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getTag
//(JNIEnv *env, jobject obj, CppClassWrapper *wrapper){
(JNIEnv *env, jobject obj, long handle){
//int real_handle = (int)handle;
long real_handle = handle;
CppClassWrapper *wrapper = real_handle;
char *tag = wrapper->obj->getTag();
return env->NewStringUTF(tag);
}
/*
* Class: com_vonchenchen_jnitest_JavaClassDemoWrapper
* Method: release
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_release
//(JNIEnv *env, jobject obj, CppClassWrapper *wrapper){
(JNIEnv *env, jobject obj, long handle){
//int real_handle = (int)handle;
long real_handle = handle;
CppClassWrapper *wrapper = real_handle;
if(wrapper != NULL){
wrapper->obj->release();
delete wrapper;
wrapper = NULL;
}
}
#ifdef __cplusplus
}
#endif
#endif