一,Ndk开发【基础】
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 -> jintLOGD("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;}@Overridepublic 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:获取jclassjclass stuCls = env->GetObjectClass(stu);//2.获取属性idjfieldID 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:获取jclassjclass stuCls = env->FindClass("com/example/ndk_study_base/Student");//开辟对象控件jobject jstuObj = env->AllocObject(stuCls);//获取方法idjmethodID 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的APIenv->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
#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;//获取JNIEnvJNIEnv* 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 21targetSdk 31versionCode 1versionName "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-liblog)#链接库:环境变量中配置的目录中,按照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
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
