本篇介绍开发一个最简单的JNI sample,内容包括:
1. 环境搭建与配置
2. 代码实现
3. 调试与运行
4. 编译安卓可用的jni程序
其实AndroidStudio已经集成了Cmake,这是一个相当方便快捷的工具,但是我不用,原因有两点:
1. 用不了(我的电脑上创建完包含c++的文件的示例程序,移植sync不成功);
2. 更加容易理解原理性的东西;
那么,套用小红母鸡说的,“既然这样,那就我自己来吧!”
搭建GCC编译环境
如果你是在linux下开发,可以跳过这一步,搭建这个环境是为了模拟linux环境编译底层代码(相当于开了个虚拟机,效率肯定会比直接在linux下编译慢很多);
Windows下有两种搭建gcc的方式:Cygwin和minGW,使用都差不多,我这里使用minGW;
关于minGW的环境搭建,github上面有一篇很详细的关于gcc环境搭建的教程,
https://github.com/cpluspluscom/ChessPlusPlus/wiki/MinGW-Build-Tutorial
说的超级详细,这里我制作一个简短的翻译,简化起来就三条:
1. 下载完得到exe后运行解压到指定目录(建议不要有空格和中文);
2. make.exe复制一份到改名为mingw32-make.exe;
3. 将C:\MinGW\bin配置到path环境变量,并在cmd下输入g++ –version检验;
注意:如果你的jdk是64为别下载的mingGW是32位的;
编码实现
想平常一样,创建一个普通的Android应用程序,为了叙述方便,我创建了一个HelloJni的类:
java实现
package com.larson.jni;
/**
* @author Larsonzhong (larsonzhong@163.com)
* @since 2017-12-07 11:14
*/
public class HelloJni {
// 编译后会生成 libhello.so ,然后添加到工程中引用该so文件的时候添加以下这段
static {
// hello 为本地库名,在编写make文件的时候指定,了解make参考我的其他博客
System.loadLibrary("hello");
}
// native方法用于生成.h头文件及给java调用
private native void sayHelloFromC();
// 调用native方法来访问so文件里面的方法
public static void main(String[] args) {
new HelloJni().sayHelloFromC(); // invoke the native method
}
}
生成头文件
有两种方式,如果你开发jni的程序比较多,建议采用第二种;
方式一:命令行使用javah
javah -jni -classpath (搜寻类目录) -d (输出目录) (类名)
搜寻类目录:你的需要生成头文件的java文文件对应生成的class文件所在目录,路径可以是相对路径;我的是
E:SampleProject/testmodule/build/intermediates/classes/[packagename]/
输出目录:存放目标头文件所在的目录,一般放在需要在项目中放置的位置,如果不确定,你可以使用ide自带的new->folder->jni folder,ide会自动创建好这个目录,然后你把这个目录作为输出目录即可;
方式二:使用IDE
打开File->Setting->Tools->Enternal Tools
$Argument$
用来取ide中定义好的值;你照着写就行,不过我觉得把这几个输入想完全弄明白并非难事,我这里是直接把输出目录设置成了java文件所在目录,你也可以直接设置成jni目录(自己摸索,我觉得生成好了拖过去也不费事);
在HelloJNI.java文件中点击右键>External Tools>Generate Header File
编写C并编译成So文件
#include<jni.h>
#include <stdio.h>
#include "com_larson_jni_HelloJni.h"
//Java_com_larson_jni_HelloJni_sayHelloFromC对应头文件中声明的方法
JNIEXPORT void JNICALL Java_com_larson_jni_HelloJni_sayHelloFromC(JNIEnv *env, jobject thisObj) {
printf("Hello World from C !\n");
return;
}
方法一:
接下来在idea自带的Terminal中输入:
gcc -c [c文件所在路径]
文件所在路径可以通过右键该c文件,复制path获得;
比如我的是gcc -c testmodule/src/main/jni/HelloJni.c
然后,会得到一个错误:
F:\LarsonDocument\github\XCar>gcc -c testmodule/src/main/jni/HelloJni.c
testmodule/src/main/jni/HelloJni.c:1:16: fatal error: jni.h: No such file or directory
#include<jni.h>
我们吧jdk下的include目录以及win32目录包含进来:
> gcc -c -I"D:\Program Files\jdk1.8.0_77\include" -I"D:\Program Files\jdk1.8.0_77\include\win32" testmodule/src/main
/jni/HelloJni.c
目测这样有点繁琐,我们来一键生成吧;
方法二:一键生成
前面已经有使用external tool了,所以这里不多废话,直接上图:
转存失败重新上传取消
编译安卓可用的jni程序
安卓针对c代码的编译开发有一套ndk,这个ndk其实也是像cgywin以及mingGW一样,用于交叉编译的,之所以写在后面,是因为很多小伙伴一上来就只要结果,其实和mingGW一比较对比会发现,这都是有很多共通之处,也便于触类旁通,说的通俗点,这套方法不仅仅在eclipse上可用,在idea上可用,在AndroidStudio上可用,你甚至不需要IDE也能进行交叉编译(当然效率会低很多)。
本人电脑的原因导致AndroidStudio2.2版本以后的ide在我公司的电脑上死活同步不成功,我甚至花了一天时间在搞这个东西,后来意识到效率太低,我像是面对一个黑盒子在各种盲测,但是就是不知道怎么就出问题了。想到这里我才开了这一贴,一方面帮助自己理清思路,一方面帮助更多的人从依赖IDE这个死胡同走出来;
我们生产代码,我们不做代码的搬运工!
言归正传,如果看这篇帖子的同学是安卓开发者,会发现上面的方法编译成功后会导致应用打开后崩溃,加载so文件出错,这是因为平台不一样导致的,上面使用mingGW编译出来的是在Windows下通用的so文件,而安卓多半使用的arm架构,而且细心的朋友会发现一般的ndk编译都会有各种平台指定,而mingGW并没有,其实要改成ndk很简单,有了上面的基础,分分钟搞定;
准备ndk
下载ndk可以去AndroidDevTools这个网站下载,其实我作为安卓开发者,很多相关软件都不是翻墙得到的,而是直接从这个网站拿来,当然拿来主义很好,翻翻墙也是很好的;
配置external Tools
我取名为ndkbuild,配置和上面差不多,只是使用的命令是ndk根目录下的ndk-build.bat,界面如下:
转存失败重新上传取消
编写编译脚本Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#生成libNativeLibTest.so和上面例子有区别
LOCAL_MODULE := NativeLibTest
LOCAL_SRC_FILES := NativeLibTest.cpp
include $(BUILD_SHARED_LIBRARY)
编写编译配置Application.mk
APP_ABI := all#编译所有平台下的so
#如果只是编译指定平台,则把all替换成指定平台比如arm64-v8a armeabi ,不同平台中间空格隔开
APP_STL := stlport_static
提示
在配置参数的时候有几点需要注意的:
1. 在没有接触ide的时候我们基本都是配置ndk环境变量,然后cmd进入到需要编译的目录,输入ndk-build就开始编译了,这里是不需要配置环境变量的,因为通过Program已经指定了;
2. paraments配置出错会出现Please define the NDK_PROJECT_PATH variable to point to it错误,ndk会自动搜索配置目录下的jni文件夹并编译,因此只需要指定module所在的目录即可,即$FileDir$
;
3. 可选参数有APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk
就是指定编译脚本和编译配置(Application.mk)配置指定运行平台(abi,比如x86,armeabi等);
4. 如果提示include相关的错误,比如找不到iostream,则属于编译配置相关的,在Application.mk下配置APP_STL := stlport_static
即可;
然后,编译,会自动把target成的so文件添加到libs/下各个平台中,运行即可;