NDK开发——JNI必须掌握的常用例子

来源:互联网 发布:suse 查看端口 编辑:程序博客网 时间:2024/05/21 11:17

    • 前言
    • 效果预览
    • 导入依赖库
    • 宏定义
    • 访问Java属性
    • 访问java静态属性
    • 访问java方法
    • 访问java静态方法
    • 访问构造方法
    • 调用父类的方法
    • 字符串转码
    • 给定一个数组进行排序并同步到Java中
    • 创建一个指定大小的数组
    • 局部变量引用
    • 创建全局引用
    • 获得全局引用
    • 释放全局引用
    • 弱全局引用
    • 抛出异常处理
    • 局部静态变量
    • 初始化全局变量
    • 文件加密
    • 文件解密
    • 获取文件大小
    • 分割文件
    • 合并文件

前言

要学习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_studyLOCAL_SRC_FILES := jni_study.cLOCAL_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

抛出异常处理

异常处理做法

  1. Java层捕获JNI自己抛出的Throwable异常
  2. 用户通过清空异常,然后抛出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);}
阅读全文
2 0
原创粉丝点击