1.1-基本概念
Android SDK【Java层】
Android SDK全称是:Android Software Development Kit【Android 软件开发工具包】,Android SDK主要为开发者提供Java层的API调用以及开发过程中所需要的一些构建工具和其它工具;所以说你可以把Android SDK理解成就是Android应用的Java层开发【执行环境:虚拟机】
Android NDK【C/C++层】
Android NDK全称是:Android Native Development Kit【Android 本地开发工具包】,Android NDK主要为开发者提供C/C++层的API接口以及开发过程中所需要的一些构建工具和其它工具,NDK是SDK的一部分;所以说你可以把Android NDK理解成就是Android应用的C/C++底层库开发【执行环境:操作系统】
Android JNI【Java-桥梁-C/C++】
Android JNI全称:Android native interface【Android 本地接口】,Android JNI主要是为上层Java/Kotlin与本地C/C++ 提供互通互调机制,简单点说: Java代码中可以调用Native C/C++代码,Native C/C++代码可以调用上层Java代码;所以说Android JNI就是连接上层Java与本地C/C++互通互调的一个桥梁和机制!
JNI【最先出来】最早出现在JDK中,Android NDK【后来出来】这个开发工具集集成了JNI!
1.2-环境配置
-
安装Android Studio
https://developer.android.google.cn/studio 下载地址
https://developer.android.google.cn/studio/intro/ 帮助文档 -
创建NDK项目
-
配置NDK
手动配置: https://developer.android.google.cn/ndk/downloads
1.3-JNI基本语法
声明本地方法【Java层:Native方法】
//Native方法-》目的-》调用本地C/C++方法!
public native String stringFromJNI();
实现本地方法【C++层:C++方法】
/*
* extern "C" 表示:以C语言的方式导出【保持函数名不变,如果是C++,函数名会发生变化】
* JNIEXPORT 导出【暴露给外部使用】
* JNICALL 调用约定【stdcall,fastcall,ccall等一系列,调用约定会决定入栈顺序和释放的问题】
* */
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNI静态注册
Java层: Native声明-》对应一种固定格式-》C++层: C++方法【Java_包名_Java类名_方法名】
注意点: 如果包名中包含_,那么就会给下划线加上一个_1用来标识这是包名自带下划线,不是规则中的下划线!
JNI核心元素
JavaVM
JavaVM是虚拟机在JNI层的表示,一个进程只有JVM,所有线程共用JavaVM;
JNIEnv
JNIEnv它是一个与线程相关的用来代表java运行环境的,用来进行java-native互调的一个结构体,内部包含了一个JNI本地接口指针【JNINativeInterface* functions】,这个指针又指向了函数指针数组【定义了JNI相关的函数指针】;
同一个线程调用本地方法,JNIEnv是同一个,不同线程调用native方法,JNIEnv不相同【java可以在不同线程中调用】
重点说明JNIEnv在C和C++中使用的区别之处:
- 定义不同
#if defined(__cplusplus)
//C++对JNINativeInterface*进行了二次封装-》_JNIEnv 结构体
typedef _JNIEnv JNIEnv;
/*
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
*/
typedef _JavaVM JavaVM;
#else
//C语言直接使用的就是JNINativeInterface*
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
- 调用不同
JNIEnv *env
C++中:JNIEnv-》结构体【JNINativeInterface*】
JNIEnv *env === _JNIEnv * 【_JNIEnv一级指针】
C中:JNIEnv-》指针【JNINativeInterface* JNIEnv】
JNIEnv *env === JNINativeInterface**【JNINativeInterface二级指针】
jobject
Native方法中传递过来的jobject其实就是一个java实例引用,实例就是一个具体的对象,这个native方法是一个具体的java对象调用的!
jclass
Native方法中传递过来的jclass就是一个java类引用,这个native方法是一个静态类方法,jclass其实这个类引用!
JNI数据类型
基本类型
JNI基本类型,可以直接拿来使用,本质还是C/C++的基本数据类型,只不过使用typedef定义了而已!
引用类型
引用类型不能直接使用,必须通过JNIEnv中的JNI函数转换后,才能够使用!
类型描述
JNI基本操作
声明native函数:
public native int senderBaseTypeToJNI(
boolean boolValue,
byte byteValue,
char charValue,
short shortValue,
int intValue,
long longValue,
float floatValue,
double doubleValue
);
调用native函数:
boolean boolValue = false;
byte byteValue = 10;
char charValue = 'a';
short shortValue = 100;
int intValue = 200;
long longValue = 300;
float floatValue = 3.14f;
double doubleValue = 5.12;
Log.d("JavaLog", "onCreate: senderBaseTypeToJNI");
int iRet = senderBaseTypeToJNI(
boolValue,
byteValue,
charValue,
shortValue,
intValue,
longValue,
floatValue,
doubleValue
);
Log.d("JavaLog", "onCreate: iRet=" + iRet);
实现native函数:
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndk_1study_1base_MainActivity_senderBaseTypeToJNI(
JNIEnv *env, jobject thiz,
jboolean bool_value,
jbyte byte_value,
jchar char_value,
jshort short_value,
jint int_value,
jlong long_value,
jfloat float_value,
jdouble double_value) {
//Java基本类型:
// public native void senderBaseTypeToJNI(
// boolean boolValue,
// byte byteValue,
// char charValue,
// short shortValue,
// int intValue,
// long longValue,
// float floatValue,
// double doubleValue
// );
//JNI 基本类型:
// typedef uint8_t jboolean; /* unsigned 8 bits */
// typedef int8_t jbyte; /* signed 8 bits */
// typedef uint16_t jchar; /* unsigned 16 bits */
// typedef int16_t jshort; /* signed 16 bits */
// typedef int32_t jint; /* signed 32 bits */
// typedef int64_t jlong; /* signed 64 bits */
// typedef float jfloat; /* 32-bit IEEE 754 */
// typedef double jdouble; /* 64-bit IEEE 754 */
//一,每个Java基本类型都对应一个j前缀修饰的JNI基本类型
//例子:int -> jint
LOGD("boolValue=%d,"
"byteValue=%d,"
"charValue=%c,"
"intValue=%d,"
"longValue=%ld,"
"floatValue=%f,"
"doubleValue=%f\n",
bool_value,
byte_value,
char_value,
int_value,
long_value,
float_value,
double_value)
//二,JNI基本类型本质:C/C++基本类型
//结论:JNI基本类型,可以直接使用!
int iNum = int_value;
float fNum = float_value;
LOGD("\niNum=%d\n,fNum=%f",iNum,fNum);
return iNum;
}
JNI数组操作
jintArray
声明native函数:
public native void senderArrayToJNI(int[] ary);
public native int[] newArrayFromJNI();
调用native函数:
int[] ary = {1,2,3,4,5};
senderArrayToJNI(ary);
for (int num: ary) {
Log.d("JavaLog", "onCreate:print_array1 num=" + num);
}
int[] newAry = newArrayFromJNI();
for (int num: newAry) {
Log.d("JavaLog", "onCreate:print_array2 num=" + num);
}
实现native函数:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1base_MainActivity_senderArrayToJNI(
JNIEnv *env,
jobject thiz,
jintArray jary) {
//一,通过env获取数组长度【jsize->本质->jint->int】
jsize jaryCount = env->GetArrayLength(jary);
//二,获取数组指针,不拷贝
jint* pJary = env->GetIntArrayElements(jary,NULL);
//三,访问修改打印数组元素
for(int i=0;i<jaryCount;i++){
LOGD("before update the value : index=%d,value=%d",i,*(pJary + i));
*(pJary + i) = *(pJary + i) + 10000;
LOGD("after update the value : index=%d,value=%d",i,*(pJary + i));
}
//四,释放数组指针,并将C++数组的修改通过env同步至JVM java数组
//1.pJary必须为数组初始位置,如果修改了,需要回到数组头
//2.标记
//JNI_OK【同步C++修改至JVM,并释放C++层数组】
//JNI_COMMIT【同步C++修改至JVM,不释放C++层数组】
//JNI_ABORT【释放C++层数组】
env->ReleaseIntArrayElements(jary,pJary,JNI_OK);
}
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_ndk_1study_1base_MainActivity_newArrayFromJNI(
JNIEnv *env,
jobject thiz) {
//一,指定长度
jsize jaryCount = 5;
//二,创建数组
jintArray jary = env->NewIntArray(jaryCount);
//三,获取数组指针
jint* pJary = env->GetIntArrayElements(jary,NULL);
for(int i=0;i<jaryCount;i++){
*(pJary + i) = *(pJary + i) + 20000 + i;
}
//四,同步,并释放指针
//不同步,返回的jary,只有默认初始值,没有修改后的元素数据
env->ReleaseIntArrayElements(jary,pJary,JNI_OK);
return jary;
}
jobjectArray
声明native函数:
public native void senderStrArrayToJNI(String[] strs);
public native String[] newStrArrayToJNI();
调用native函数:
String[] strAry = {"wawa","dada","gaga"};
senderStrArrayToJNI(strAry);
for (String str: strAry) {
Log.d("JavaLog", "onCreate:print_array3 str=" + str);
}
String[] newStrAry = newStrArrayFromJNI();
for (String str: newStrAry) {
Log.d("JavaLog", "onCreate:print_array4 str=" + str);
}
实现native函数:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1base_MainActivity_senderStrArrayToJNI(
JNIEnv *env,
jobject thiz,
jobjectArray jStrs) {
//一,获取数组长度
jsize jStrsCount = env->GetArrayLength(jStrs);
//二,遍历数组元素
for (int i = 0; i < jStrsCount; i++) {
//获取对应index的字符串元素
jstring jstr = (jstring)env->GetObjectArrayElement(jStrs,i);
const char* strc = env->GetStringUTFChars(jstr,NULL);
LOGD("修改前: index=%d,strValue=%s",i,strc);
env->ReleaseStringUTFChars(jstr,strc);
jstring jstrValue = env->NewStringUTF("lalala");
env->SetObjectArrayElement(jStrs,i,jstrValue);
env->DeleteLocalRef(jstrValue);
jstring jstrUpdate = (jstring)env->GetObjectArrayElement(jStrs,i);
const char* strcUpdate = env->GetStringUTFChars(jstrUpdate,NULL);
LOGD("修改后: index=%d,strValue=%s",i,strcUpdate);
env->ReleaseStringUTFChars(jstrUpdate,strcUpdate);
}
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_example_ndk_1study_1base_MainActivity_newStrArrayFromJNI(
JNIEnv *env,
jobject thiz) {
//一,声明数组长度
jsize jAryCount = 5;
//二,查找String类
jclass jstrCls = env->FindClass("java/lang/String");
//三,创建字符串jstring,作为初始默认对象
jstring jstr = env->NewStringUTF("auto_value");
//四,创建一个object数组
jobjectArray jstrAry = env->NewObjectArray(jAryCount,jstrCls,jstr);
//释放jstr本地引用
env->DeleteLocalRef(jstr);
//释放jstrcls本地引用
env->DeleteLocalRef(jstrCls);
for (int i = 0; i < jAryCount; i++) {
//获取对应index的字符串元素
jstring jstr = (jstring)env->GetObjectArrayElement(jstrAry,i);
const char* strc = env->GetStringUTFChars(jstr,NULL);
LOGD("默认值: index=%d,strValue=%s",i,strc);
env->ReleaseStringUTFChars(jstr,strc);
jstring jstrValue = env->NewStringUTF("xixixi");
env->SetObjectArrayElement(jstrAry,i,jstrValue);
env->DeleteLocalRef(jstrValue);
jstring jstrUpdate = (jstring)env->GetObjectArrayElement(jstrAry,i);
const char* strcUpdate = env->GetStringUTFChars(jstrUpdate,NULL);
LOGD("修改值: index=%d,strValue=%s",i,strcUpdate);
env->ReleaseStringUTFChars(jstrUpdate,strcUpdate);
}
return jstrAry;
}
JNI对象操作
JNI操作对象
package com.example.ndk_study_base;
import android.util.Log;
public class Student {
private String name;
private int age;
private char sex;
public String getName() {
Log.d("Student", "getName:run ");
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
@Override
public String toString() {
return "com.example.ndk_study_base.Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
声明Native函数:
public native void senderStudentToJNI(Student stu,String name);
public native Student newStudentFromJNI();
调用Native函数:
Student stu = new Student();
stu.setName("娃哈哈");
stu.setAge(18);
stu.setSex('M');
senderStudentToJNI(stu,"奥利给给");
stu.getName();
Student stu2 = newStudentFromJNI();
Log.d("JavaLog", "RunJNIObjectCode: " + stu2.toString());
实现Native函数:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndk_1study_1base_MainActivity_senderStudentToJNI(
JNIEnv *env,
jobject thiz,
jobject stu,
jstring name) {
//一,修改Student name属性
//1.获取类引用
//方式1:获取jclass
jclass stuCls = env->GetObjectClass(stu);
//2.获取属性id
jfieldID stuNameFieldId = env->GetFieldID(stuCls,"name", "Ljava/lang/String;");
//3.获取属性
jstring jstrName = (jstring)env->GetObjectField(stu,stuNameFieldId);
//获取
const char* cstrName = env->GetStringUTFChars(jstrName,NULL);
//打印
LOGD("student name=%s",cstrName);
//释放
env->ReleaseStringUTFChars(jstrName,cstrName);
//4.修改属性
env->SetObjectField(stu,stuNameFieldId,name);
//二,调用Student func方法
jmethodID getNameMethodId = env->GetMethodID(stuCls,"getName", "()Ljava/lang/String;");
jstring jstrRet = (jstring)env->CallObjectMethod(stu,getNameMethodId);//调用返回类型是object的方法API
//获取
const char* cstrNameRet = env->GetStringUTFChars(jstrRet,NULL);
//打印
LOGD("student cstrNameRet=%s",cstrNameRet);
//释放
env->ReleaseStringUTFChars(jstrRet,cstrNameRet);
env->DeleteLocalRef(stuCls);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_ndk_1study_1base_MainActivity_newStudentFromJNI(
JNIEnv *env,
jobject thiz) {
//方式2:获取jclass
jclass stuCls = env->FindClass("com/example/ndk_study_base/Student");
//开辟对象控件
jobject jstuObj = env->AllocObject(stuCls);
//获取方法id
jmethodID setNameMethodId = env->GetMethodID(stuCls,"setName", "(Ljava/lang/String;)V");
jmethodID setAgeMethodId = env->GetMethodID(stuCls,"setAge", "(I)V");
jmethodID setSexMethodId = env->GetMethodID(stuCls,"setSex", "(C)V");
//参数-调用-释放
jstring jstrName = env->NewStringUTF("JNIStudentLala");
env->CallVoidMethod(jstuObj,setNameMethodId,jstrName);//调用返回值void的API
env->DeleteLocalRef(jstrName);
env->CallVoidMethod(jstuObj,setAgeMethodId,99);
env->CallVoidMethod(jstuObj,setSexMethodId,'M');
env->DeleteLocalRef(stuCls);
return jstuObj;
}
JNI字符串
JNI引用问题
- 局部引用
Native方法作用域中创建的引用对象,返回的引用被称之为局部引用,只能在本Native方法以及调用Native方法的范围内使用,执行完毕离开作用域会被自动释放,没有被释放前,是不能被垃圾回收的,不能保存局部引用至全局【内存异常或系统崩溃】!
**局部引用手动释放:**DeleteLocalRef - 全局引用
NewGlobalRef唯一创建全局引用API【局部引用的区别】
DeleteGlobalRef 释放全局引用
都是需要手动创建和释放!
JNI动态注册
Java层: native方法声明
public native void DynamicFromJNI();
public native int DynamicIntFromJNI(int nums);
public native String DynamicStrFromJNI();
C++层: JNI方法实现
extern "C" JNIEXPORT void DynamicFuncCall(){
LOGD("DynamicFuncCall successes!");
}
extern "C" JNIEXPORT int DynamicFuncIntCall(jint nums){
LOGD("DynamicFuncIntCall successes!");
return 200;
}
extern "C" JNIEXPORT jstring DynamicFuncStrCall(
JNIEnv* env,
jobject obj){
LOGD("DynamicFuncStrCall successes!");
jstring jStr = env->NewStringUTF("哈哈");
return jStr;
}
桥梁打通Native-》JNI方法:
加载库:Java
static {
System.loadLibrary(“ndk_study_dynamic”);
}
自动调:JNI
extern “C” JNIEXPORT jint JNI_OnLoad(JavaVM* javaVm,void* param)
....
// 日志输出
#include <android/log.h>
#define TAG "NDKLog"
// __VA_ARGS__ 代表...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);
JavaVM* jvm;
extern "C" JNIEXPORT void DynamicFuncCall(){
LOGD("DynamicFuncCall successes!");
}
extern "C" JNIEXPORT int DynamicFuncIntCall(jint nums){
LOGD("DynamicFuncIntCall successes!");
return 200;
}
extern "C" JNIEXPORT jstring DynamicFuncStrCall(
JNIEnv* env,
jobject obj){
LOGD("DynamicFuncStrCall successes!");
jstring jStr = env->NewStringUTF("哈哈");
return jStr;
}
// typedef struct {
// const char* name;
// const char* signature;
// void* fnPtr;
// } JNINativeMethod;
//(void*)DynamicFuncCall void不是返回值类型,就是一个函数强转void*指针类型
//你写:int*,char*,void*都是可以赋值给void,但是直接将函数赋值给void*是不可以的,需要指针类型强制转换!
//void* pVoid = (void*)DynamicFuncIntCall;
//将Java Native方法和JNI方法进行绑定:
static const JNINativeMethod jniNativeMethods[] = {
{"DynamicFromJNI","()V",(void*)DynamicFuncCall},
{"DynamicIntFromJNI","(I)I",(void*)DynamicFuncIntCall},
{"DynamicStrFromJNI","()Ljava/lang/String;",(void*)DynamicFuncStrCall}
};
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* javaVm,void* param){
//保存:jvm指针
::jvm = javaVm;
//获取JNIEnv
JNIEnv* env = nullptr;
jint jRet = javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if(jRet != JNI_OK){
return -1;
}
LOGD("JNI_OnLoad init successes!");
//注册绑定
// jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
// jint nMethods)
jclass mainActiveCls = env->FindClass("com/example/ndk_study_dynamic/MainActivity");
env->RegisterNatives(mainActiveCls,jniNativeMethods,sizeof(jniNativeMethods)/sizeof(JNINativeMethod));
//返回版本
return JNI_VERSION_1_6;
}
总结:
- 静态绑定优缺点
- 方便使用,快速生成
- 名称过长,暴露信息
- 效率低于动态绑定一丢对
- 动态绑定优缺点
- JNI方法名可以任意起,只要进行绑定注册即可
- 一开始进行所有JNI方法的注册
1.4-Ndk导库流程【第三方库】
CPU架构:
指令派系:
- 精简指令集【RISC】
Intel,AMD - 复杂指令集【CISC】
IBM,ARM
常见架构:
x86,x86-64/x64
arm64-v8a,armeabi-v7a【32位】,armeabi【32位】
API-ABI:
- API
API【Application Program Interface 应用程序接口】其实就是一套预先定义的,无需了解内部的实现机理和细节,与硬件系统无关的一套调用接口! - ABI
ABI【Application Binary Interface 应用程序二进制接口】定义了一套二进制规则,它允许在所有兼容该ABI编译的目标代码的操作系统和硬件系统中,无须改动即可运行,从这里我们就看出,ABI其实就是一套约束底层,系统,硬件,编译规则的一套二进制接口!
【EABI : 是 arm 对于 ABI规范的较新(2005年)的实现】
配置支持ABI
大厂适配ABI文章:https://mp.weixin.qq.com/s/jnZpgaRFQT5ULk9tHWMAGg
module模块: build.gradle
defaultConfig {
applicationId "com.example.ndkdemo"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ''
// 方法1:externalNativeBuild-》cmake:
// abiFilters 'armeabi-v7a'
}
}
// 方法2:defaultConfig-》ndk:
ndk{
abiFilters 'armeabi-v7a','arm64-v8a','x86'
}
}
配置输出目录
默认输出目录:
#不同Android Studio版本有的生效有的不生效
#方式1:
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI})
#方式2:
#set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI})
引用外部库流程:
cmake_minimum_required(VERSION 3.10.2) #cmake 最低版本号
# Declares and names the project.
project("ndk_study_fmod") #项目工程名
#以音频音效库fmod作为Demo介绍导库流程:
#一,导入头文件所在目录
#解释:include_directories 默认路径:CMakeLists所在目录,inc与它同级所以相对目录可以直接写
include_directories(inc) #inc我们放在了CMakeLists同级目录下
#二,导入库文件所在目录【将库文件所在目录配置到环境变量中,找库会自动去环境变量中配置的目录中寻找】
#解释:set 赋值【环境变量赋值】
#先获取环境变量中已有的目录,拼接上我们的目录,重新赋值给环境变量
#fmod库文件:放在了与cpp同级的jniLibs目录下:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
#定义一个变量allCpp 代表所有的.c,.h,.cpp
file(GLOB allCpp *.c *.h *.cpp)
add_library( # Sets the name of the library.
ndk_study_fmod #库名称 libndk_study_fmod.so
# Sets the library as a shared library.
SHARED #库类型 -> .a 还是 .so
# Provides a relative path to your source file(s).
#native-lib.cpp #源文件 通用写法:
${allCpp}
)
#查找库 并起一个别名 目的:查找一次,缓存下来,避免反复查找
find_library( # Sets the name of the path variable.
log-lib
log)
#链接库:环境变量中配置的目录中,按照libxxx.so libxxx.a去找对应库,链接到一起
target_link_libraries( # Specifies the target library.
ndk_study_fmod
xxx库 #只需要写库名称,不需要写lib,.so前后缀,它会自动添加匹配对应的库
# Links the target library to the log library
# included in the NDK.
${log-lib})
#总结:导入库流程
#一,项目工程添加头文件,添加库文件
#【.jar放在libs里面,动态库放在cpp同级目录,创建一个jniLibs目录,这个目录是gradle默认寻找的目录】
#二,设置头文件所在目录
#三,设置库文件所在目录-》环境变量-》追加方式不是覆盖
#四,链接库-》只写库名称省略前缀lib和后缀库类型.so