您的位置:首页 > 移动开发 > Android开发

Android JNI学习:Jni第一行代码

2018-03-20 18:30 260 查看
一 前言
      Jni开发在Android开发中是一个比较重要的模块,网上虽然有很多文章,但我一开始学习的的时候还是参考了很多,有些文章有疏漏,或者一些小错误,这里我结合了网上那些文章,加上自己的一些感悟和心得,写了这篇文章,意在以前没有接触过 JNI 开发的新手能有一个良好的开端,写出自己第一个Jni程序。
二 JNI开发
        首先新建一个 Android 工程,我把工程命名为JNI_study,然后在Java目录下新建一个 JniMethod类,在该文件中加载 JNI 的库和定义需要通过 native 实现的方法,如下:public class JniMethod {
static {
/**
* 类加载时便加载库文件,文件名与实现所需的库函数的 C 代码文件名相同
*/
System.loadLibrary("JniMethod");
}
// native 方法
public static native String getNativeString(String s);
}整个目录结构和MainActivity如图:



然后我们进行JNI方法的编译和.so包的制作:
    1.我们先进入刚刚写的 JniMethod 所在的目录,使用javac命令编译该类,得到该类的 .class 文件,因为后面需要通过该 .class 文件生成 .h 头文件,如图:



用命令ls 可以发现这个jni_study文件加下有JniMethod.java和MainActivity.java两个Java文件,我们执行命令javac JniMethod.java后会在该目录下生成一个JniMethod.class文件,如图:



    2.然后要进入项目根目录,使用 javah 命令生成与 JniMethod.class 文件相对应的 .h 头文件,一定要注意要去根目录(java目录下)去执行,如果没去根目录下执行,会报 错误: 找不到 'JniMethod' 的类文件 这样的错误,正确的如图:



如图会在该目录下生成.h文件,文件如图:



其实这个.h文件本身没什么用,叫什么名字也不重要,关键是这个头文件所生成的Java_com_example_jni_1study_JniMethod_getNativeString 这个方法,这个方法就是我们.c文件所要实现的方法,也就JVM用静态方式查找的native方法,具体可以看下这篇文章,写的很好(http://blog.csdn.net/u011913612/article/details/52583523),如果熟悉方法的命名规则,完全可以跳过生成.h文件,直接进行.c文件的编写。    3.接下来就是最重要的.c文件的编写。在java同级目录下新创建一个jni目录(其实这个目录只是一个临时的目录,用来创建.mk文件和.c文件来执行ndk-build,生成.so文件来使用,生成.so文件后这个jni文件夹及其内的文件都可以删除,为了方便查看,也可以把刚刚生成的.h文件也移到该目录下),在jni文件夹下创建一个JniMethod.c 的文件,实现定义在 JniMethod.h 头文件中方法。#include <jni.h>
#include <stdio.h>
#include <android/log.h> #define TAG "clog" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)

JNIEXPORT jstring JNICALL Java_com_example_jni_1study_JniMethod_getNativeString(JNIEnv *env,jobject thiz, jstring jstr) {
//函数的命名规则:
//用javah工具生成函数原型的头文件,函数命名规则为:Java_类全路径_方法名。如Java_com_example_jni_1study_JniMethod_getNativeString,
//其中Java_是函数的前缀,com_example_jni_1study_JniMethod_getNativeString是类名,getNativeString是方法名,它们之间用 _(下划线) 连接

//函数参数:
//第一个参数:JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。
//第二个参数:调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass
//第三个参数:Java对应JNI中的数据类型,Java中String类型对应JNI的jstring类型。(详细介绍JAVA与JNI数据类型的映射关系可查看http://blog.csdn.net/u011913612/article/details/52583523)

//函数返回值类型:
//夹在JNIEXPORT和JNICALL宏中间的jstring,表示函数的返回值类型,对应Java的String类型
// env是JNIEnv类型的参数,jstr是jstring类型的函数参数
int jstrL = (*env)->GetStringUTFLength(env, jstr);//获取jstring的字符数量
LOGI("jstrL %d", jstrL);
char tempChars[jstrL]; //使用GetStringUTFChars转化后,需要显示使用ReleaseStringUTFChars释放内存,所以我们copy后再进行处理
char *temp = (*env)->GetStringUTFChars(env, jstr, NULL);
if(temp == NULL)
{//注释2
return NULL;
}
LOGI("temp %s", temp);
strcpy(tempChars, temp); //复制转化后的char *
LOGI("tempChars %s", tempChars);
(*env)->ReleaseStringUTFChars(env, jstr, temp);//释放

// char * to jstring
jstring result = (*env)->NewStringUTF(env, tempChars);
return result;
}网上一般是最简单的JNI的开发,.c文件下也是最简单的直接返回一段字符串,即
JNIEXPORT jstring JNICALL  Java_com_xxx_xxx_xxx_getNativestring(JNIEnv *env, jobject thiz) { return (*env)->NewStringUTF(env, "Hello from JNI !");}也没有第三个参数,我们这里稍微比较难点,加入了输入参数,因为大多数方法还是需要有参数输入的,还加入了log用来调试检测(还需要在gradle里配置一些参数,后面会讲到),即
#include <android/log.h>

#define TAG "clog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
这里还涉及到了C语言的使用,有兴趣的同学可以学习下C语言,这里不再展开说明,到这里一个完整的.c文件就编写完毕了。
    4.最后在该目录下创建 Android.mk 和 Application.mk 文件,即 Makefile 文件。Android.mk文件如下:LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := JniMethod
LOCAL_SRC_FILES := JniMethod.c
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)注意:如果要有log信息,这句LOCAL_LDLIBS := -llog 是必不可少的

Application.mk文件如下:APP_ABI := armeabi x86 mips具体这2个文件和参数的意义这里不再展开,大家可以用自行百度,参考链接http://blog.csdn.net/bupt073114/article/details/45030781
到这里所有的编译.so文件所需的文件都已经创建完毕,目录如下:



    5.到这里我们就是进行.so文件的制作了,想想是不是有点小激动。进入到 Android.mk 所在的目录(即jni目录),运行 ndk-build 命令,得到动态库 .so 文件。



可以看到在和 jni 目录同级的目录下生成包含不同平台的 .so 文件的 libs 目录和 obj 目录,我们创建一个jniLibs文件夹,把libs文件夹下的所有文件夹(即我们在Application.mk文件里配置的信息后生成的armeabi,mips,x86文件夹,注意要复制整个文件夹,而不是文件夹下的.so文件)复制到jniLibs文件夹目录下,然后可以把 libs 目录和 obj 目录给删除了,其实jni目录也可以删除的。



终于.so文件制作完毕了,是不是以为第一个Android JNI开发app就这样完成了,是不是很激动,结果一运行发现就报错了,感觉是不是被泼了一盆冷水,别急,还有最后一步。
    7.最后一步,配置gradle信息。在app目录下的build.gradle里配置如下



在gradle.properties配置:



在local.properties配置ndk-bundle的安装目录:



到这里第一个Android JNI开发的app正式完成了,可以好好激动下啦!
三 总结
        一个简单的JNI开发的程序就这样完成了,但是这仅仅是JNI开发的第一步,真正学好JNI,还有好多路要走,如何实现.c文件需要实现的方法,用c语言编写高质量的代码,还有很长很长的路要走,学海无涯,希望大家一起进步,如果有什么疑问,欢迎留言,一起交流进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: