前言
Android开发无法绕开JNI,因为通过JNI我们可以使用大量成熟的用C或者C++写的库,比如openssl等。这几天一直在研究JNI的开发过程,现记录如下。
开发环境:Android Studio 2.0 Preview 3b
1. 新建项目
新建项目,命名MyJniProject,选择Empty Activity。
2. 新建一个HelloWorld类
在HelloWorld类里新增一个native方法,命名为getJNIString,如下图所示。
然后Build整个工程,会自动生成class文件。先把目录结构从Android切为Project,看到class文件的目录如下图所示。
3. 生成.h文件
点击Android Studio底部的Terminal,会打开IDE内置的命令行工具。然后cd命令进入到debug目录下,然后输入命令:
set classpath=”debug目录的绝对路径”
比如在我的MAC上,绝对路径就是
/Users/lqynydyxf/Documents/MyJniProject/app/build/intermediates/classes/debug
所以我输入的就是:
1 | set classpath=/Users/lqynydyxf/Documents/MyJniProject/app/build/intermediates/classes/debug |
然后用javah -jni命令编译HelloWorld.class,生成.h文件。
1 | javah -jni com.example.lqynydyxf.myjniproject.HelloWorld |
执行上述操作后,在debug目录下,会出现一个文件名为
com_example_lqynydyxf_myjniproject_HelloWorld的.h文件。
.h文件内的代码其实就是对之前定义的getJNIString方法的声明,但是此时方法名会变成
Java_com_example_lqynydyxf_myjniproject_HelloWorld_getJNIString**。
.h文件的内容如下图所示:
4. 生成.c文件
进入到src目录下,新建一个名为jni的文件夹(New->Directory),然后把之前生成的.h文件剪切过来,放到jni目录下,同时在jni目录下新建一个.c文件,可随意命名,但是这里为了整齐,我将.c文件的文件名写的和.h文件一样。.c文件的内容是对.h文件中声明的方法进行具体实现。.c文件的内容如下所示:
1 |
|
这里,我们直接返回了一个 Hello World! 字符串。
5. 生成Android.mk文件
此时,在jni目录下,我们已经拥有了一个.h和.c文件,然后我们需要再新建一个Android.mk文件,然后添加如下内容:
1 | LOCAL_PATH := $(call my-dir) |
添加后,结果如下图所示:
- LOCAL_PATH: 这个变量用于给出当前文件的路径。 必须在 Android.mk 的开头定义,可以这样使用:LOCAL_PATH := $(call my-dir)。如当前目录下有个文件夹名称 src,则可以这样写 $(call src),那么就会得到 src 目录的完整路径。这个变量不会被$(CLEAR_VARS)清除,因此每个 Android.mk 只需要定义一次(即使在一个文件中定义了几个模块的情况下)。
my-dir是GNU Make函数宏,必须通过使用$(call ),返回当前Android.mk文件所在的目录的路径,相对于 NDK 编译系统的顶层。
LOCAL_MODULE: 这是模块的名字,它必须是唯一的,而且不能包含空格。 必须在包含任一的$(BUILD_XXXX)脚本之前定义它。模块的名字决定了生成文件的名字。
LOCAL_SRC_FILES: 这是要编译的源代码文件列表。 只要列出要传递给编译器的文件,因为编译系统自动计算依赖。
如果想要生成不同架构下的so文件,再新建一个Application.mk文件(默认是armeabi),添加想要支持的平台,比如:
Application.mk
1 | APP_ABI := armeabi armeabi-v7a x86 mips |
6. 其他配置
- 在local.properties文件中指明ndk的目录:
- 修改Module的build.gradle文件:在defaultConfig中加入如下代码:
1 | ndk{ |
这里的moduleName需要和Android.mk文件里的LOCAL_MODULE一致。
- 在gradle.properties中加入以下代码:
1 | android.useDeprecatedNdk=true |
到这里,基本的配置结束,然后Build整个项目。Build通过后,就可以进行下一步,也是最重要的一步。
7. 生成so文件
打开Terminal,使用cd命令进入到jni目录下,执行ndk-build命令。
如图所示,代表执行成功,这时切换到Project目录结构下,我们可以发现,在libs文件夹下,成功生成了.so文件。
NDK编译系统会在moduleName前面加上lib,后面加上.so来作为so文件的文件名。
8. 调用so文件
回到HelloWorld类中,我们添加如下代码:
1 | static { |
最后HelloWorld类的内部如下图所示:
然后修改MainActivity的onCreate代码如下:
1 | protected void onCreate(Bundle savedInstanceState) { |
结果如图所示:
9.运行
运行项目,模拟器界面如下:
这里输出的Hello World!就是我们通过JNI调用得到的。