Android JNI学习

2017-12-26  本文已影响71人  DramaScript

本篇总结Android JNI如何通过两种不同的构建方式与C/C++进行交互,话不多少先上两张效果图,注意:本项目是基于Android Studio3.0开发的。


图1.png
图2.png

以上就是与C/C++语言交互的内容部分分,涵盖了开发基本上的需求,让我进入正题

两种不同的构建方式

以下介绍一种基于CMake的方式来构建,一种是基于比较传统的mk文件构建方式,虽然基于CMake的方式更加的只能,但是也是基于传统的方式来的,这种方式只能适用于Android Studio 2.2以上的版本。无论基于什么构建,首先,你必须要下载Android NDK开发包。点我下载

基于CMake方式

基于CMake方式构建的项目,在写C++和C代码的时候可以与写Java代码一样的有提示,而且还可以直接的debug。
打开一个项目,从菜单栏中选择 Tools > Android > SDK Manager。
点击 SDK Tools 选项卡。
勾选 LLDB,CMake 和 NDK。如图:


setting.png
创建支持C/C++的项目

点击新建项目,勾选以下选项以支持C/C++


图3.png
cmake_minimum_required(VERSION 3.4.1)

include_directories(src/main/cpp/include/)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

为了让 CMake 将源代码(native source code)编译成 native library。需要在编译文件中添加 cmake_minimum_required() 和 add_library() 命令。
看下图:

图6.png
include_directories(src/main/cpp/include/)这行代码的意思就是当你使用 add_library(),将一个源文件(source file)或库添加到你的 CMake 构建脚本,同步你的项目,然后你会发现 Android studio 将关联的头文件也显示了处理。然而,为了让 CMake 在编译时期能定位到你的头文件,你需要在 CMake 构建脚本中添加 include_directories() 命令,并指定头文件路径。
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

这段代码就是如果你的native-lib代码中有引用别的头文件,在这里就需要把头文件链接到这里,这个与add_library的顺序是一致的。
最后在Java代码中load的so库是与上面native-lib是一致的。

  static {
        System.loadLibrary("native-lib");
    }

至此,一个完整的jni项目就集成完毕,至于如何引用第三方C/C++的库到CMake中,后期会介绍将librtmp,FFmpeg集成到Android中来讲解。

传统的构建JNI项目的方式

这个方式比较古老了,相信从eclipse转过来的童鞋都知道,这里我就不详细讲述这种方式的集成,这里推荐大家看这位大神的一篇博客:看不到我,看不到我
我这里就详细总结一下Android.mk与Application.mk文件的编写。

Android.mk

Android.mk是Android提供的一种makefile文件,用来指定诸如编译生成so库名、引用的头文件目录、需要编译的.c/.cpp文件和.a静态库文件

最基本的格式应该是这样:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_xxx       := xxx  
LOCAL_MODULE := NdkTools
LOCAL_SRC_FILES := NdkTools.cpp
LOCAL_xxx       := xxx  

include $(BUILD_SHARED_LIBRARY)

下面详细说说中间比较常见的东西吧:

可能以后还会经常用到一些以“LOCAL_”开头的编译变量,这些变量是啥意思,可以参考这篇文章:看不到我,看不到我

使用C代码通过JNI与Java代码沟通

好了,前面铺垫完了,接下才是编写代码中经常需要用到的地方

JNIEXPORT jboolean

JNICALL Java_com_dramascript_ndktest_NdkTools_getBoolean
        (JNIEnv *evn, jobject obj, jboolean b) {
    unsigned char bo = b;
    bo = 1;
    return (jboolean)bo;
}
JNIEXPORT jbyte

JNICALL Java_com_dramascript_ndktest_NdkTools_getByte
        (JNIEnv *evn, jobject obj, jbyte b) {
    int i = b;
    i = 15;
    return (jbyte)
    i;
}
JNIEXPORT jchar

JNICALL Java_com_dramascript_ndktest_NdkTools_getChar
        (JNIEnv *evn, jobject obj, jchar c) {
    char ch = c;
    ch = 'S';
    return (jchar)
    ch;
}
JNIEXPORT jshort

JNICALL Java_com_dramascript_ndktest_NdkTools_getShort
        (JNIEnv *evn, jobject obj, jshort s) {
    short st = s;
    st = 100;
    return (jshort)
    st;
}
JNIEXPORT jint

JNICALL Java_com_dramascript_ndktest_NdkTools_getInt
        (JNIEnv *evn, jobject obj, jint i) {
    int a = i;
    a = 1024;
    return (jint)
    a;
}
JNIEXPORT jlong

JNICALL Java_com_dramascript_ndktest_NdkTools_getLong
        (JNIEnv *evn, jobject obj, jlong l) {
    long lg = l;
    lg = 1024L;
    return (jlong)
    lg;
}
JNIEXPORT jfloat

JNICALL Java_com_dramascript_ndktest_NdkTools_getFloat
        (JNIEnv *evn, jobject obj, jfloat f) {
    float ft = f;
    ft = 23;
    return (jfloat)
    ft;
}
JNIEXPORT jdouble

JNICALL Java_com_dramascript_ndktest_NdkTools_getDouble
        (JNIEnv *evn, jobject obj, jdouble d) {
    double dl = d;
    dl = 2048.12;
    return (jdouble)
    dl;
}
JNIEXPORT jstring

JNICALL Java_com_dramascript_ndktest_NdkTools_getString
        (JNIEnv *env, jobject obj, jstring s) {

    char *st = (char *) env->GetStringUTFChars(s, 0);
    char *str = "我很好!";
    jstring rtn;
    rtn = env->NewStringUTF(str);
    return rtn;
}
JNIEXPORT jbyteArray

