android中JNI使用探究

最近一直比较忙,在做前端和后台相关的东西。主要研究方向是vue和node,所以博客很久没更新了。JNI是android里面比较难的一个环节,咱们先想一下,为什么jni比较难,有一句话叫做不明觉厉,你不知道他,不懂他,心里就觉得这个东西很高大上,很厉害。但是我想说的是,在android这一块,jni还没你想象的难。但是在开始jni的学习之前,有两点你必须要知道,其一:基本的android nkd开发配置你要懂,其二:基本的c语法你要懂。jni的本质就是android调用c或c++的代码来实现需求。主要使用的范围在音视频编解码等方面,因为我们都知道,c可以直接操作系统内存,像耗费性能比较大的编解码操作,c在使用的性能上远远高于java代码。那么开始撸码吧!!!!

那就从第一步开始,android的基本配置:

从AS2.0开始android对jni的支持越来越好,到AS3.0可以说对jni的支持好到爆炸!新创建的项目在勾选c支持的时候,基本上所有的jni支持都配置好了,你要做的就是在指定的jni开发文件夹下,编写你的c代码。时代在更新,大家在进步,我们本次的工程就以AS3.0开发环境。在开始ndk开发的前,下载lldb与cmake插件,这两个插件一个是c代码调试工具,一个是ndk编译配置工具,当然jni编译工具ndk你得有



ok!下载完这些,环境配置就是这么easy。配置完这个剩下的就是代码部分了,这里还要说一下cmake配置

cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
             cpp-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/cpp2.c )     //配置c文件入口

find_library( # Sets the name of the path variable.
              log-lib                    

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )            //引入ndk log模块

target_link_libraries( cpp-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )  //生成动态库

app/gradle 文件配置

apply plugin: 'com.android.application'
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.iwintrue.todoproject"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
            }
        }
        ndk{
            abiFilters "armeabi","armeabi-v7a","x86"  //配置生成不同cpu架构的so文件
        }

        //ndk编译
        ndk {
            moduleName "cpp-lib"                       //生成so文件的名称
 
        }
//        sourceSets.main {                                

//            jni.srcDirs = []                    
//        }
//        task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
//            commandLine '/Users/zhoukai/Library/Android/sdk/ndk-bundle/ndk-build',//这里本地ndk的路径
//                    'NDK_PROJECT_PATH=build/intermediates/ndk',
//                    'NDK_LIBS_OUT=src/main/jniLibs',
//                    'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
//                    'NDK_APPLICATION_MK=src/main/jni/Application.mk'
//        }
//        tasks.withType(JavaCompile){
//            compileTask->compileTask.dependsOn ndkBuild
//        }      
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"                
        }
    }
}

主要看红色部分,其中gradle中的注释部分是ndk使用.mk文件的配置,现在已经不用了,可以了解一下。

java 部分:

public class JNI {

    {
        System.loadLibrary("cpp-lib");
    }

    public native String getCString();

    //利用c返回计算的和
    public  native int  getSum(int a,int b);
    //将java字符串与c字符串相加
    public  native String getCatString(String javaString);
    //回调c的数组运算
    public  native  int[] getCArray();

    //返回c中的字符串数组
    public  native  String[] getCStringArray(String _str);
    //返回c中的char数组
    public  native  char[] getCCharArray();
    //检查密码是否正确
    public  native  boolean checkPassWord(String pass);
    //使用java方法
    public  native  void useJavaMethod();
    //返回结构体
    public  native  JavaStruct callBackJavaObj();
    

}

声明jni方法很简单,只要在方法声明中添加native关键字就可以,我们定义几个简单的方法,只要你掌握了这些基本操作,那么你就算开启了jni的大门,万丈高楼平地起,从打地基开始吧

c代码:

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <android/log.h>

#define TAG "TODO" // 这个是自定义的LOG的标识
//引入android/log.h文件后,宏定义函数名,方便调用,之后可以在控制台打印出log日志
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型

jint sum(jint a, jint b) {
    return a + b;
}

/**

* 工具方法

* 作用: 把java中的string 转化成一个c语言中的char数组

* 接受的参数 envjni环境的指针

* jstr 代表的是要被转化的java的string 字符串

* 返回值 : 一个c语言中的char数组的首地址 (char 字符串)

*/

