Android JNI使用笔记

最近在使用JNI,实现android调用C代码功能并返回结果的功能,于是记下来整个流程以作记录。

JNI(Java Native Interface),可以实现Java代码与C/C++代码之间的相互调用。

本文分为以下几部分:

1.JNI基础使用

2.JNINativeMethod数据类型描述符

3.JNI获取java自定义类中数据

1.JNI的基础使用

使用之前需要使用Android Studio下载NDK(SDK Manager—>SDK Tools—>NDK)。

创建一个用来连接C的类,所有需要C用到的方法都写这个类里面,如下面 native修饰的方法。静态区域内加载了名为libjnitest.so的库,只需要写jnitest即可。创建完native方法会报错,先不理会,继续往下走错误会自动消失。在getTestData中使用native方法,可以在activity中调用getTestData方法。

public class AndroidJni {

    static{
        System.loadLibrary("jnitest");
    }

    private native int getTestData(TestData data);

    public String getTestData(int testI){
        TestData data = new TestData();
        data.setData(testI);
        int i = getTestData(data);
        return "数据:" + i;
    }
}

创建一个专门存放C代码的文件夹(右键—>New—>Floder—>JNI Floder),文件夹结构如下

在cpp文件中创建C文件代码(右键—>New—>C/C++ Source File,C选择.C,C++选择.cpp,有可能选择.C但是文件图标显示的c++,没有影响)

static jint jni_getTestData(JNIEnv* env,jclass thiz, jobject data){
    jclass jcobs = (*env)->GetObjectClass(env, data);
    jfieldID jdata = (*env)->GetFieldID(env, jcobs, "data", "I");
    jint ji = (*env)->GetIntField(env, data, jdata);
    return ji;
}

static JNINativeMethod nativeMethods[] = {
    {"getTestData","(Lcom/ly/jnitest/TestData;)I", (void*)jni_getTestData}
};

int registerNatives(JNIEnv* env) {
    /* look up the class */
    jclass clazz = (*env)->FindClass(env, "com/ly/jnitest/AndroidJni");

    if (clazz == NULL)
       return 0;

    if ((*env)->RegisterNatives(env, clazz, nativeMethods, sizeof(nativeMethods)
        / sizeof(nativeMethods[0])) != JNI_OK)
       return 0;

    return 1;
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    (void)reserved;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
        return -1;

    if (!registerNatives(env))
        return -1;

    /* success -- return valid version number */
    result = JNI_VERSION_1_6;

    return result;
}

上面是jnitest.c文件代码,JNI_Onload方法相当于onCreate方法,会自动执行这个方法,执行registerNatives方法找到android中放C使用方法的文件(AndroidJni),然后使用RegisterNatives方法完成C与Java方法的联系。使用JNINativeMethod结构体描述android方法。

typedef struct {
    const char* name;//java中方法名
    const char* signature;//java方法传值与返回值
    void*       fnPtr;//C中方法名
} JNINativeMethod;

如{"getTestData","(Lcom/ly/jnitest/TestData;)I", (void*)jni_getTestData}中第一个参数为java文件中Native方法名,第二个参数()中为java方法的传值类型,括号后为java方法的返回值类型,第三个参数为c文件中与java方法对应的方法名。JNINativeMethod中使用数据类型在第二节统一记录。

jni_getTestData方法中主要取出java中TestData类中data数据,然后返回取出的数据。JNI中怎么取出java中数据在第三节统一记录。

再在cpp文件中创建Android.mk、Application.mk(右键—>New—>File)

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jnitest
LOCAL_SRC_FILES := jnitest.c

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH:Android.mk必须以它开始

LOCAL_MODULE:定义生成so库名字,与java中LoadLibrary中一致

LOCAL_SRC_FILES :列出要打包的所有C/C++ 源码

 

Application.mk:

APP_ABI := all

 

最后在build.gradle(app)中,添加两段代码 ,点击sync now按钮

android.defaultConfig中添加

ndk {
    moduleName "jnitest"
}

android中添加

externalNativeBuild {
    ndkBuild {
        path 'src/main/jni/Android.mk'
    }
}

以上代码部分就全部结束,然后运行代码,可能会报找不到生成的so库,这时需要在android studio下方点击Terminal窗口,跳转至jni文件目录(输入 cd E:\XXX\app\src\main\jni),然后输入ndk-build就会生成so库,再运行代码,就会成功。输入ndk-build还可以检查C代码中的错误,有错误将会生成不了so库。

2.JNINativeMethod数据类型描述符

本节主要记录JNINativeMethod结构体中第二个参数该怎么写。以"(Lcom/ly/jnitest/TestData;)I"为例,括号中为java中传递的参数类型。

基本类型:

java类型C字符
byteB
charC
shortS
intI
longJ
floatF
doubleD
booleanZ

 引用类型(需以L开头 ;结尾):

java类型C字符
StringLjava/lang/String;
TestData(自定义类)Lcom/ly/jnitest/TestData;

 引用类型镶嵌类(使用的是类中的内部类):使用$符号隔开

  Lcom/ly/jnitest/TestData$Native;

 数组类型:在C字符前添加 [ 符号,如 [B 表示 byte[]类型。二维数组添加[[ 符号,依此类推。

 如果java中没有传递参数,括号里空的即可,如果有多个传递参数,可以写成 "(BB)",直接连在一起,也可以为了直观写成

"("
"B"
"B"
")"

括号后面为java方法中定义的返回值,与上面使用的字符一样,直接跟在括号后面,只比传递参数多了一个void返回类型,在括号后面跟上V字符,例如"()V",对应的java就是 void XX()方法。

 3.JNI获取java自定义类中数据

本节主要讲java传递参数为自定义类,JNI中怎么获取类中各个参数。通过

jclass jc = (*env)->GetObjectClass(env, data);

获取到 jcalss,data为传递的自定义类。然后

jfieldID jdata = (*env)->GetFieldID(env, jc, "data", "I");

获取到FieldId,GetFieldID函数中第二个参数为jclass类型,第三个参数是自定义类中成员名称,第四个参数是成员对应的类型字符(字符参考第二节内容,使用是相同的规则)。

最后通过FieldId获取数据,如果是自定义类中的基本类型参数可以通过GetIntField、GetDoubleField等对应的基本类型的方法获取到,如

jint str = (*env)->GetIntField(env, data, jdata);

 第二个参数为java传递参数,第三个参数为FieldId。

如果自定义类中参数为String,需要使用

jstring jstr = (jstring)(*env)->GetObjectField(env, data, jfieldid);

参数为自定义类

jobject jobj = (*env)->GetObjectField(env, data, jfieldid);

参数为数组

jdoubleArray jarray = (jdoubleArray)((*env)->GetObjectField(env, data, jfieldid));

获取数组中数据并赋值给C类型:

jdouble* array = (*env)->GetDoubleArrayElements(env,jarray, NULL);
uint32_t arraysize = (*env)->GetArrayLength(env, array);//获取长度
int j = 0;
for(j = 0; j < arraysize; j++) {
    P[j] = array[j];
}

 到这里,基本用到的JNI的内容就已经记录完了。

 实例代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值