JNI 与 NDK

JNI 与 NDK

JNI

定义:Java Native Interface,即 Java本地接口

作用: 使得Java 与 本地其他类型语言(如C、C++)交互,Java代码与C,C++语言代码相互调用

注意:

  1. JNIJava 调用 Native 语言的一种特性
  2. JNI 是属于 Java 的,与 Android 无直接关系

背景:Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱

解决方案: 采用 JNI特性 增强 Java 与 本地代码交互的能力

实现步骤

1、在Java中声明Native方法(即需要调用的本地方法)
2、编译上述Java源文件javac(得到.class文件)
3、通过javah命令导出JNI的头文件(.h文件)
4、使用Java需要交互的本地代码,实现在Java中声明的Native方法
如Java需要与C++交互,那么就用C++实现 Java的Native方法
5、编译.so库文件
6、通过Java命令执行 Java程序,最终实现Java调用本地代码

NDK

定义:Native Development Kit,是 Android的一个工具开发包。NDK是属于 Android 的,与Java并无直接关系

作用:快速开发CC++的动态库,并自动将so和应用一起打包成 APK
即可通过 NDKAndroid中 使用 JNI与本地代码(如C、C++)交互

应用场景:在Android的场景下 使用JNI

特点

1.运行效率高
在开发要求高性能的需求中,采用C/C++更加有效率
如果使用本地代码(C/C++)执行算法,能大大提高算法的执行效率
2.代码安全性高
java是半解释型语言,容易被反汇编后得到源码,而本地有些代码语言则不会,如C/C++
3.功能扩展性好
可方便地使用其他开发语言的开源库
4.易于代码复用和移植
用本地代码开发的代码不仅可在Android中使用,还可嵌入到其他类型平台上使用

注意

​ 提供了把.so和.apk打包的工具,而JNI开发则没有,只要把.so文件放到文件系统的特定位置
NDK提供的库有限,仅用于处理算法效率和敏感的问题
提供了交叉编译器,用于生成特定的cpu平台动态库

使用步骤

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

具体使用——AS 2.2 以下

1、配置 Android NDK环境

下载Android NDK工具包,如android-ndk-r14b-darwin-x86_64.zip

​ 官网下载:https://developer.android.com/ndk/downloads/index.html

解压 NDK包

​ 解压路径不要出现空格和中文

​ 建议将解压路径设置为Android Studio的SDK根目录中(与build-tools同级),并命名为ndk-bundle。此举的好处:启动Android Studio时会自动检查NDK,并直接将配置添加到ndk.dir中,使用时不用配置与NDK相关连的配置

安装 & 配置NDK

​ NDK环境变量配置类似JDK

https://blog.csdn.net/yuanzhihua126/article/details/78732371

2、关联AS项目与NDK

在Gradle的local.properties中添加配置
ndk.dir=ndk-bundle路径,如:ndk.dir=E:/AS/SDK/ndk-bundle
如NDK目录放在SDK目录中,并命名为ndk-bundle,则此配置会自动添加
在Gradle的gradle.properties中添加配置
android.useDeprecatedNdk=true,用于对旧版本的支持
在Gradle的build.gradle的android的defaultConfig节点中添加ndk节点

ndk{
    //.so文件名
    moduleName "hello_jni"	
    //使用STL标准库,默认无法使用
    stl "stlport_static"	
    //log表示加入Android的调试日志,只要再导入#include <android/log.h>就可使用_android_log_print方法打印日志到logcat中
    ldLibs "log"	
}

3、创建本地代码文件

将创建好的test.cpp文件放到工程目录中的src/main/jni文件夹中,没有则手动创建

JNI与java数据类型对应表见本文件夹

