最近一直比较忙,在做前端和后台相关的东西。主要研究方向是vue和node,所以博客很久没更新了。JNI是android里面比较难的一个环节,咱们先想一下,为什么jni比较难,有一句话叫做不明觉厉,你不知道他,不懂他,心里就觉得这个东西很高大上,很厉害。但是我想说的是,在android这一块,jni还没你想象的难。但是在开始jni的学习之前,有两点你必须要知道,其一:基本的android nkd开发配置你要懂,其二:基本的c语法你要懂。jni的本质就是android调用c或c++的代码来实现需求。主要使用的范围在音视频编解码等方面,因为我们都知道,c可以直接操作系统内存,像耗费性能比较大的编解码操作,c在使用的性能上远远高于java代码。那么开始撸码吧!!!!
那就从第一步开始,android的基本配置:
从AS2.0开始android对jni的支持越来越好,到AS3.0可以说对jni的支持好到爆炸!新创建的项目在勾选c支持的时候,基本上所有的jni支持都配置好了,你要做的就是在指定的jni开发文件夹下,编写你的c代码。时代在更新,大家在进步,我们本次的工程就以AS3.0开发环境。在开始ndk开发的前,下载lldb与cmake插件,这两个插件一个是c代码调试工具,一个是ndk编译配置工具,当然jni编译工具ndk你得有
ok!下载完这些,环境配置就是这么easy。配置完这个剩下的就是代码部分了,这里还要说一下cmake配置
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
cpp-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/cpp2.c ) //配置c文件入口
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log ) //引入ndk log模块
target_link_libraries( cpp-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} ) //生成动态库
app/gradle 文件配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.iwintrue.todoproject"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
}
}
ndk{
abiFilters "armeabi","armeabi-v7a","x86" //配置生成不同cpu架构的so文件
}
//ndk编译
ndk {
moduleName "cpp-lib" //生成so文件的名称
}
// sourceSets.main {
// jni.srcDirs = []
// }
// task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
// commandLine '/Users/zhoukai/Library/Android/sdk/ndk-bundle/ndk-build',//这里本地ndk的路径
// 'NDK_PROJECT_PATH=build/intermediates/ndk',
// 'NDK_LIBS_OUT=src/main/jniLibs',
// 'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
// 'NDK_APPLICATION_MK=src/main/jni/Application.mk'
// }
// tasks.withType(JavaCompile){
// compileTask->compileTask.dependsOn ndkBuild
// }
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
主要看红色部分,其中gradle中的注释部分是ndk使用.mk文件的配置,现在已经不用了,可以了解一下。
java 部分:
public class JNI {
{
System.loadLibrary("cpp-lib");
}
public native String getCString();
//利用c返回计算的和
public native int getSum(int a,int b);
//将java字符串与c字符串相加
public native String getCatString(String javaString);
//回调c的数组运算
public native int[] getCArray();
//返回c中的字符串数组
public native String[] getCStringArray(String _str);
//返回c中的char数组
public native char[] getCCharArray();
//检查密码是否正确
public native boolean checkPassWord(String pass);
//使用java方法
public native void useJavaMethod();
//返回结构体
public native JavaStruct callBackJavaObj();
}
声明jni方法很简单,只要在方法声明中添加native关键字就可以,我们定义几个简单的方法,只要你掌握了这些基本操作,那么你就算开启了jni的大门,万丈高楼平地起,从打地基开始吧
c代码:
#include
#include
#include
#include
#include
#define TAG "TODO" // 这个是自定义的LOG的标识
//引入android/log.h文件后,宏定义函数名,方便调用,之后可以在控制台打印出log日志
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
jint sum(jint a, jint b) {
return a + b;
}
/**
* 工具方法
* 作用: 把java中的string 转化成一个c语言中的char数组
* 接受的参数 envjni环境的指针
* jstr 代表的是要被转化的java的string 字符串
* 返回值 : 一个c语言中的char数组的首地址 (char 字符串)
*/
char *Jstring2CStr(JNIEnv *env, jstring jstr) {
char *rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
//jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
jmethodID mid =
(*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
// String.getByte("GB2312");
jbyteArray barr =
(jbyteArray) (*env)->CallObjectMethod(env, jstr, mid, strencode);
jsize alen = (*env)->GetArrayLength(env, barr);
jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
if (alen > 0) {
rtn = (char *) malloc(alen + 1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
return rtn;
}
/**
* 返回c语言字符串
* @param env
* @param obj
* @return
*/
JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCString(
JNIEnv *env,
jobject obj) {
char *d; //c语言没有字符串类型,使用char类型指针可以表示连续的字符地址,也就是字符串
char str[20] = "c返回的str";
d = str;
return (*env)->NewStringUTF(env, d);
}
/**
* 返回c语言字符串
* @param env
* @param obj
* @return
*/
JNIEXPORT jint Java_com_iwintrue_todoproject_ndk_JNI_getSum(
JNIEnv *env,
jobject obj, jint a, jint b) {
jint total = sum(a, b); //使用定义好的函数求和
return total;
}
JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCatString
(JNIEnv *env, jobject obj, jstring javaString) {
char *cstr = Jstring2CStr(env, javaString);
char *hellostr = "这是c的字符串";
strcat(cstr, hellostr); //拼接两个字符串 //使用string.h中的strcat拼接字符串
LOGI("%s", cstr);
return (*env)->NewStringUTF(env, cstr);
}
/**
* 返回c数组
* @param env
* @param instance
* @return
*/
JNIEXPORT jintArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCArray(JNIEnv *env, jobject instance) {
//创建数组
jintArray array = (*env)->NewIntArray(env, 10); //创建数组
//获取数组指针
jint *array_pi = (*env)->GetIntArrayElements(env, array, NULL); //获取数组指针,这里是获取的数组第一个索引值的指针
for (int i = 0; i < 10; i++) {
array_pi[i] = i;
}
//4.释放资源
(*env)->ReleaseIntArrayElements(env, array, array_pi, 0); //获取到数组之后,别忘了释放c开辟的内存空间
return array;
}
JNIEXPORT jobjectArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCStringArray(JNIEnv *env, jobject instance, jstring str_) {
jsize len = 10;
//获取java中的String类
jclass objClass = (*env)->FindClass(env, "java/lang/String"); //获取到java String类的字节码文件
//获取concat方法 //获取到String拼接字符串的方法,因为c中的strcat在for循环中有内存溢出问题,所以使用java方法来拼接字符串
jmethodID concat_methodID = (*env)->GetMethodID(env, objClass, "concat",
"(Ljava/lang/String;)Ljava/lang/String;");// "(Ljava/lang/String;)Ljav/lang/String;" 函数签名
//创建数组
jobjectArray array = (*env)->NewObjectArray(env, len, objClass, 0);
for (int i = 0; i < 10; i++) {
char index[25];
//将整数转化为string
sprintf(index, " %d", i);
jstring strObject = (*env)->NewStringUTF(env, "字符串");
jstring str = (*env)->NewStringUTF(env, index);
jobject str1 = (*env)->CallObjectMethod(env, strObject, concat_methodID, str);
const char *chars = (*env)->GetStringUTFChars(env, (jstring) str1, 0);
jstring jstring1 = (*env)->NewStringUTF(env, chars);
(*env)->SetObjectArrayElement(env, array, i, jstring1);
(*env)->ReleaseStringUTFChars(env, str1, chars);
}
return array;
}
JNIEXPORT jcharArray JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_getCCharArray(JNIEnv *env, jobject instance) {
jcharArray charArray = (*env)->NewCharArray(env, 10);
jchar *pi = (*env)->GetCharArrayElements(env, charArray, 0);
for (int i = 0; i < 10; i++) {
pi[i] = "e";
}
//数组释放
(*env)->ReleaseCharArrayElements(env, charArray, pi, 0);
return charArray;
}
JNIEXPORT jboolean JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_checkPassWord(JNIEnv *env, jobject instance,jstring pass) {
jstring originPass = (*env)->NewStringUTF(env,"12345");
char* s1 = (*env)->GetStringUTFChars(env,originPass,JNI_FALSE);
char* s2 = (*env)->GetStringUTFChars(env,pass,0);
LOGI("s1:%s",s1);
LOGI("s2:%s",s2);
int equ = strcmp(s1,s2);
(*env)->ReleaseStringChars(env,originPass,s1);
(*env)->ReleaseStringChars(env,pass,s2);
if(equ==0){
return 1;
} else{
return 0;
}
}
/**
* 使用java方法
* @param env
* @param instance
* javap -s class
*/
JNIEXPORT void JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_useJavaMethod(JNIEnv *env, jobject instance) {
//获取java Class
jclass jniUtils = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/JniUtils");
//获取要调用的方法Id
jmethodID methodId = (*env)->GetMethodID(env,jniUtils,"add","(II)I");
//获取jobject
jobject job = (*env)->AllocObject(env,jniUtils);
//调用该方法
jint sum = (*env)->CallIntMethod(env,job,methodId,10,20);
LOGI("%d",sum);
}
/**
* 刷新ui
* @param env
* @param instance
*/
JNIEXPORT void JNICALL
Java_com_iwintrue_todoproject_ndk_Main2Activity_refreshUi(JNIEnv *env, jobject instance) {
//获取class
jclass objClass = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/Main2Activity");
//执行环境 方法名 方法签名
jmethodID methodId = (*env)->GetMethodID(env,objClass,"toastText","()V");
(*env)->CallVoidMethod(env,instance,methodId);
}
/**
* 返回结构体
* @param env
* @param instance
* @return
*/
JNIEXPORT jobject JNICALL
Java_com_iwintrue_todoproject_ndk_JNI_callBackJavaObj(JNIEnv *env, jobject instance) {
//btn_callback_stuct
//获取class
jclass javaStruct = (*env)->FindClass(env,"com/iwintrue/todoproject/ndk/JavaStruct");
jobject javaStructObj = (*env)->AllocObject(env,javaStruct);
jfieldID age = (*env)->GetFieldID(env,javaStruct,"age","I");
jfieldID name = (*env)->GetFieldID(env,javaStruct,"name","Ljava/lang/String;");
jstring nameValue = (*env)->NewStringUTF(env,"张三");
(*env)->SetIntField(env,javaStructObj,age,19);
(*env)->SetObjectField(env,javaStructObj,name,nameValue);
return javaStructObj;
}
c代码中的注释已经很详细了,其中包括了java调用c代码以及c代码调用java代码。调用c代码中有分别有返回字符串,函数求和,返回基本类型数组,返回object数组,回调ui刷新。
注意:
- java不能直接返回c类型变量,要通过env这个结构体来转换
- 创建c数组的时候,别忘了内存释放
-
- 在调用ui相关的方法时,因为对象是根据反射获取到的,activity没有生命周期,所以要讲native方法定义到activity中
-
执行结果截图
控制台输出:
在jni的使用中,还有一种情景是存在的。那就是我们调用其他so文件中的方法,然后重新生成我们的so文件。这时候就要重新配置一下cmake文件来加载第三方的so文件,然后创建头文件引入。最后在新的.c文件中调用这个方法就可以了。看代码:
java代码:
public class JNI2 {
//
{
System.loadLibrary("cpp-lib"); //先引入你调用的so文件
System.loadLibrary("cpp2-lib"); //生成新的so文件
}
public native String callJNIBack();
}
c代码:
#include
#include
#include
#include
#include
#include "cpp2.h" //创建.h文件 这个文件可以自定义名称
#define TAG "TODO" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI2_callJNIBack(JNIEnv *env, jobject obj) {
jstring str = Java_com_iwintrue_todoproject_ndk_JNI_getCString(env,obj); //引用cpp-lib中的方法返回jstring
return str;
}
头文件:
#include
JNIEXPORT jstring Java_com_iwintrue_todoproject_ndk_JNI_getCString
(JNIEnv *, jclass);
修改cmake文件
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
cpp2-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/cpp3.c )
add_library(cpp-lib STATIC IMPORTED)
#添加动态库的路径
set_target_properties(cpp-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jni1/${ANDROID_ABI}/libcpp-lib.so)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( cpp2-lib
cpp-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
运行效果:
这个字符串是在cpp-lib中返回的。到此jni的运用你就基本掌握了,之后我运用我所说的内容搞一搞ffempg,敬请期待!
工程github地址