Android开发&Android studio中的jni

因为开发需要,很多时候java函数得靠c或者c++来 实现,不得不说是很神奇。
其实说白了,jni就是来”协调”不同语言中的变量类型的。先来看看最基本的,利用jni来完成单个java文件的执行。

第一步,新建一个文件夹,编写java代码:

public class HelloWorld {
    public native void displayHelloWorld();
    //定义为native类型,等待本地实现方法。

    static {
        System.loadLibrary("jniLib");
    }
    //加载库文件,displayHelloWorld方法的具体实现就在这个库中,使用jni的主要目的就是生成这个库。

    public static void main(String[] args) {
        new HelloWorld().displayHelloWorld();
        //定义个main函数入口,方便我们最后检测成果。
    }
}

这个类很简单,就是定义一个native类型的方法,java中只要是native类型的方法,都表示”本地方法“,需要借助本地的库文件来实现.
第二步,生成该HelloWorld类对应的HelloWorld.class文件:
cmd命令为:javac -d . -o HelloWorld.java
javac是编译的指令,-d 表示编译生成的文件存放的位置,这里后面跟了个 ”.“,表示把class文件生成在当前目录,最后是要编译的java文件

生成的class文件内容我们不去管。现在我们的目录下已经有两个文件了,一个是HelloWorld.java,一个是HelloWorld.class。

第三步,使用java自带的javac命令,利用class文件生成 .h 的头文件:
cmd命令为:javah HelloWorld
此时,文件夹下应该多出来一个文件HelloWorld.h,这是c的头文件,打开该头文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    displayHelloWorld
 * Signature: ()V
 */

JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
  (JNIEnv *, jobject);
  //这里参数可以只要类型。

#ifdef __cplusplus
}
#endif
#endif

这个头文件也很简单,只看几个点

include<jni.h>

extren "C"表示以c语言的方式引用

Java_HelloWorld_displayHelloWorld 表示我们想要本地实现的方法,跟开始的java文件里面定义的native方法相关。

很明显,要想实现该方法,首先得把jni.h这个头文件库给找到,我们可以到java安装目录下,再进入include目录,有一个jni.h文件,再进入win32目录,有一个jni_md.h文件,把这两个文件拷贝出来,放到之前我们使用的那个已经存放了三个文件的文件夹。此时文件夹中是五个文件。
然后,把HelloWorld.h文件中include后面的尖括号改成双引号

#include <jni.h>改成
#include"jni.h"
尖括号表示到c的库中找jni.h,很明显c库中是没有这个文件的。
双引号表示先到当前目录下寻找jni.h,这也是为什么我让把两个文件从java的安装目录中拷贝出来。

第四步,编写Java_HelloWorld_displayHelloWorld的具体实现:
新建一个文件,名字可以随便起(后缀名用c或者cpp),这里用HelloWorldInfo.c

#include "HelloWorld.h"
//注意,一定是尖括号,
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
  (JNIEnv * env, jobject obj){
      //这里一定要注意,在上面的HelloWorld.h里,是没有具体的参数的,我们一定要把不全,这里补上了env和obj。
      printf("jni\n");
      return ;
  }

很多人奇怪为什么不直接在HelloWorld.h中把函数具体实现,而要新建一个文件,是不是多此一举?
这里直说两点,第一,我用dev_c++做过一个尝试,对于一个普通的c的源文件,如果该文件后缀名是c或者cpp,那么编译成的exe文件可以直接运行,如果后缀名改成h,生成的exe文件点击后系统会弹出提示框,说版本不兼容什么的;第二,请看这个讲解的很精彩,推荐学c的一定要看

第五步,生成动态库jniLib.dll。
在windows系统下,动态库是dll格式,linux多为so格式。
什么是静态库,什么是动态库,简单的说,生成的exe就是静态库,可以直接执行。
cmd指令:gcc -shared HelloWorldInfo.c -o jinLib.dll
-shared表示生成共享库,-o只生成的库名,和前面java文件中System.loadLibrary(“jniLib”)对应。只是多加了dll。

