NDK學習筆記:java類封裝c++類

背景

在最近的開發中遇到了這樣的一個場景,使用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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章