char *Jstring2CStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");

    //jstring strencode = (*env)->NewStringUTF(env,"GB2312");
    jstring strencode = (*env)->NewStringUTF(env, "utf-8");

    jmethodID mid =

            (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");

// String.getByte("GB2312");

    jbyteArray barr =

            (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid, strencode);

    jsize alen = (*env)->GetArrayLength(env, barr);

    jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);

    if (alen > 0) {

        rtn = (char *) malloc(alen + 1); //"\0"

        memcpy(rtn, ba, alen);

        rtn[alen] = 0;

    }

    (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //

    return rtn;
}


/**
 * 返回c语言字符串
 * @param env
 * @param obj
 * @return
 */

JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCString(
        JNIEnv *env,
        jobject obj) {
    char *d;  //c语言没有字符串类型,使用char类型指针可以表示连续的字符地址,也就是字符串
    char str[20] = "c返回的str";
    d = str;
    return (*env)->NewStringUTF(env, d);
}

/**
 * 返回c语言字符串
 * @param env
 * @param obj
 * @return
 */

JNIEXPORT jint Java_com_iwintrue_todoproject_ndk_JNI_getSum(
        JNIEnv *env,
        jobject obj, jint a, jint b) {
    jint total = sum(a, b);    //使用定义好的函数求和

    return total;

}

JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCatString
        (JNIEnv *env, jobject obj, jstring javaString) {


    char *cstr = Jstring2CStr(env, javaString);
    char *hellostr = "这是c的字符串";
    strcat(cstr, hellostr); //拼接两个字符串    //使用string.h中的strcat拼接字符串

    LOGI("%s", cstr);
    return (*env)->NewStringUTF(env, cstr);

}


/**
 * 返回c数组
 * @param env
 * @param instance
 * @return
 */

JNIEXPORT jintArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCArray(JNIEnv *env, jobject instance) {

    //创建数组
    jintArray array = (*env)->NewIntArray(env, 10);    //创建数组
    //获取数组指针
    jint *array_pi = (*env)->GetIntArrayElements(env, array, NULL);  //获取数组指针,这里是获取的数组第一个索引值的指针

    for (int i = 0; i < 10; i++) {
        array_pi[i] = i;
    }
    //4.释放资源
    (*env)->ReleaseIntArrayElements(env, array, array_pi, 0);    //获取到数组之后,别忘了释放c开辟的内存空间
    return array;
}

JNIEXPORT jobjectArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCStringArray(JNIEnv *env, jobject instance, jstring str_) {

    jsize len = 10;
    //获取java中的String类
    jclass objClass = (*env)->FindClass(env, "java/lang/String");    //获取到java String类的字节码文件
    //获取concat方法    //获取到String拼接字符串的方法,因为c中的strcat在for循环中有内存溢出问题,所以使用java方法来拼接字符串
    jmethodID concat_methodID = (*env)->GetMethodID(env, objClass, "concat",
                                                    "(Ljava/lang/String;)Ljava/lang/String;");// "(Ljava/lang/String;)Ljav/lang/String;" 函数签名                                                                                                 
 //创建数组
    jobjectArray array = (*env)->NewObjectArray(env, len, objClass, 0);

    for (int i = 0; i < 10; i++) {

        char index[25];
        //将整数转化为string
        sprintf(index, " %d", i);

        jstring strObject = (*env)->NewStringUTF(env, "字符串");
        jstring str = (*env)->NewStringUTF(env, index);

        jobject str1 = (*env)->CallObjectMethod(env, strObject, concat_methodID, str);

        const char *chars = (*env)->GetStringUTFChars(env, (jstring) str1, 0);

        jstring jstring1 = (*env)->NewStringUTF(env, chars);
        (*env)->SetObjectArrayElement(env, array, i, jstring1);
        (*env)->ReleaseStringUTFChars(env, str1, chars);

    }

    return array;

}

JNIEXPORT jcharArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCCharArray(JNIEnv *env, jobject instance) {

    jcharArray charArray = (*env)->NewCharArray(env, 10);
    jchar *pi = (*env)->GetCharArrayElements(env, charArray, 0);

    for (int i = 0; i < 10; i++) {
        pi[i] = "e";
    }
    //数组释放
    (*env)->ReleaseCharArrayElements(env, charArray, pi, 0);

    return charArray;

}

