NDK开发——JNI必须掌握的常用例子
2017-09-01 12:45
211 查看
前言
要学习NDK之前,我们得先在AndroidStudio中学习JNI,还有C和C++基础,这些都是接触NDK的前提,那么废话不多说,开始吧对于NDK在AndroidStudio的配置,可以关注我的博客找到相关文章,本文章是基于配置好NDK环境之后来操作的
效果预览
github:https://github.com/AndroidHensen/JNIDemo
导入依赖库
我们先导入我们需要用到的依赖库#include <stdio.h> #include <stdlib.h> #include <jni.h> #include <string.h> #include <android/log.h>
宏定义
定义两个宏函数,用于Log输出调试#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"TAG",FORMAT,__VA_ARGS__) #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"TAG",FORMAT,__VA_ARGS__)
这里使用到AndroidLog的输出,需要手动在mk中导入,或者在Gradle的ndk配置中
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := jni_study LOCAL_SRC_FILES := jni_study.c LOCAL_LDLIBS := -llog //Log打印 include $(BUILD_SHARED_LIBRARY)
ndk{ moduleName "jni_study" ldLibs "log" //Log打印 abiFilters "armeabi", "armeabi-v7a", "x86" }
访问Java属性
JNIEXPORT jstring JNICALL Java_com_handsome_ndkdemo_MainActivity_accessField (JNIEnv *env, jobject jobj){ //获取对象的class jclass cls = (*env)->GetObjectClass(env, jobj); //获取属性id,参数二:class对象,参数三:属性名,参数四:属性签名 jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;"); //获取属性值 jstring jstr = (*env)->GetObjectField(env, jobj, fid); //isCopy代表:函数内部是否已经复制,复制了返回JNI_TRUE,没复制返回JNI_FALSE jboolean isCopy = NULL; //转换成c语言识别的字符串 char *c_str = (*env)->GetStringUTFChars(env,jstr,&isCopy); //拼接字符串 char text[20] = "myName is"; strcat(text,c_str); //生成Java识别的字符串 jstring new_jstr = (*env)->NewStringUTF(env,text); //修改属性的值 (*env)->SetObjectField(env,jobj,fid,new_jstr); //只要使用了GetStringUTFChars,官方建议一定要释放 (*env)->ReleaseStringUTFChars(env, jstr, c_str); return new_jstr; }
访问java静态属性
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_accessStaticField (JNIEnv *env, jobject jobj){ //获取对象的class jclass cls = (*env)->GetObjectClass(env, jobj); //获取属性id,参数二:class对象,参数三:属性名,参数四:属性签名 jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I"); //获取属性值 jint count = (*env)->GetStaticIntField(env, cls, fid); //++操作 count++; //修改属性的值 (*env)->SetStaticIntField(env,cls,fid,count); }
访问java方法
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_accessMethod (JNIEnv *env, jobject jobj){ //jclass jclass cls = (*env)->GetObjectClass(env, jobj); //获取方法id,参数二:class对象,参数三:方法名,参数四:方法签名 jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I"); //调用方法 jint random = (*env)->CallIntMethod(env, jobj, mid, 200); }
访问java静态方法
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_accessStaticMethod (JNIEnv *env, jobject jobj){ //jclass jclass cls = (*env)->GetObjectClass(env, jobj); //获取方法id,参数二:class对象,参数三:方法名,参数四:方法签名 jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;"); //调用方法 jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid); }
访问构造方法
使用java.util.Date产生一个当前的时间戳JNIEXPORT jobject JNICALL Java_com_handsome_ndkdemo_MainActivity_accessConstructor (JNIEnv *env, jobject jobj){ //jclass jclass cls = (*env)->FindClass(env, "java/util/Date"); //获取方法id,参数二:class对象,参数三:方法名,参数四:方法签名 jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V"); //创建对象 jobject date_obj = (*env)->NewObject(env , cls, constructor_mid); //获取方法id,参数二:class对象,参数三:方法名,参数四:方法签名 jmethodID mid = (*env)->GetMethodID(env , cls, "getTime", "()J"); //调用方法 jlong time = (*env)->CallLongMethod(env, date_obj, mid); printf("time:%lld\n",time); return date_obj; }
调用父类的方法
该方法出错,还未找到解决方法JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_accessNonvirtualMethod (JNIEnv *env, jobject jobj){ //jclass jclass cls = (*env)->GetObjectClass(env, jobj); //获取human对象id jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/handsome/ndkdemo/Bean/Human;"); //获取human这个对象 jobject human_obj = (*env)->GetObjectField(env, jobj, fid); //获取父类Human的jclass jclass human_cls = (*env)->FindClass(env, "com/handsome/ndkdemo/Bean/Human"); //获取父类Human的sayHi的方法 jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V"); //调用的父类的方法 (*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid); }
字符串转码
字符串转码,c字符串直接给jstring时,由于编码不同会出现中文乱码问题,解决方法是采用Java的String(byte bytes[], String charsetName)构造方法JNIEXPORT jstring JNICALL Java_com_handsome_ndkdemo_MainActivity_chineseChars (JNIEnv *env, jobject jobj){ char *c_str = "马蓉与宋江"; //第一步:jmethodID jclass str_cls = (*env)->FindClass(env, "java/lang/String"); jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V"); //第二步:创建byte数组 jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str)); //byte数组赋值 (*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str); //第三步:字符编码jstring jstring charsetName = (*env)->NewStringUTF(env, "UTF-8"); //调用构造函数,返回编码之后的jstring return (*env)->NewObject(env,str_cls,constructor_mid,bytes,charsetName); }
给定一个数组进行排序并同步到Java中
int compare(int *a,int *b){ return (*a) - (*b); } JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_giveArray (JNIEnv *env, jobject jobj,jintArray arr){ //jintArray -> jint指针 -> c int 数组 jint *elems = (*env)->GetIntArrayElements(env, arr, NULL); //数组的长度 int len = (*env)->GetArrayLength(env, arr); //排序,参数三:排序函数 qsort(elems, len, sizeof(jint), compare); //同步,以下对第四个参数的解释 //0, Java数组进行更新,并且释放C/C++数组 //JNI_ABORT, Java数组不进行更新,但是释放C/C++数组 //JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放) (*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT); }
创建一个指定大小的数组
JNIEXPORT jintArray JNICALL Java_com_handsome_ndkdemo_MainActivity_getArray (JNIEnv *env, jobject jobj, jint len){ jintArray jint_arr = (*env)->NewIntArray(env, len); jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL); int i = 0; for (; i < len; i++){ elems[i] = i; } //同步 (*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0); return jint_arr; }
局部变量引用
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_localRef (JNIEnv *env, jobject jobj){ int i = 0; for (; i < 5; i++){ //创建Date对象 jclass cls = (*env)->FindClass(env, "java/util/Date"); jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V"); jobject obj = (*env)->NewObject(env, cls, constructor_mid); //此处省略一百行代码... //通知垃圾回收器回收这些对象 (*env)->DeleteLocalRef(env, obj); } }
创建全局引用
//全局引用,可以共享(跨多个线程),手动控制内存使用 jstring global_str; JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_createGlobalRef (JNIEnv *env, jobject jobj){ jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!"); global_str = (*env)->NewGlobalRef(env, obj); }
获得全局引用
JNIEXPORT jstring JNICALL Java_com_handsome_ndkdemo_MainActivity_getGlobalRef (JNIEnv *env, jobject jobj){ return global_str; }
释放全局引用
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_deleteGlobalRef (JNIEnv *env, jobject jobj){ (*env)->DeleteGlobalRef(env, global_str); }
弱全局引用
节省内存,在内存不足时可以是释放所引用的对象可以引用一个不常用的对象,如果为NULL,临时创建
创建:NewWeakGlobalRef,销毁:DeleteGlobalWeakRef
抛出异常处理
异常处理做法Java层捕获JNI自己抛出的Throwable异常
用户通过清空异常,然后抛出ThrowNew新的异常
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_exeception (JNIEnv *env, jobject jobj){ jclass cls = (*env)->GetObjectClass(env, jobj); //会抛出异常 jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;"); //检测是否发生Java异常 jthrowable exception = (*env)->ExceptionOccurred(env); if (exception != NULL){ //清空异常信息,为了让Java代码可以继续运行 (*env)->ExceptionClear(env); //补救措施 fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;"); } //获取属性的值 jstring jstr = (*env)->GetObjectField(env, jobj, fid); char *str = (*env)->GetStringUTFChars(env, jstr, NULL); //对比属性值是否合法 if (strcmp(str, "super Hensen") != 0){ //不合法则抛出异常,给Java层处理 jclass newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); (*env)->ThrowNew(env,newExcCls,"key's value is invalid!"); } }
局部静态变量
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_cached (JNIEnv *env, jobject jobj){ jclass cls = (*env)->GetObjectClass(env, jobj); //局部静态变量 static jfieldID key_id = NULL; if (key_id == NULL){ //只执行一次代码 key_id = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;"); } }
初始化全局变量
jfieldID key_fid; jmethodID random_mid; JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_MainActivity_initIds (JNIEnv *env, jclass jcls){ key_fid = (*env)->GetFieldID(env, jcls, "key", "Ljava/lang/String;"); random_mid = (*env)->GetMethodID(env, jcls, "genRandomInt", "(I)I"); }
文件加密
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_Utils_FileUtils_crypt (JNIEnv *env, jclass jcls, jstring normal_path_jstr,jstring crypt_path_jstr) { const char* normal_path = (*env)->GetStringUTFChars(env, normal_path_jstr, NULL); const char* cpypt_path = (*env)->GetStringUTFChars(env, crypt_path_jstr, NULL); FILE *normal_fp = fopen(normal_path,"rb"); FILE *cpypt_fp = fopen(cpypt_path,"wb"); int ch; while((ch = fgetc(normal_fp)) != EOF){ fputc(ch ^ 9 ,cpypt_fp); } fclose(normal_fp); fclose(cpypt_fp); }
文件解密
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_Utils_FileUtils_decrypt (JNIEnv * env, jclass jcls, jstring crypt_path_jstr, jstring decrypt_path_jstr) { const char* normal_path = (*env)->GetStringUTFChars(env, crypt_path_jstr, NULL); const char* cpypt_path = (*env)->GetStringUTFChars(env, decrypt_path_jstr, NULL); FILE *normal_fp = fopen(normal_path,"rb"); FILE *cpypt_fp = fopen(cpypt_path,"wb"); int ch; while((ch = fgetc(normal_fp)) != EOF){ fputc(ch ^ 9 ,cpypt_fp); } fclose(normal_fp); fclose(cpypt_fp); }
获取文件大小
long get_file_size(char *path){ FILE *fp = fopen(path,"r"); fseek(fp,0,SEEK_END); return ftell(fp); }
分割文件
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_Utils_FileUtils_diff (JNIEnv *env, jclass jcls, jstring path_jstr,jstring path_pattern_jstr, jint file_num){ const char* path = (*env)->GetStringUTFChars(env,path_jstr,NULL); const char* path_pattern = (*env)->GetStringUTFChars(env,path_pattern_jstr,NULL); //得到分割之后的子文件的路径列表 char **patches = malloc(sizeof(char*) * file_num); int i = 0; for (; i < file_num; i++) { patches[i] = malloc(sizeof(char) * 100); //元素赋值 sprintf(patches[i], path_pattern, (i+1)); LOGI("patch path:%s",patches[i]); } int filesize = get_file_size(path); FILE *fpr = fopen(path,"rb"); //不断读取path文件,循环写入file_num个文件中 //如果文件大小和文件数目整除,则每个文件大小为 filesize / file_num //如果文件大小和文件数目不整除,则每个文件大小为 filesize / (file_num - 1),最后一个文件大小为 filesize % (file_num - 1) if(filesize % file_num == 0){ //单个文件大小 int part = filesize / file_num; i = 0; //逐一写入不同的分割子文件中 for (; i < file_num; i++) { FILE *fpw = fopen(patches[i], "wb"); int j = 0; for(; j < part; j++){ //边读边写 fputc(fgetc(fpr),fpw); } fclose(fpw); } }else{ int part = filesize / (file_num - 1); i = 0; //逐一写入不同的分割子文件中 for (; i < file_num - 1; i++) { FILE *fpw = fopen(patches[i], "wb"); int j = 0; for(; j < part; j++){ //边读边写 fputc(fgetc(fpr),fpw); } fclose(fpw); } //最后一个文件 FILE *fpw = fopen(patches[file_num - 1], "wb"); i = 0; for(; i < filesize % (file_num - 1); i++){ fputc(fgetc(fpr),fpw); } fclose(fpw); } //关闭文件流 fclose(fpr); //释放动态内存 i = 0; for(; i < file_num; i++){ free(patches[i]); } free(patches); (*env)->ReleaseStringUTFChars(env,path_jstr,path); (*env)->ReleaseStringUTFChars(env,path_pattern_jstr,path_pattern); }
合并文件
JNIEXPORT void JNICALL Java_com_handsome_ndkdemo_Utils_FileUtils_patch (JNIEnv *env, jclass jcls,jstring path_pattern_jstr, jint file_num,jstring merge_path_jstr){ const char* merge_path = (*env)->GetStringUTFChars(env,merge_path_jstr,NULL); const char* path_pattern = (*env)->GetStringUTFChars(env,path_pattern_jstr,NULL); //得到分割之后的子文件的路径列表 char **patches = malloc(sizeof(char*) * file_num); int i = 0; for (; i < file_num; i++) { patches[i] = malloc(sizeof(char) * 100); //元素赋值 sprintf(patches[i], path_pattern, (i+1)); LOGI("patch path:%s",patches[i]); } FILE *fpw = fopen(merge_path,"wb"); //把所有的分割文件读取一遍,写入一个总的文件中 i = 0; for(; i < file_num; i++){ int filesize = get_file_size(patches[i]); FILE *fpr = fopen(patches[i], "rb"); int j = 0; for (; j < filesize; j++) { fputc(fgetc(fpr),fpw); } fclose(fpr); } //关闭文件流 fclose(fpw); //释放动态内存 i = 0; for(; i < file_num; i++){ free(patches[i]); } free(patches); (*env)->ReleaseStringUTFChars(env,path_pattern_jstr,path_pattern); (*env)->ReleaseStringUTFChars(env,merge_path_jstr,merge_path); }
相关文章推荐
- 作为基础开发人员必须掌握的linux常用命令
- Android中使用NDK环境开发JNI程序例子
- 使用eclipse开发ndk之:导入现有的ndk工程(NDK例子-Hello-jni导入到eclipse)(转载)
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- Android-本地方法与Java相互调用-自定义ProgressBar(锅炉压力监测例子)/NDK-JNI开发实例(七)
- 作为一个Java开发人员,有些常用的Linux命令必须掌握
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- 一个例子掌握JNI开发
- Android-NDK开发之基础--Android JNI开发高级篇(JNI中的常用方法)
- 【android 开 发 】 - Android studio 下 NDK Jni 开发 简单例子
- 【android 开 发 】 - Android studio 下 NDK Jni 开发 简单例子
- 使用eclipse开发ndk之:导入现有的ndk工程(NDK例子-Hello-jni导入到eclipse)
- 一个掌握JNI开发的例子
- 使用eclipse开发ndk之:导入现有的ndk工程(NDK例子-Hello-jni导入到eclipse)
- NDK开发 - JNI基本数据和字符串处理
- Android的NDK开发(4)————JNI数据结构之JNINativeMethod