Android JNI基础及其简单使用
前言:内容主要包括jni的概念、安卓jni开发的两种方式以及工具的简单使用,读者将了解最基础的jni开发流程。
主要参考:齐行超老师的教程
目录
一、JNI、NDK、Cmake、ndk-build的概述
JNI(Java Native Interface)Java本地接口,起作用是使得Java与C/C++具有交互能力,而JNI相当于作为翻译官,使两种语言能够互相理解相互调用,JNI更像是一种协议的作用。
NDK(Native Development kit)本地开发工具包,允许使用原生语言(C/C++)来实现应用程序的部分功能
库文件类型分为:静态库.a 动态库.so 动态库.dll
为什么要使用JNI技术呢?因为当我们需要进行大量数据计算时,原生代码的计算速度远远快于Java:
在安卓中主要有两种方式创建JNI工程(Cmake、ndk-build):
二、Cmake工具的使用(Android默认)
我们先概括性了解一下编译流程吧,工具使用流程如下:
1. 在工程中默认创建的cpp目录下可以看到cmake.txt文件,我们还要在该目录下编写C/C++源文件(此处就暂时不写了,后面有时间再添上)该txt文件中主要就是原生库的相关配置(模块名、源文件位置、版本等)
2. 在build.gradle中进行相关配置(c++库相关配置)
externalNativeBuild {
cmake {
cppFlags ”-fexceptions -std=c++14"
arguments '-DANDROID_STL=c++_shared'
}
}
还可以配置库的生成位置:
# 配置库生成路径
# CMAKE_CURRENT_SOURCE_DIR是指 cmake库的源路径,通常是build/.../cmake/
# /../jniLibs/是指与CMakeList.txt所在目录的同级目录:jniLibs (如果没有会新建)
# ANDROID_ABI 生成库文件时,采用gradle配置的ABI策略(即:生成哪些平台对应的库文件)set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
- 1、默认/app/build/intermediates/cmake/..
- 2、安卓项目默认的JNI库目录:jinLibs
- 3、可选配置本地jni库的目录:
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
}
}
还可以配置生成指定ABI库文件:
externalNativeBuild {
cmake {
abiFilters "arm64-v8a”
}
}
三、ndk-build工具的使用
ndk-build工具又分为两种方式进行jni开发,其中一种类似于Cmake,要配合gradle使用,另一种直接使用命令行进行开发,完全脱离了AS
1. 首先在工程main目录下创建jni目录,用于存放配置文件(源文件、Android.mk、Application.mk)
Android.mk:
#此变量表示源文件在的位置
LOCAL_PATH := $(call my-dir)
#用于清除部分LOCAL_变量
include $(CLEAR_VARS)
#module名称,即库文件名称
LOCAL_MODULE := xxx
#源文件列表
LOCAL_SRC_FILES := xxxxxx.cpp
#收集LOCAL_XXX变量中配置的模块的相关信息
#指定了编译目标库类型(静态、动态)
include $(BUILD_SHARED_LIBRARY)
Application.mk:
#c++标准库支持
APP_STL := c++_static
#abi过滤
APP_ABI :=arm64-v8a,armeabi-v7a
#指定Android平台版本
APP_PLATFORM := android-21
2-1. 利用gradle编译工程,生成库文件:
externalNativeBuild {
ndkBuild {
path = "Android.mk路径"
abiFilters "arm64-v8a"
}
}
2-2. 利用命令行生成库文件:
进入jni目录,输入ndk-build就可以进行编译
指定库文件生成目录:
# 配置库生成路径
#输出目录 ../jniLibs/
#ABI $(TARGET_ARCH_ABI)
NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI)
把jniLib重定向:
1、安卓项目默认的JNI库目录:jinLibs
2、可选配置本地jni库的目录:
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
}
}
配置生成指定ABI库文件:
ndk {
abiFilters "arm64-v8a"
}
四、函数的静态注册和动态注册
静态注册:按照JNI规范书写函数名:Java_包名_类名_方法名
动态注册:JNI_OnLoad中指定Java Native函数与C实现函数的对应关系
动态注册流程:
// jni头文件
#include <jni.h>
#include <cassert>
#include <cstdlib>
#include <iostream>
using namespace std;
//native 方法实现
jint get_random_num(){
return rand();
}
/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明)
3.C/C++中对应函数的函数名(地址)
*/
static JNINativeMethod getMethods[] = {
{"getRandomNum","()I",(void*)get_random_num},
};
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if(clazz == NULL){
return JNI_FALSE;
}
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env){
//指定类的路径,通过FindClass 方法来找到对应的类
const char* className = "com/example/wenzhe/myjni/JniTest";
return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回调函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
//获取JNIEnv
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
assert(env != NULL);
//注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
if(!registerNatives(env)){
return -1;
}
//返回jni 的版本
return JNI_VERSION_1_6;
}
动态注册原理图:
静态注册流程图:
Java调用Native流程图: