Android NDK和JNI简略总结、使用示例

一、JNI (Java Native Interface)

    JNI是Java提供的与系统Native库交互的一种机制。

    Java代码运行在JVM中,JVM可以跟操作系统来调用系统的Native动态链接库。

    这些动态链接库由C/C++源代码来编译生成,在不同操作系统中格式不同,例如在linux平台下会编译为.so或.a文件,在window平台下会编译为dll文件,在mac os下会编译为jnilib。

    Android是基于Linux的,所以在Android平台上JNI调用的动态连接库是.so文件。

    从效果上讲,把JNI理解成通过Java来与C/C++代码交互的机制也可以。(实际是与这些C/C++编译成的动态链接库交互。)

    参考剪藏:详解JNI到底是什么

            

二、NDK (Native Development Kit)

    NDK是Android提供的一个开发工具包,主要是方便开发者将C/C++代码编译成Android中的动态链接库(.so)。

    这样开发者就可以用Java代码通过JNI方式来调用这些.so库中的方法。

之所以不完全用Java代码,而是要把部分功能封装在.so中,原因主要是:

1.部分功能需要较高的执行效率,这方面java远不如C/C++;

2.出于安全考虑,java代码较容易被反编译,而C/C++编译成的动态链接库(.so库)反编译难度很大。

   一些关键而机密的信息,例如一些ID值,应该存放在.so库中。

&&.a库和.so库的区别:

    Linux下的链接库分两种,.a静态链接库和.so动态链接库。同样的源代码,一般打出的静态库体积较大。

1.a静态库在编译时就会被连接到目标代码,最终会被整合到目标代码中输出。

   静态库只能被native层调用,在NDK开发中,一般用于支持别的静态库或动态库打包。

2.so动态库在编译时不会被连接到目标代码,而是运行时才会在.so中找目标方法来执行。

   动态库可被natvie层调用,java层通过JNI方式也可调用。很显然,我们一般需要打的是能供java层调用的.so动态库。

参考剪藏:Android NDK 简介 - JNI/NDK 开发指南 - UDN开源文档

三、JNI、NDK在AndroidStudio下的简单使用示例

现在Android官方文档主要讲得都是如何用CMake方式来进行NDK编译,感觉这是官方所提倡的方式。

而且CMake的编译脚本是跨平台的,所以我们这里用CMake的方式来演示一个简单的.so包的编译。

1.前期准备工作

1.1首先要在AndroidStudio的SDKManager中下载NDK和CMake

1.2在local.properties中配置好NDK的路径 

ndk.dir=“NDK在本机的具体安装路径”

2.编写代码

2.1 Java代码:根据自己的业务需要,在某个Java类中创建需要的以native修饰的方法。以下为示例:

public class JniDemo {
    static {
        System.loadLibrary("jni_demo");
    }

    public native static long testLong();
    public native static String testString();
    public native static byte[] testBytes();
}

2.2 c/c++代码:在src/main文件夹下新建jni目录,将c/c++源文件存放其中,实现native层的逻辑。

以下为示例jni_demo.cpp,这里使用动态注册的方式:

//必须引入jni.h,才能供java层调用相应方法
#include "jni.h"

//下面具体实现三个供java层调用的native方法
static jlong testLong(JNIEnv *env, jobject callObj)
{
    return 1111L;
}

static jstring testString(JNIEnv *env, jobject callObj)
{
    const char *str = "hello world!";
    return (*env).NewStringUTF(str);
}

static jbyteArray testBytes(JNIEnv *env, jobject callObj)
{
    jbyte res[8] = {0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25};
    jbyteArray data = env->NewByteArray(8);
    env->SetByteArrayRegion(data,0,8, (jbyte*)res);
    return data;
}

//目标类:包路径+类名
#define JNI_JAVA_CALLER     "定义native方法的Java类包路径"
//方法数量
#define METHOD_NUM 3
//方法结构定义
//java中方法名,(传入参数类型)返回类型,c/cpp中对应的方法
JNINativeMethod g_nativeMethod[METHOD_NUM]={
        {"testLong","()J",(void*)testLong},
        {"testString","()Ljava/lang/String;",(void*)testString},
        {"testBytes","()[B",(void*)testBytes}
};