到现在为止,对于单个java文件的jni搭建就完成了,此时文件夹中应该是七个文件:
这里写图片描述
一个生成的h头文件,两个java文件夹拷贝的头文件,一个java源码,一个class文件,一个具体的方法实现文件,一个最后生成的库文件

最后最命令行中输入:java HelloWorld
这里写图片描述

根据上面简单的jni搭建,可以明白实现jni的大致流程,然后就据此来进行Android studio中jni搭建,首先确保你的ndk已经下载好了,大小现在大概一个G。

首先把各种配置工作做好:
这里写图片描述
app目录下有个build.gradle文件,加上对应的内容。
序号一的区域一般不加内容也没事。
序号二的区域对应字符串,就是我们想要生成的lib库的名字,需要和编写的java文件对应。

这里写图片描述
配置gradle.properties,加上最后一行的代码
这里要说明一下,我用的android studio版本为1.5 beta eap,很多老的版本不要这个设置,但新版本是需要的,否则无法编译。

这里写图片描述
在local.properties里设置ndk的目录

然后开始之后的操作。
第一步 ,编写java文件,这点不需多说。
这里写图片描述
package后面是我新建项目的类名,Jnis为类,static块来加载动态库,System.loadLibrary()里面的字符串要对应之前配置文件里面的库名。
native表明该方法为本地方法,小括号后面是没有大括号的。
这里还要说一下,因为as是java编译器,因此很多代码看起来像是显示错误,这点我们不用管,就像里面的getStringFromJni方法就变红了,不影响运行的。

第二步,生成class
这里写图片描述
先点击build里面的make build或者rebuild,让编译器生成对应的class文件。Jnis对应的class目录在序号二的位置可以看出来。

第三步,进入相应位置,键入命令
这里写图片描述
先点击序号一的位置,弹出terminal,这是东西跟cmd窗口一样,你直接使用cmd窗口也行。
然后一步步进入到包的上级目录,我们这里Jnis所在包目录,在第一步的package后面可以看出来,(如果你gcc命令很熟,可以在其他目录进行操作,但为了方便简单,我选择包的根目录)。
看序号二的位置,就是com.lovingning.javaattempt.jnis包的上级目录,然后键入命令:javah com.lovingning.javaattempt.jnis.Jnis 包名加类
然后就可以看到生成的h头文件了。

第四步,生成h头文件
这里写图片描述
头文件的具体位置可以从序号1和2看出。

第五步,把h头文件剪贴放到jni目录。
这里写图片描述
序号一就是我们剪贴过来的头文件。这里的include后面要使用尖括号
序号二是个空的c文件,因为某些原因,jni目录下必须有两个或以上的除了h以外的文件。
序号三是新建的用来具体实现native方法的文件,这里命名为info.c。
jni和java,res同一级目录,需要自己手动建立。

第六步,编写native实现方法
这里写图片描述
这就是往info.c中写入的内容,要注意两个地方;
第一,include包含的头文件就是我们之前生成的那个,要用双引号,是双引号
第二,方法中除了类型,还要把参数补全,下面return的内容需要NewStringUTF来转换成java中的String类型。

好了,这就完成所有的工作,直接尝试运行吧,我是真机调试的,对话框中显示出了native方法返回的字符串。
这里写图片描述

对比一下单个java文件搭建jni与as搭建jni有什么差异。
第一,as不需要我们剪贴拷贝java目录里的jni.h以及jni_md.h,这些工作ndk都帮我们完成了,虽然不知道编译器是如果根据代码里的#include jni.h就找到对应的实现的。
第二,我们没有生成dll动态库;其实动态库已经生成了,不过android内核可是linux,因此生成的动态库也是so格式的,具体目录在这里
这里写图片描述
可以看到其实生成了很多的so文件,这是因为apk安装的手机肯定不会一模一样。

可以看到虽然很多文件内容都是红的,可执行没问题。
另外博客里尖括号没法直接打出来,所以多担待。
最后提醒,一定要搞清楚尖括号和双引号的区别。

猜你喜欢

转载自blog.csdn.net/lovingning/article/details/50034467