JNI简介

JNI(Java Native Interface),是方便Java调用C/C++等Native代码封装的一层接口。

NDK简介

NDK(Native Development Kit),是Android提供的一套工具集合,通过NDK可以在Android中更加方便的通过JNI开访问本地代码。NDK提供了交叉编译,开发人员只需要简单的修改mk文件就可以生成特定CPU平台的动态库。使用NDK有如下好处:

1、提高代码安全性。由于apk的java层代码很容易被反编译,而so库反编译比较困难

2、可以很方便的使用目前已有的C/C++开源库

3、便于平台间的移植。通过C/C++实现的动态库可以很方便的在其他平台上使用

4、提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能

NDK使用步骤

  • 第1步:配置Android NDK环境
  • 第2步:创建Android项目,并与NDK进行关联
  • 第3步:在Android项目中声明所需要调用的Native方法
  • 第4步:使用Android需要交互的本地代码实现在Android中声明的Native方法
  • 第5步:通过ndk-build命令编译产生.so库文件
  • 第6步:编译Android Studio工程,从而实现Android调动本地代码

1、配置NDK环境

2、关联Android Studio 项目与NDK

在local.properties文件中添加配置

ndk.dir=D\:\\Android\\sdk\\ndk-bundle

3、在Android项目中声明Native方法

public class JNIUtils {

    //加载生成的so库文件
    //注意要跟.so库文件名相同
    static {
        System.loadLibrary("native-lib");
    }

    //java调C中的方法都需要用native声明且方法名必须和c的方法名一样
    public native static String stringFromJNI();
}

4、实现在Android中声明的Native方法

重新Make Project一下工程,完成后会在工程目录app/build/intermediates/classes/debug/com/hsdi/jnidemo下看到编译后的classes文件JNIUtils.class文件

a、使用javah工具生成头文件

我们的NDK模块源代码由C/C++的头/源文件和make文件组成,这些文件必须放在jni目录下

理论上,jni目录可以放在任何地方,例如我们放在xx/jni/下,在xx/jni/下执行ndk-build之后会在xx/下(即jni同级目录下)产生编译结果(即libs和obj文件)在Android Studio项目里,我们一般把jni目录放在项目根目录下(即src同级目录),这样我们编译出来的libs和obj将会在项目根目录下。

打开终端,输入以下命令:

javah -d ./app/jni -classpath ./app/build/intermediates/classes/debug com.hsdi.jnidemo.JNIUtils

b、创建本地代码文件

在jni目录下创建com_hsdi_jnidemo_JNIUtils.cpp文件,内容如下:

#include "com_hsdi_jnidemo_JNIUtils.h"

JNIEXPORT jstring JNICALL Java_com_hsdi_jnidemo_JNIUtils_stringFromJNI(JNIEnv *env, jobject){

        // 1. JNIEnv:代表了VM里面的环境,本地的代码可以通过该参数与Java代码进行操作
        // 2. jobject:定义JNI方法的类的一个本地引用(this)
        return env->NewStringUTF("Hello from C++");
}

注:

  • JNIEXPORT jstring JNICALL中的 JNIEXPORTJNICALL不能省略,其中jstring表示该方法返回字符串
  • 方法名的格式:Java_包名_类名_Java需要调用的方法名,对于包名,包名中的 . 要改成 _ _ 要改成 _1

c、在Module的build.gradle添加ndk节点

android{
    ...
    defaultConfig{
        ...
        ndk{
            //模块名称,即编译的.so文件名
            moduleName "native-lib"
           
            //"log"表示加入Android的调试日志,只要再导入#include <android/log.h>
            //就可以使用__android_log_print方法打印日志到logcat中
            ldLibs "log"
        }
        ...
    }
    ...
}

 

5、创建Android.mk文件和Application.mk文件

Android.mk文件是一个负责向NDK构建系统描述NDK项目的GNU Makefile片段,是每一个NDK项目的必备组件,根据GNU Make的命名规则,变量名要大写

# Android.mk必须以LOCAL_PATH开头,注释#除外
# 设置工作目录,而my-dir则会返回Android.mk文件所在的目录
LOCAL_PATH := $(call my-dir)

# 借助CLEAR_VARS变量清除除LOCAL_PATH外的所有LOCAL_<name>变量
include $(CLEAR_VARS)

# 设置模块的名称,即编译出来.so文件名
# 注,要和上述步骤中build.gradle中NDK节点设置的名字相同
LOCAL_MODULE := native-lib

# 指定参与模块编译的C/C++源文件列表,多文件用"\"隔开
LOCAL_SRC_FILES := com_hsdi_jnidemo_JNIUtils.cpp


# 必须在文件结尾定义编译类型,指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
# BUILD_SHARED_LIBRARY 共享库,供java或者其他共享库调用
# BUILD_STATIC_LIBRARY 静态库,供共享库调用,不能直接被java调用
include $(BUILD_SHARED_LIBRARY)

Application.mk文件配置编译平台相关的内容

# 最常用的APP_ABI字段:指定需要基于哪些CPU平台的.so文件
# 常见的平台有armeabi x86 mips,其中移动设备主要是armeabi平台
# 默认情况下,Android平台会生成所有平台的.so文件,即同APP_ABI := armeabi x86 mips
# 指定CPU平台类型后,就只会生成该平台的.so文件,即上述语句只会生成armeabi平台的.so文件
# APP_ABI := armeabi armeabi-v7a mips x86
APP_ABI := all
APP_PLATFORM := android-23

5、编译上述文件,生成.so库文件

经过上述步骤,在app/jni文件夹中已经有3个文件(即test.cpp、Android.mk、Application.mk

打开终端,输入以下命令

$ cd ./app/jni
$ ndk-build

可能会编译失败,提示如下:

D:/Android/sdk/ndk-bundle/build//../build/core/add-application.mk:178: *** Android NDK: APP_STL gnustl_static is no longer supported. Please switch to either c++_static or c++_shared. See https://developer.android.com/ndk/guides/cpp-support.html for more information.    .  Stop.
出现的原因可能是由于ndk版本过高导致的,使用低版本的ndk再编译一次:

$ cd ./app/jni
$ D:\Android\android-ndk-r14b-windows-x86_64\android-ndk-r14b\ndk-build

编译成功后,会在jni的同级目录中生成libsobj两个文件夹,其中libs下存放的是.so库文件

6、在Android Studio项目中使用NDK实现JNI功能

此时,我们已经将本地代码文件编译成.so库文件,有两种方法可以供Java代码调用本地代码

方法一:把这些编译好的.so库打包到apk里面供java调用,我们还必须在Module的build.gradle里面添加下面的代码:

android {
    ...
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
    ...
}

方法二:在src/main/中创建一个名为jniLibs的文件夹,将上述生成的so文件夹放到该目录下,

在Java代码中调用本地代码中的方法,具体代码如下:

 

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.tv);
        tv.setText(JNIUtils.stringFromJNI());
    }
}

效果如下:

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