/*
 * 被虚拟机自动调用
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
        return JNI_ERR;
    //注册目标类与native方法
    jclass jClass = env->FindClass(JNI_JAVA_CALLER);
    env->RegisterNatives(jClass,g_nativeMethod,METHOD_NUM);
    env->DeleteLocalRef(jClass);
    return JNI_VERSION_1_6;
}

void JNI_OnUnload(JavaVM* vm, void* reserved) {
    JNIEnv *env;
    int nJNIVersionOK = vm->GetEnv((void **)&env, JNI_VERSION_1_6) ;
    jclass jClass = env->FindClass(JNI_JAVA_CALLER);
    env->UnregisterNatives(jClass);
    env->DeleteLocalRef(jClass);
}

2.3 编译文件:在src/main/jni下新建CMakeList.txt,写编译脚本

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.

add_library(
             # 设置jni编译生产的native库的名字
             jni_demo

             # 设置输出native包的类型,
             # SHARED为动态库.so
             # STATIC为静态库.a
             SHARED

             # Provides a relative path to your source file(s).
             # Associated headers in the same location as their source
             # file are automatically included.
             # 要编译的c/c++文件列表 文件路径想对于cmake文件路径
             # 若有多个源文件,就分行罗列在此
             ./jni_demo.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

# 从系统库中搜索指定的依赖并设置它在CMake编译过程中的别名
# 例如这里我们从系统查找 log 库,并把他的别名设置为 log-lib
find_library( # Sets the name of the path variable.
              # 设置库的在CMake编译过程中的别名
              log-lib
              # 该别名所对应的系统库名称
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.
# target_link_libraries
# 配置库的依赖关系
# 可以依赖多个库,比如可以依赖下面的几个
# 1. 自己在项目中定义的库
# 2. 依赖的第三方库
# 3. 系统库
target_link_libraries( # Specifies the target library.
                       # 要输出的目标库名称
                       jni_demo

                       # Links the target library to the log library
                       # included in the NDK.
                       # 依赖库名称列表,分行罗列在此
                       # 如果是项目中定义的库或放到jniLibs下的第三方库,直接写名称
                       # 如果是系统库,要用${上面定义的别名}的方式
                       ${log-lib} )

3.build.gradle配置

添加闭包,配置NDK编译类型、输出包类型、编译文件路径等:

android {
        externalNativeBuild {
            //配置CMake编译时的参数
            cmake {
                cppFlags ""
                /*输出包类型
                armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多,现在已经非常少见了。
                armeabiv-v7a: 第7代及以上的 ARM 处理器。2011年15月以后的生产的很多Android设备都使用它.
                arm64-v8a: 第8代、64位ARM处理器。
                x86: 平板、模拟器用得比较多。
                x86_64: 64位的平板。
                因为目前几乎已经很难找到armeabi对应的设备,
                所以从NDK-r17开始,已经移除了对"armeabi"的支持,不会构建相应的输出包。
                */
                abiFilters "armeabi-v7a", "arm64-v8a"
            }
        }
    }
    //配置该节点,将编译native包
    externalNativeBuild{
        //指明使用CMake方式来编译
        cmake{
            //CMake脚本文件的路径
            path file("src/main/jni/CMakeLists.txt")
        }
    }
}

4.编译打包和使用

4.1 输出native库:对该module进行build或者直接进行assemble打包等操作,就会生成相应的native库。

默认输出位置如图所以,也可以在build.gralde中进行配置,修改输出路径。

4.2 使用native库:将编译出的native包,按照平台类型放到要使用的module的src/main/jniLibs文件夹下

这是默认的放置路径,也可以通过build.gralde修改配置,放置到其它路径下。

4.3 使用时,调用方java类的包名地址不能变动,必须与c/cpp文件中定义的一致。

在调用native方法前,必须先执行

System.loadLibrary(“native库名称");

其它相关参考:

 官网文档:https://developer.android.com/studio/projects/add-native-code

 Android JNI 使用的数据结构JNINativeMethod详解 - 麦二蛋 - 博客园

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124517097