JNICALL Java_com_dramascript_ndktest_NdkTools_getByteArray
        (JNIEnv *evn, jobject obj, jbyteArray ba) {

    //获得byte数组
    jbyte * bytes = evn->GetByteArrayElements(ba, 0);
    int chars_len = evn->GetArrayLength(ba);
    //返回新的byte数组
    jbyteArray arr = evn->NewByteArray(6);
    jbyte * by = evn->GetByteArrayElements(arr, 0);
    char ch[10] = "abcd";
    for (int i = 0; i < 4; ++i) {
        by[i] = ch[i];
    }
    evn->SetByteArrayRegion(arr, 0, 6, by);
    return arr;

}
JNIEXPORT jobject

JNICALL Java_com_dramascript_ndktest_NdkTools_getObject
        (JNIEnv *env, jobject obj, jobject paramIn) {

    //获取
    jclass paramInClass = env->GetObjectClass(paramIn);
    if (paramInClass == NULL) {
        return NULL;
    }
    if (env->IsInstanceOf(obj, paramInClass))//判断jobject是否是某个jclass类型。
    {
        jboolean iscopy;
        jfieldID intId = env->GetFieldID(paramInClass, "age", "I");
        jint num = (int) env->GetIntField(paramIn, intId);

        jfieldID strId = env->GetFieldID(paramInClass, "name", "Ljava/lang/String;");
        jstring str = (jstring)(env)->GetObjectField(paramIn, strId);
        const char *locstr = env->GetStringUTFChars(str, &iscopy);

        env->ReleaseStringUTFChars(str, locstr);
    }

    //返回
    jclass cls = env->FindClass("com/dramascript/ndktest/User");
    jmethodID id = env->GetMethodID(cls, "<init>", "()V");

    jobject paramOut = env->NewObjectA(cls, id, 0);

    jfieldID intId = env->GetFieldID(cls, "age", "I");
    env->SetIntField(paramOut, intId, 23);

    jfieldID strId = env->GetFieldID(cls, "name", "Ljava/lang/String;");
    env->SetObjectField(paramOut, strId, (jstring)(env)->NewStringUTF("林俊杰"));

    return paramOut;
}

JNIEXPORT jobjectArray

JNICALL Java_com_dramascript_ndktest_NdkTools_getObjectArray
        (JNIEnv *env, jobject _obj, jobjectArray objarr) {

    //获取
    //JNI提供了两个函数来访问对象数组,GetObjectArrayElement返回数组中指定位置的元素,
    // SetObjectArrayElement修改数组中指定位置的元素。与基本类型不同的是,我们不能一次得到数据
    // 中的所有对象元素或者一次复制多个对象元素到缓冲区。
    jobject objects = env->GetObjectArrayElement(objarr, 0);//返回第0个对象
    jsize length = (env)->GetArrayLength(objarr);

    //返回

    //申明一个object数组
    jobjectArray args = 0;
    //获取object所属类,一般为ava/lang/Object就可以了
    jclass objClass = (env)->FindClass("java/lang/Object");
    //新建object数组
    args = (env)->NewObjectArray(5, objClass, 0);


    //给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中
    for (int i = 0; i < 5; i++) {
        jclass cls = env->FindClass("com/dramascript/ndktest/User");
        jmethodID id = env->GetMethodID(cls, "<init>", "()V");

        jobject paramOut = env->NewObjectA(cls, id, 0);

        jfieldID intId = env->GetFieldID(cls, "age", "I");
        env->SetIntField(paramOut, intId, 23);

        jfieldID strId = env->GetFieldID(cls, "name", "Ljava/lang/String;");
        env->SetObjectField(paramOut, strId, (jstring)(env)->NewStringUTF("书戏"));

        //添加到objcet数组中
        (env)->SetObjectArrayElement(args, i, paramOut);
    }
    return args;
}

JNIEXPORT jobject

JNICALL Java_com_dramascript_ndktest_NdkTools_getObjectList
        (JNIEnv *env, jobject obj) {

    jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//获得ArrayList类引用
    jmethodID list_costruct = env->GetMethodID(list_cls, "<init>", "()V"); //获得得构造函数Id
    jobject list_obj = env->NewObject(list_cls, list_costruct); //创建一个Arraylist集合对象
    //或得Arraylist类中的 add()方法ID,其方法原型为: boolean add(Object object) ;
    jmethodID list_add = env->GetMethodID(list_cls, "add", "(Ljava/lang/Object;)Z");

    jclass cls = env->FindClass("com/dramascript/ndktest/User");
    jmethodID id = env->GetMethodID(cls, "<init>", "()V");

    for (int i = 0; i < 3; i++) {
        jobject paramOut = env->NewObject(cls, id);
        env->CallBooleanMethod(list_obj, list_add, paramOut); //执行Arraylist类实例的add方法,添加一个stu对象
    }
    return list_obj;
}
JNIEXPORT void JNICALL Java_com_dramascript_ndktest_NdkTools_callSetData
(JNIEnv *env, jobject obj){

    jclass clz = env->FindClass("com/dramascript/ndktest/NdkTools");
    jmethodID mid = env->GetMethodID(clz, "setData", "(Ljava/lang/String;I)V");
    env->CallVoidMethod(obj, mid, env->NewStringUTF("haha in C ."),20);
    

}

注意:在C++中调用JNI头文件的方法和在C代码中调用的方法传的参数是不一样的,如下:

//在C++可以少env这个参数
 jfieldID intId = env->GetFieldID(paramInClass, "age", "I");

//在C里面是不能缺少的
 jfieldID intId = (*env)->GetFieldID(*env,paramInClass, "age", "I");

至此,结束,代码在:Demo代码,如果觉得不错,给点打赏呗。

上一篇 下一篇

猜你喜欢

热点阅读