//如果本地代码是C++(.cpp或者.cc),要使用extern "C" {}把本地方法包裹
extern "C"{
    //JNIEXPORT 和 JNICALL不能省
    JNIEXPORT jstring JNICALL 	 Java_scut_carson_1ho_ndk_1demo_MainActivity_getFromJNI(JNIEnv *env, jobject obj ){
    // 参数说明
    // 1. JNIEnv:代表了VM里面的环境,本地的代码可以通过该参数与Java代码进行操作
    // 2. obj:定义JNI方法的类的一个本地引用(this)
    //方法名定义说明:
    //格式:Java_包名_类名_Java需要调用的方法名,如此处包名为:										scut.carson_ho.ndk_demo
    //Java首字母必须大写
    //对于包名,符号.改成_;_改成_1
    return env -> NewStringUTF("Hello, I am JNI.");
    // 上述代码是返回一个String类型的字符串
    }
}

4、创建Android.mk文件

将创建好的Android.mk文件放在src/main/jn文件夹下,没有则手动创建

LOCAL_PATH:=$(call my-dir)
// 设置工作目录,而my-dir则会返回Android.mk文件所在的目录
include $(CLEAR_VARS)
// 清除几乎所有以LOCAL—PATH开头的变量(不包括LOCAL_PATH)
LOCAL_MODULE:=hello_jni
// 设置模块的名称,即编译出来.so文件名
// 注,要和上述步骤中build.gradle中NDK节点设置的名字相同
LOCAL_SRC_FILES:=test.cpp
// 指定参与模块编译的C/C++源文件名
include $(BUILD_SHARED_LIBRARY)
// 指定生成的静态库或者共享库在运行时依赖的共享库模块列表。

5、创建Application.mk文件

将创建好的Application.mk文件放在src/main/jn文件夹下,没有则手动创建

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

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

编译成功后,在src/main/会多了两个文件夹libs & obj,其中libs下存放的是.so库文件

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

.so文件必须放在对应的cpu平台的文件夹下

cd .../src/main/jni	//进入到上述三个文件的目录下
ndk-build	//运行NDK编译命令

7、在AS项目中使用NDK实现的JNI功能

public class MainActivity extends Activity  {
    //加载生成的.so库文件,注意.so文件名
    static {
        System.loadLibrary("hello_jni");
    }
    //定义在JNI中实现的方法,固定写法,必须加native关键字
    public native String getFromJNI();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		 //触发JNI方法
		getFromJNI();
...

具体使用——AS 2.2以上

AS 2.2 以上内部已经集成了NDK,因此只需在AS内部进行配置

1、创建含有NDK功能的工程

2、工程中根目录中创建cpp文件夹,放入.cpp文件

3、在AS项目中使用NDK实现的JNI功能

​ 与上述的第七步一样,直接调用

JNI与NDK关系

​ JNI是实现的目的,NDK是Android中实现JNI的手段,即在Android开发中,通过NDK实现JNI的功能

应用实例:APP监听自身被卸载

方案

  1. 监听系统卸载广播:只能监听到其他应用的卸载广播,无法监听到自己是否被卸载。
  2. 读取系统 log:第三方软件卸载无法得知。
  3. 静默安装另一个程序,监听自己是否被卸载:需要 root 权限。
  4. Java 线程轮询,监听/data/data/{package-name}目录是否存在:卸载 app,进程退出,线程也被销毁。
  5. C 进程轮询,监听/data/data/{package-name}目录是否存在:目前业界普遍采用的方案

原理

单纯的 Java 层代码是无法监听自身卸载的,使用 C 语言在底层实现

借助 Linux 进程 fork 出来的 C 进程在应用被卸载后不会被销毁,监听/data/data/{package-name}目录是否存在,如果不存在,就证明应用被卸载了

实现

  1. fork()子进程
  2. 创建监听文件
  3. 初始化 inotify 实例
  4. 注册监听事件
  5. 调用 read 函数开始监听
  6. 卸载反馈统计

测试场景

  1. 正常卸载
  2. 断网卸载
  3. 清除数据(5.0 以上不支持)
  4. kill 进程(5.0 以上不支持)
  5. 插拔 USB 线
  6. 覆盖安装
  7. 内部存储移到 SD 卡
  8. 开机监听(官方不推荐)
  9. 打开浏览器(5.0 以上部分机型无法开启)

JNI与java数据类型对应表

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/mLuoya/article/details/87902381