JNIEXPORT jboolean JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_checkPassWord(JNIEnv *env, jobject instance,jstring pass) {

    jstring  originPass = (*env)->NewStringUTF(env,"12345");
    char* s1 = (*env)->GetStringUTFChars(env,originPass,JNI_FALSE);
    char* s2 = (*env)->GetStringUTFChars(env,pass,0);
    LOGI("s1:%s",s1);
    LOGI("s2:%s",s2);
    int equ =  strcmp(s1,s2);
    (*env)->ReleaseStringChars(env,originPass,s1);
    (*env)->ReleaseStringChars(env,pass,s2);

    if(equ==0){
        return 1;
    } else{
        return 0;
    }

}

/**
 * 使用java方法
 * @param env
 * @param instance
 * javap -s class
 */

JNIEXPORT void JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_useJavaMethod(JNIEnv *env, jobject instance) {

    //获取java Class
    jclass jniUtils = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/JniUtils");

    //获取要调用的方法Id
    jmethodID methodId = (*env)->GetMethodID(env,jniUtils,"add","(II)I");
    //获取jobject
    jobject  job = (*env)->AllocObject(env,jniUtils);
    //调用该方法
    jint  sum = (*env)->CallIntMethod(env,job,methodId,10,20);

    LOGI("%d",sum);

}
/**
 * 刷新ui
 * @param env
 * @param instance
 */

JNIEXPORT void JNICALL
Java_com_iwintrue_todoproject_ndk_Main2Activity_refreshUi(JNIEnv *env, jobject instance) {

   //获取class
    jclass  objClass = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/Main2Activity");
    //执行环境  方法名 方法签名
    jmethodID  methodId = (*env)->GetMethodID(env,objClass,"toastText","()V");

    (*env)->CallVoidMethod(env,instance,methodId);
}

/**
 * 返回结构体
 * @param env
 * @param instance
 * @return
 */

JNIEXPORT jobject JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_callBackJavaObj(JNIEnv *env, jobject instance) {

    //btn_callback_stuct
    //获取class
    jclass  javaStruct = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/JavaStruct");
    jobject javaStructObj = (*env)->AllocObject(env,javaStruct);

    jfieldID age = (*env)->GetFieldID(env,javaStruct,"age","I");
    jfieldID name = (*env)->GetFieldID(env,javaStruct,"name","Ljava/lang/String;");
    jstring  nameValue = (*env)->NewStringUTF(env,"张三");

    (*env)->SetIntField(env,javaStructObj,age,19);
    (*env)->SetObjectField(env,javaStructObj,name,nameValue);

    return javaStructObj;

}

c代码中的注释已经很详细了,其中包括了java调用c代码以及c代码调用java代码。调用c代码中有分别有返回字符串,函数求和,返回基本类型数组,返回object数组,回调ui刷新。

注意:<ul>
<li>java不能直接返回c类型变量,要通过env这个结构体来转换</li>
 <li>创建c数组的时候,别忘了内存释放<li>
 <li>在调用ui相关的方法时,因为对象是根据反射获取到的,activity没有生命周期,所以要讲native方法定义到activity中<li>

        

</ul>

执行结果截图


控制台输出:


在jni的使用中,还有一种情景是存在的。那就是我们调用其他so文件中的方法,然后重新生成我们的so文件。这时候就要重新配置一下cmake文件来加载第三方的so文件,然后创建头文件引入。最后在新的.c文件中调用这个方法就可以了。看代码:

java代码:

public class JNI2 {
//
    {

        System.loadLibrary("cpp-lib");    //先引入你调用的so文件
        System.loadLibrary("cpp2-lib");   //生成新的so文件
    }


    public native String  callJNIBack();

}

c代码:

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <android/log.h>
#include "cpp2.h"        //创建.h文件 这个文件可以自定义名称

#define TAG "TODO" // 这个是自定义的LOG的标识

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型




JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI2_callJNIBack(JNIEnv *env, jobject obj) {

    jstring  str  = Java_com_iwintrue_todoproject_ndk_JNI_getCString(env,obj);    //引用cpp-lib中的方法返回jstring


    return str;

}

头文件:

#include <jni.h>


JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCString
        (JNIEnv *, jclass);

修改cmake文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)



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

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/cpp3.c )


add_library(cpp-lib STATIC IMPORTED)
#添加动态库的路径
set_target_properties(cpp-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jni1/${ANDROID_ABI}/libcpp-lib.so)


find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )


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

运行效果:


这个字符串是在cpp-lib中返回的。到此jni的运用你就基本掌握了,之后我运用我所说的内容搞一搞ffempg,敬请期待!

工程github地址 


猜你喜欢

转载自blog.csdn.net/ZACH_ZHOU/article/details/79681482