1.在公司开发JNI程序的步骤:
1.得到别人给.so文件和开发文档;
2.根据开发文档的说明,在java代码中创建本地方法;
3.把so文件放到工程的lib/armeabi/目录;
4.在java代码中加载动态库;
5.调用本地方法;
2.JNI第一个HelloWorld程序
NDK 开发环境:
下载NDK, 最新版本android-ndk-r9.
Windows 32-bit 版本下载地址:
http://dl.google.com/android/ndk/android-ndk-r9-windows-x86.zip
Windows 64-bit 版本下载地址:
http://dl.google.com/android/ndk/android-ndk-r9-windows-x86_64.zip
解压压缩包.
配置环境变量.
3.开发jni程序流程
1.在java代码中写一个本地方法:public native String sayHelloFromC();
2.在工程的根目录下创建jni文件夹;
3.在jni目录下写一个c文件:
4.在c文件中实现C方法:
#include <stdio.h> #include <stdlib.h> #include <jni.h> // public native String sayHelloFromC(); // String cn.itcast.hellojni.MainActivity.sayHelloFromC() // 方法:返回值 Java_ 方法的全名(把里面的.换成_),接收两个参数:NIEnv* env,jobject obj jstring Java_cn_itcast_hellojni_MainActivity_sayHelloFromC(JNIEnv* env,jobject obj){ // 声明一个c中的爱富川 char* text = "hello from C!!!"; // 把c中的字符串转换成jstring:jstring (*NewStringUTF)(JNIEnv*, const char*); jstring jstr = (*env)->NewStringUTF(env,text); return jstr; }
5.在jni目录下创建一个Android.mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello // 动态库的名称 LOCAL_SRC_FILES := hello.c // c文件 include $(BUILD_SHARED_LIBRARY)
6.在jni目录下创建一个Application.mk文件:
APP_ABI := all
7.在命令行窗口切换工程的根目录下,执行ndk-build命令生成动态库;
8.在java代码中加载动态库:
// 加载动态库 System.loadLibrary("hello");
9.在java代码中调用本地方法;
4.jni开发常见错误
1.缺少Android.mk文件, 在jni目录下创建一个Android.mk文件.
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
2.c文件没有include导入jni.h的头文件, 导致某些类型找不到.
"Compile thumb : itheima31 <= Hello.c
jni/Hello.c:4:1: error: unknown type name 'JNIEXPORT'
jni/Hello.c:4:19: error: expected '=', ',', ';', 'asm' or '__attribute__' before
'JNICALL'
jni/Hello.c:4:19: error: unknown type name 'JNICALL'
3.方法的形参没有指定名称. 加上名字就可以了.
jni/Hello.c:6:3: error: parameter name omitted
4.没有加载动态库.so文件.
No implementation found for native Lcom/itheima31/commonerrordemo/MainActivity;.helloFromC ()Ljava/lang/String;
5.加载动态链接库错误, 名字写错. 加载时一定要写: Android.mk文件中的LOCAL_MODULE对应的名字.
Caused by: java.lang.UnsatisfiedLinkError: Couldn't load libitheima31.so: findLibrary returned null
6、 确定加载.so库文件的名字没有错误, 还是报一下错误. 是模拟器问题. 把生成的arm下的.so文件防盗x86模拟器上运行就报此错误.
Caused by: java.lang.UnsatisfiedLinkError: Couldn't load itheima31: findLibrary returned null
解决方法: 在jni目录下创建一个Application.mk文件, 声明以下内容:
APP_ABI := all
// 当前只能在x86的模拟器上运行, 因为生成的so文件是x86机器的机器码
APP_ABI := x86
5.Android.mk文件说明
// $表示调用本地的方法(JNI里面的方法),my-dir表示工程中的jni目录
LOCAL_PATH := $(call my-dir)
//下次构建动态库时先清除上次构建产生的变量
include $(CLEAR_VARS)
//指定动态库的名称
LOCAL_MODULE := hello
//指定本地的c文件
LOCAL_SRC_FILES := hello.c
// 构建动态库.so,.a静态库
include $(BUILD_SHARED_LIBRARY)
6.简便的开发流程
1.检查NDK的目录是否配置了:window-preferences-Android-NDK-NDK Location: 自己NDK的目录;
2.在java代码中创建一个本地方法;
3.右键工程->Android Tools->add native support...,设置动态库的名称,会创建jni目录和Android.mk,Cpp文件;
4.把cpp文件改成c文件,把Android.mk中的cpp改成c;
5.在 命令行窗口中,切换到工程src的目录下,使用javah 类的全名,生成头文件;
6.把头文件拖到jni目录下;
7.把头文件导入到c文件中,实现c方法,设置路径与符号: 右键工程->properties->C/C++ General ->Paths and Symbols->includes->
add->File system:D:\develops\android-ndk-r9d\platforms\android-16\arch-arm\usr\include;
代码:
#include <stdio.h>
#include <stdlib.h>
#include "cn_itcast_simplejni_MainActivity.h"
/**
*JNIEnv * env 是jni.h文件中指定的结构体二级指针,里面定义了很多函数指针,如果想使用这些函数指针,就需要使用到env
* jobject obj 是在java代码中调用给本地方法的对象
*/
JNIEXPORT jstring JNICALL Java_cn_itcast_simplejni_MainActivity_hellFromC
(JNIEnv * env, jobject obj){
char* text = "hello from C????";
// 把c中的字符串转换成jstring,调用:jstring (*NewStringUTF)(JNIEnv*, const char*);
jstring jstr = (*env)->NewStringUTF(env,text);
return jstr;
}
8.选中工程,砸一锤子,生成动态库
9.在Java代码加载动态库;
10.在Java代码调用本地方法;
7.java传递数据给c
#include <stdio.h> #include <stdlib.h> #include "cn_itcast_passdatademo_JNI.h" /** * 把jstr转换成char* */ 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; } JNIEXPORT jint JNICALL Java_cn_itcast_passdatademo_JNI_add (JNIEnv * env, jobject obj, jint x, jint y){ return x + y; } JNIEXPORT jstring JNICALL Java_cn_itcast_passdatademo_JNI_sayHelloInC (JNIEnv * env, jobject obj, jstring jstr){ // 把jstr转换成c里面的字符串 char* text = _JString2CStr(env,jstr); char* cStr = " yuanyuan"; // 在text后面拼接一个字符串: char* strcat(char *, const char *); strcat(text,cStr); jstring result = (*env)->NewStringUTF(env,text); return result; } JNIEXPORT jintArray JNICALL Java_cn_itcast_passdatademo_JNI_arrElementsIncrease (JNIEnv * env, jobject obj, jintArray iArray){ // 修改数组中每一个元素,返回数组 // 1.得到数组的长度:jsize (*GetArrayLength)(JNIEnv*, jarray); jsize length = (*env)->GetArrayLength(env,iArray); // 2.得到数组的地址:jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); jint* arrayAddress = (*env)->GetIntArrayElements(env,iArray,JNI_FALSE); // 3.循环修改每个元素的值 int i; for(i=0;i<length; i++){ *(arrayAddress + i) += 10; } // 4.返回数组 return iArray; } JNIEXPORT jint JNICALL Java_cn_itcast_passdatademo_JNI_checkPassword (JNIEnv * env, jobject obj , jstring jstr){ // 使用jstr与c中的字符串比较,把结果值返回出去 // 1.创建一个c的字符串 char* text = "abc"; // 2.把jstring转换成c中的字符串 char* cstr = _JString2CStr(env,jstr); // 3.比较两个字符串 int result = strcmp(text,cstr); // 4.返回结果值 return result; }
8.回调java中的方法
#include <stdio.h> #include <stdlib.h> #include "cn_itcast_calljavaincdemo_JNI.h" #include <android/log.h> #define LOG_TAG "System.out" #define LOGE(...)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGI(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) JNIEXPORT void JNICALL Java_cn_itcast_calljavaincdemo_JNI_callHelloFromJavaInC( JNIEnv * env, jobject obj) { // 使用反射的机制调用java类中的方法:public void helloFromJava() // 1.得到class对象 char* path = "cn/itcast/calljavaincdemo/JNI"; // 得到类的class:jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env, path); // 2.得到方法对象:jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodId = (*env)->GetMethodID(env, clazz, "helloFromJava", "()V"); // 3.得到java类的对象(JNI类的对象):jobject (*AllocObject)(JNIEnv*, jclass); jobject jobj = (*env)->AllocObject(env, clazz); // 4.调用方法:void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env, jobj, methodId, NULL); } JNIEXPORT void JNICALL Java_cn_itcast_calljavaincdemo_JNI_callAddInC( JNIEnv * env, jobject obj) { // 使用反射的机制调用java类中的方法:public int add(int x,int y) // 1.得到class对象 char* path = "cn/itcast/calljavaincdemo/JNI"; // 得到类的class:jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env, path); // 2.得到方法对象:jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodId = (*env)->GetMethodID(env, clazz, "add", "(II)I"); // 3.得到java类的对象(JNI类的对象):jobject (*AllocObject)(JNIEnv*, jclass); jobject jobj = (*env)->AllocObject(env, clazz); // 4.调用方法:jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); jint result = (*env)->CallIntMethod(env, jobj, methodId, 10, 20); LOGE("result==========%d\n", result); } // public void printString(String s) JNIEXPORT void JNICALL Java_cn_itcast_calljavaincdemo_JNI_callPrintStringInC( JNIEnv * env, jobject obj) { // 使用反射的机制调用java类中的方法:public void printString(String s) // 1.得到class对象 char* path = "cn/itcast/calljavaincdemo/JNI"; // 得到类的class:jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env, path); // 2.得到方法对象:jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodId = (*env)->GetMethodID(env, clazz, "printString", "(Ljava/lang/String;)V"); // 3.得到java类的对象(JNI类的对象):jobject (*AllocObject)(JNIEnv*, jclass); jobject jobj = (*env)->AllocObject(env, clazz); // 4.调用方法:void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); jstring jstr = (*env)->NewStringUTF(env, "itcast05"); (*env)->CallVoidMethod(env, jobj, methodId, jstr); }
9.回调java中的静态方法
JNIEXPORT void JNICALL Java_cn_itcast_calljavaincdemo_JNI_callSayHelloInC( JNIEnv * env, jobject obj) { // 使用反射的机制调用java类中的方法:public static void sayHello(String text) // 1.得到class对象 char* path = "cn/itcast/calljavaincdemo/JNI"; // 得到类的class:jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env, path); // 2.得到 静态方法对象:jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodId = (*env)->GetStaticMethodID(env, clazz, "sayHello","(Ljava/lang/String;)V"); // 3.调用静态方法:void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); jstring jstr = (*env)->NewStringUTF(env, "I'm from C!!!"); (*env)->CallStaticVoidMethod(env,clazz,methodId,jstr); }
10.回调MainActivity中的showToast方法
/** * jobject obj 在java代码中调用本地方法类对象 就是mainActivity对象 */ JNIEXPORT void JNICALL Java_cn_itcast_showtoastinmainactivity_MainActivity_callShowToastInC (JNIEnv * env, jobject obj){ //使用反射机制的过程调用MainActivity类中的 方法:public void showToast() // 1.得到class char* path = "cn/itcast/showtoastinmainactivity/MainActivity"; // jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env,path); // 2.得到方法对象:jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodId = (*env)->GetMethodID(env,clazz,"showToast","()V"); // 3.得到java类的对象 // obj就是mainActivity对象 // 4.调用Java类中的方法:void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env,obj,methodId,NULL); }