• 使用NDK移植开源项目,JNI的使用技巧


    强烈推荐:

    Android模仿易网新闻页面源码
    http://www.eoeandroid.com/thread-186670-1-1.html

    一个简易版的iphone电子书阅读器
    http://www.eoeandroid.com/thread-186664-1-1.html

    android仿酷狗播放器
    http://www.eoeandroid.com/thread-185894-1-1.html

    jni 的介绍
      JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。以下介绍Android 中如何使用jni移植开源库的技巧.
      JNI日志输出到Logcat中

    #include <android/log.h>
      #define LOG_TAG "===xcloud==="
      #define LOGI(...) android_log_print(ANDROID_LOG_INFO,LOG_TAG,VA_ARGS__)
      #define LOGW(...) android_log_print(ANDROID_LOG_WARN,LOG_TAG,VA_ARGS__)
      #define LOGE(...) android_log_print(ANDROID_LOG_ERROR,LOG_TAG,VA_ARGS__)
      Android.mk文件添加编译模块:
      LOCAL_LDLIBS=-lm -llog

    使用方法:

    LOGE("%s",test);
    

    JNI调用Java方法
      以调用String 的getBytes方法为例:

    第一步:
      jclass clsstring = (*env)->FindClass(env,"java/lang/String"); //找到Java String 类
      第二步:
      jstring strencode = (*env)->NewStringUTF(env,"utf-8");  //得到一个jstring 对象
      第三步:
      jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); //得到getBytes方法
      第四步:
      jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode); //“jstr 为传入的字符串”调用getBytes方法
      第五步:
      jsize alen = (*env)->GetArrayLength(env,barr); //得到数组长度
      jstring 转换 char*
      char* test=(char*)(*env)->GetStringUTFChars(env,jstringVariable,NULL);

    返回一个java对象数组

    第一步:
      jclass objectClass=(*env)->FindClass(env,"com/xuzhitech/xcloud/resource"); //找到对应的java 类(对象)
      第二步:
      jobjectArray array=(*env)->NewObjectArray(env,currentListCount,objectClass,NULL); //通过取到的java类(对象)创建一个指定固定大小的数组
      第三步:
      jfieldID _uri=(*env)->GetFieldID(env,objectClass,"uri","Ljava/lang/String;");//找到对象中的列
      注意:在JNI中并未提供jstring 类型的对象,所以必须通过L指定包名找到该类,在有提供的类型中,可以直接使用该类型的大写首字母(jlong 需使用J)
      如jint 类型可以如此编写:
      jfieldID _vcr=(*env)->GetFieldID(env,objectClass,"is_vcr","I");对应的JNI提供类型
      第四步:
      jmethodID jid = (*env)->GetMethodID(env,objectClass,"<init>","()V");//"<init>"代表可以访问默认构造函数
      jobject jobj=(*env)->NewObject(env,objectClass,jid); //通过第一步找到的jclass创建对应的对象
      第五步:
      (*env)->SetObjectField(env,jobj,_modtime,jmodtime); //为对象中的每一列赋值。注意:如果JNI有提供的数据类型,可按提供的类型为对象中的列赋值,
      如:(*env)->SetIntField(env,jobj,_vcr,current->is_vcr);
      第六步:
      (*env)->SetObjectArrayElement(env,array,i,jobj);将对象设置进第二步声明的数组中
      DeleteLocalRef(env,jobj);//删除引用对象

    在JNI中循环读取对象数组

    for (i = 0 ;i < currentListCount ;i++) {
    jobject obj = (*env)->GetObjectArrayElement(env,array,i);
    jstring jstr=(*env)->GetObjectField(env,obj,_uri);
    LOGE("=====uri===%s====",jstringtoChar(env,jstr));
    }
    上面使用到的jstringtoChar方法代码:
    /**
    *jstring to char
    */
    char* jstringtoChar(JNIEnv* env,jstring jstr){
    char* rtn = NULL;
    jclass clsstring = (*env)->FindClass(env,"java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env,"utf-8");
    jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
    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);
    memcpy(rtn, ba, alen);
    rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env,barr, ba, 0);
    return rtn;

    cpp JNI与 c JNI的主要注意事项:

    用C编写jni文件,访问JNI内置提供方法格式:
      (*env)->SetObjectArrayElement(env,array,i,jobj);
      用CPP编写JNI文件,访问JNI内置提供方法格式:
      env->SetObjectArrayElement(array,i,jobj);
      另外,使用CPP编写的JNI代码,在调用C语言编写的库的时候,要添加以下代码,才可以正常使用(不然在链接的时候找不到相关接口:undefined reference.....):

    ifdef __cplusplus
      extern "C" {
      #endif
      ... //引入的头文件
      #ifdef __cplusplus
      }
      #endif

    其他用法几乎一致。


    JNI 使用Native注册
      按照上面的方法写函数体,必须遵循JNI官方的一大堆标准进行方法的定义,有时候方法一多,不大好管理,也不利用查看,并且每次都要写一大堆恶心的标准方法名也不是一件好事。对此JNI有一套可以通过Native 注册的机制可以使用,以方便函数体的编写。
      以下是Android 调用JNI注册Natives 的步骤:

    第一步
      声明Java中要调用jni 的类路径:
      static const char *className="com/xuzhitech/xcloud/cadaver";
      第二步
      创建方法格式结构体:
      struct JNINativeMethod {
      const char* name;//method name
      const char* signature; //java method return value
      void* fnPtr;//c/c++ method
      } ;
      第三步
      使用结构体注册需要供Android 调用的方法体:
    static JNINativeMethod methods[] = {
    {"StringTestOne", "()Ljava/lang/String;", (void*)StringTestOne},
    {"executels","()[Lcom/xuzhitech/xcloud/resource;",(void*)executels},
    {"getProgress","()Lcom/xuzhitech/xcloud/fileProgress;",(void*)getProgress},
    {"getdownloadState","()I",(void*)getdownloadState},
    {"changeExecutable","(Ljava/lang/String;Ljava/lang/String;)I",(void*)changeExecutable},
    {"Login","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",(void*)Login},
    {"getCurrentListCount","()I",(void*)getCurrentListCount},
    {"mkcol","(Ljava/lang/String;)I",(void*)mkcol},
    {"deleteFile","(Ljava/lang/String;)I",(void*)deleteFile},
    {"deleteCol","(Ljava/lang/String;)I",(void*)deleteCol},
    {"getFullUri","()Ljava/lang/String;",(void*)getFullUri},
    {"cdCommand","(Ljava/lang/String;)I",(void*)cdCommand},
    {"logout","()V",(void*)logout},
    {"doCopy","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",(void*)doCopy},
    {"doMove","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",(void*)doMove},
    {"getFile","(Ljava/lang/String;Ljava/lang/String;)V",(void*)getFile},
    {"putFile","(Ljava/lang/String;Ljava/lang/String;)V",(void*)putFile},
    {"lock","(Ljava/lang/String;)I",(void*)lock},
    {"unlock","(Ljava/lang/String;Ljava/lang/String;)I",(void*)unlock},
    {"getToken","(Ljava/lang/String;)Ljava/lang/String;",(void*)getToken},
    {"isSetToken","(Ljava/lang/String;)I",(void*)isSetToken},

    第一个参数为:Java实现要调用的方法名称
      第二个参数为:该方法需要的返回值与方法参数,如->(方法参数)返回值,详细使用参考如下:
      具体的每一个字符的对应关系如下

      字符 Java类型 C类型

    V void void
    Z jboolean boolean
    I jint int
    J jlong long
    D jdouble double
    F jfloat float
    B jbyte byte
    C jchar char
    S jshort short

    数组则以"["开始,用两个字符表示

    [I jintArray int[]
    [F jfloatArray float[]
    [B jbyteArray byte[]
    [C jcharArray char[]
    [S jshortArray short[]
    [D jdoubleArray double[]
    [J jlongArray long[]
    [Z jbooleanArray boolean[]

    也可返回任意java对象,如上代码的()[Lcom/xuzhitech/xcloud/resource;代表一个不带参数的方法,并且返回java类中的Lcom/xuzhitech/xcloud/resource;数组。'['代表数组的意思,'L'代表类的意思

      第三个参数为,第一个参数需要映射的本地c/c++对应的函数指针方法。

      第四步
      在加载jni的时候指定JNI版本并且通过传入进来的class路径注册Natives 方法

     

    jint JNI_OnLoad(JavaVM* vm, void* reserved){ 
    jint result = JNI_ERR;
    JNIEnv* env = NULL;
    jclass clazz;
    int methodsLenght;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    LOGE("ERROR: GetEnv failed\n");
    return JNI_ERR;
    }
    // assert(env != NULL); 
    clazz = (*env)->FindClass(env,className);
    if (clazz == NULL) {
    LOGE("Native registration unable to find class '%s'", className);
    return JNI_ERR;
    }
    methodsLenght = sizeof(methods) / sizeof(methods[0]);
    if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
    LOGE("RegisterNatives failed for '%s'", className);
    return JNI_ERR;
    }
    // 
    result = JNI_VERSION_1_4;
    return result;
    }

    注意,使用Natives注册运行程序时,它会先检测jni与java类使用jni 类的native 方法是否相对应与一致,即使你没有使用到该 方法也会进行检测。
      第五步
      通过上面的修改,c/c++编写的jni 文件就可以不带一大串标准名称了,您可以像正常编写c一样编写你的jni函数,如下:

     /*
    *move file
    */
    jint doMove(JNIEnv* env,jobject thiz,jstring jsrc,jstring jdest,jstring jrename){
    char* src=(char*)(*env)->GetStringUTFChars(env,jsrc,NULL);
    char* dest=(char*)(*env)->GetStringUTFChars(env,jdest,NULL);
    char* rename=(char*)(*env)->GetStringUTFChars(env,jrename,NULL);
    int returnValue=multi_move(src,dest,rename);
    // free(src);
    (*env)->ReleaseStringUTFChars(env,jsrc,src);
    // free(dest);
    (*env)->ReleaseStringUTFChars(env,jdest,dest);
    // free(rename);
    (*env)->ReleaseStringUTFChars(env,jrename,rename);
    return returnValue;
    }
  • 相关阅读:
    推荐一款快得令地发指本地搜索软件:Everything,绝对改变对NTFS的看法
    “/”应用程序中的服务器错误 WebParts开发时出现的错误
    《让人无法说 NO的攻心说话术》摘要
    UXWEEK
    2012中国交互设计体验日演讲实录
    彩色铅笔入门
    ClickOnce证书签名
    DevExpress控件使用小结
    解决ClickOnce签名过期问题
    属于自己的小小空间
  • 原文地址:https://www.cnblogs.com/vus520/p/2630243.html
Copyright © 2020-2023  润新知