JNI与NDK

JNI,是Java Native Interface的缩写,中文为Java本地调用。通俗地说,JNI是一种技术,通过这种技术可以做到以下两点: · Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。 · Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

交叉编译

  • 在一个平台下,编译出另一个平台能够执行的二进制代码* 平台:windows , mac os, linux* 处理器:x86(主要厂商英特尔和英伟达,pc一般用这个),arm(嵌入式设备,手机用这个),mips(开源的一个处理器架构,很多厂商对它进行修改)
  • 原理:
    • 源代码->编译->链接->可执行程序
    • 模拟其他平台特性
  • 交叉编译的工具链
    • 多个工具集合,一个工具使用完后接着调用下一个工具
  • 常见工具
    • NDK:模拟其他平台特性来编译代码的工具
    • CDT:C/C++ Development Tools(高亮显示C语言关键字)
    • cygwin:模拟器,可以在windows下运行linux指令

NDK介绍

  • NDK目录结构
    • build/tools:linux的批处理文件
    • platforms:编译C代码需要使用的头文件和类库
    • prebuild:预编译使用的二进制可执行文件
    • python-pachages:
    • sources:NDK的源码
    • toolchains:工具链
    • ndk-build.cmd:编译打包C代码的一个指令,肯定会调用toolchains开发人员不用管

使用JNI

  1. 在项目根目录下创建 jni文件夹
  2. 在jni文件夹中创建一个 C 文件
  3. 除了两个标准头文件在包含<jni.h>
  4. 在Java代码中创建一个本地方法helloFromC
    public native String helloFromC();
  5. 在JNI中定义函数实现这个方法,函数必须这么写
    Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv * env,jobject obj)
  • 其中 Java是必须的关键字,后面跟着包名类名方法名,中间用_隔开
  • 参数必须是JNIEnv* env和jobject obj。
    • env是一个二级指针,指向存放Java虚拟机的内存地址的内存块
    • obj表示那个对象调用该方法
  • 可以使用javah指令自动生成:javah 包名.类名
    • JDK1.7 在src目录下执行
    • JDK1.6 在bin/classes目录下执行
  • 结果在src下会生成一个c++文件,文件中有这么个方法:
    jstring JNICALL Java_com_hk_hellojni_MainActivity_helloFromC(JNIEnv *, jobject);`
    直接复制黏贴加参数名就可以在我们的C或C++文件中用了,保证无误。
  1. 返回一个字符串,用C定义一个字符串
    char* cstr = "hello from C";
  2. 将C字符串转化成Java字符串
    jstring jstr = (*env)->NewStringUTF(env,cstr);
  • NewStringUTF是env所指向的指针所指向的结构体中的函数指针变量
  1. 在jni文件夹中创建Android.mk文件,文件内容如下
    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    #编译生成的类库叫什么名字
    LOCAL_MODULE:= hello
    #要编译的C文件
    LOCAL_SRC_FILES:= Hello.c
    include $(BUILD_SHARED_LIBRARY)
  2. 在jni文件夹下执行ndk-build.cmd指令(要提前配置到环境变量中)
  3. Java代码中加载so类库,调用本地方法
    System.loadLibrary("hello");//一般在静态代码块中执行

注意:ndk-build.cmd指令执行默认生成arm架构的类库,如果需要支持其他cpu架构需要在jni文件夹下创建Application.mk文件里面加入需要支持的架构,如,加入x86的支持
APP_ABI :=armeabi armeabi-v7a x86
如果需要支持cpu全部架构,把等号右边换成all

JNI常见错误

  1. findLibrary returned null (类库加载失败)
  • CPU平台不匹配
  • 加载类库时,写错类库名字
  1. 本地方法找不到
  • 忘记加载类库
  • C代码中的和Java本地方法对应的方法名写错

Eclipse配置NDK开发环境

  • 指定NDK位置:Windows->Preferences->Android->NDK->Browser到NDK所放位置下的build文件夹
  • 关联jni.h:选中项目右键->Properties->C/C++ General->Paths and Symbols->Add->选择NDK目录下的platforms选择对应的SDK版本号,选择cpu架构选择usr选择include(例如:D:\android\android-ndk-r11c\platforms\android-21\arch-arm\usr\include)->点击完成> 配置完成后可以直接运行Android Application,在编译的时候会自动生成类库

C语言使用Logcat(调试)

  • Android.mk文件增加LOCAL_LDLIBS += -llog
  • C代码中增加
    #include <android/log.h> #define LOG_TAG "hvcker"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, VA_ARGS)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, VA_ARGS)
  • 在代码中就可以用了
    LOGI("info\n");
    LOGD("debug\n");

在C中使用反射调用Java方法

/**
 * 得到Java字节码对象内存地址 
 */ 
//jclass (*FindClass)(JNIEnv*, const char*); 
jclass clazz = (*env)->FindClass(env,"com/example/jniccj/MainActivity"); 
 /** 
 * 得到Java方法,最后一个参数是方法签名,可用javap -s 获取 
 */ 
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); 
jmethodID methodID = (*env)->GetMethodID(env,clazz,"show","(Ljava/lang/String;)V"); 
/** 
 * 调用方法,CallXxxMethod,Xxx为方法返回值,最后一个参数是可变参数,传入方法参数 
 */
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,obj,methodID,(*env)->NewStringUTF(env,"hello Java"));
  • javap -s 要在projectName/bin/classes目录下执行
  • 执行代码:javap -s packageName.classnName

调用C++代码

#include <jni.h> //包含同一个文件夹下的声明函数的头文件     
#include "com_example_jnicpp_MainActivity.h" 
JNIEXPORT jstring JNICALL       
Java_com_example_jnicpp_MainActivity_helloCpp( JNIEnv * env, jobject obj) { 
  char * cstr = "hello from cpp";  //C++的env只是一个一级指针   
  return env->NewStringUTF(cstr); 
}
  • 包含头文件(用javah 生成的那个在src目录下的文件)
  • C的env和C++的env的区别
    • 在C中JNIEnv是一个结构体指针typedef const struct JNINativeInterface* JNIEnv;,后面再加一个就表示是一个二级指针,所以用的时候要先取出env所指向的地址,该地址是指向JNINativeInterface结构体的指针,所以要调用JNINativeInterface里面的方法要(env)->xxx
    • 在C++中JNIEnv是一个里面含有JNINativeInterface的结构体
      typedef _JNIEnv JNIEnv;
      struct _JNIEnv {
      /* do not rename this;
      it does not seem to be entirely opaque
      /
      const struct JNINativeInterface
      functions;
      ...
      }
      所以C++的 JNIEnv* 是一个一级指针,所以调用的时候直接env->xxx就可以了。

还有一点要注意的是:C++是面向对象的,所以它在执行类似newStringUTF方法时不需要传入本身env,直接env->newStringUTF("hello from c");底层也是调用JNINativeInterface中的函数jstring NewStringUTF(const char* bytes) { return functions->NewStringUTF(this, bytes); }

分支C进程

int pid = fork(); 
//如果pid = 0分支成功 
if(pid == 0){ 
  while(1){ 
    LOGD("fork C"); 
    sleep(1); 
  } 
} 
  • 分支出来的C进程在不同型号的手机会有不同的表现形式,例如在魅族手机,依赖于Android进程,程序管理里面强制停止,C进程随之停止,但在有些型号手机中,C进程怎么杀也杀不死,只能用adb shell kill [pid]来删除
  • 可以分支出两个守护进程,这样怎么杀也杀不掉(可以给线程做保活)

常用代码(C部分)

  • 中文乱码
    jstring ctojstring(JNIEnv env, char tmpstr) {
    jclass Class_string;
    jmethodID mid_String, mid_getBytes;
    jbyteArray bytes;
    jbyte* log_utf8;
    jstring codetype, jstr;
    Class_string = (env)->FindClass(env, "java/lang/String"); //获取class
    //先将gbk字符串转为java里的string格式
    mid_String = (
    env)->GetMethodID(env, Class_string, "<init>", "([BLjava/lang/String;)V");
    bytes = (env)->NewByteArray(env, strlen(tmpstr));
    (
    env)->SetByteArrayRegion(env, bytes, 0, strlen(tmpstr), (jbyte) tmpstr);
    codetype = (
    env)->NewStringUTF(env, "gbk");
    jstr = (jstring) (env)->NewObject(env, Class_string, mid_String, bytes,codetype);
    (
    env)->DeleteLocalRef(env, bytes);
    //再将string变utf-8字符串。
    mid_getBytes = (env)->GetMethodID(env, Class_string, "getBytes", "(Ljava/lang/String;)[B"); codetype = (env)->NewStringUTF(env, "utf-8");
    bytes = (jbyteArray) (env)->CallObjectMethod(env, jstr, mid_getBytes, codetype);
    log_utf8 = (
    env)->GetByteArrayElements(env, bytes, JNI_FALSE);
    return (*env)->NewStringUTF(env, log_utf8);
    }

  • Java字符串转C
    char* _JString2CStr(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = (env)->FindClass(env, "java/lang/String");
    jstring strencode = (
    env)->NewStringUTF(env,"GB2312");
    jmethodID mid = (env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)(
    env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
    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语言操作Java数组
    //拿到整形数据的长度和整形数组的指针
    //jsize (GetArrayLength)(JNIEnv, jarray);
    int length = (env)->GetArrayLength(env,jintarr);
    //jint
    (GetIntArrayElements)(JNIEnv, jintArray, jboolean);
    int * arrp = (
    env)->GetIntArrayElements(env,jintarr,0);
    int i;
    for(i = 0;i<length;i++){
    *(arrp+i)+=20;
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容