文本心得通过以下三篇文章习来:
Android Studio开发JNI工程
Android Sutdio开发NDK工程
在Android Studio中直接编译C/C++文件
谢谢三位作者的分享,才能学到这么宝贵的知识。
一直以来都是用的别的SDK提供的so库,编译so库文件跟调用so库的方法都是未曾尝试过。以前看博客了解的是使用Cygwin进行编译so. 现在有了AndroidStudio这个IDE,我们就可以直接使用这个工具就行编译了。
我们先准备好工具,先安装好NDK。然后开始本文的第一步:
1.在这里我们先简单写一个方法,返回一个字符串(在项目中我们可以将关键的信息,如秘钥打在so库中,虽然也有被反编译的风险,但是还是相比定义在Java层,或者文件中会安全一点),那这里我们就先在Java层中定义一个要调用的本地方法。
public class Math {
public native static String getStringFromNative();
}
然后我们在点击Build -Make Project进行编译
编译完之后,就会在
生成Math.class文件
2.我们点击Terminal将路径跳转
进入app_path/app/build/intermediates/classes/debug下,或
app_path/app/src/main/java
在我这里app_path就是F:\demo\JniDemo>
然后输入javah -classpath . com.csf.jnidemo.Math生成对应的.h文件
这个是c或者c++ 的头文件。
执行完毕之后就会在对应的路径下生成
com_csf_jnidemo_Math.h。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
#include
#ifndef LOG_TAG
#define LOG_TAG "ANDROID_LAB"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif
/* Header for class com_csf_jnidemo_Math */
#ifndef _Included_com_csf_jnidemo_Math
#define _Included_com_csf_jnidemo_Math
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_csf_jnidemo_Math
* Method: getStringFromNative
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_csf_jnidemo_Math_getStringFromNative
(JNIEnv * env, jobject jObj){
LOGE("log string from ndk.");
return (*env)->NewStringUTF(env,"Hello From JNI!");
}
#ifdef __cplusplus
}
#endif
#endif
其中一些语句我还不太理解,主要是从上面三篇文章拷贝过来的,但是具体内容看懂就OK了,
Java_com_csf_jnidemo_Math_getStringFromNative
在这个方法中我们使用JNI的NewStringUTF生成一个UTF的jstring ,值为”Hello From JNI!”,返回到Java层,同时打下一句Log。
注:
上面的生成.h跟.c方法我们可以使用AndroidStudio来直接生成.c文件,方法如下,直接在native方法上按alt+enter生成c层的方法
4.编译.so文件
我们先配置工程的ndk路径,不然就会出现以下错误
Error:Execution failed for task ':app:compileDebugNdk'.
> NDK not configured.
Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.
(On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)
我们在local.properties文件中配置如下信息指定ndk路径
在gradle.properties中加入如下语句
android.useDeprecatedNdk = true
接着修改我们app的gradle文件,在android下增加如下信息
ndk{
moduleName "JniTest" //lib的名称,对应LOCAL_MODULE
//stl "stlport_shared" //对应APP_STL
ldLibs "log", "z", "m" //链接时使用到的库,对应LOCAL_LDLIBS
//cFlags 编译gcc的flag,对应LOCAL_CFLAGS
}
这样就能编译出各种ABI的so库,如果要指定编译某种CPU架构的so文件,在ndk标签下增加如下语句
abiFilters "armeabi", "armeabi-v7a", "x86"
下面前期准备工作就做的差不错了,不过还有一个问题,我没有去验证,
上面几篇博客都有提到的在执行”Build->Rebuild Project”,编译so文件
的时候,在Windows平台下会出现下面这个问题
Error:Execution failed for task ':app:compileDebugNdk'.
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:
D:\Mission\adt-bundle-windows\ndk-r10b\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\Android.mk APP_PLATFORM=android-21 NDK_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj NDK_LIBS_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a,x86
Error Code:
2
Output:
make.exe: *** No rule to make target `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni', needed by `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni\main.o'. Stop.
解决方法是在jni下面建立一个空的.c文件。
最后编译后的so就存在以下目录下
文件编译出来是不是很高兴,可是应该怎么使用呢
6.调用so库文件
我们创建一个Android module(Test,下文简称T工程),将上面lib下的文件拷贝到到这个新工程下lib文件夹下
之后我们要编辑一下T工程的的gradle脚本,在android标签下增加以下脚本
sourceSets {
main {
jniLibs.srcDir(['libs'])
}
}
那前期的准备工作就完成了,下面就是调用的了
这个时候我们会发现编译器根本就找不到com.csf.jnidemo.Math.getStringFromNative这个包名下的这个方法。
这是因为我们没有编写对应的Java层声明代码(以前都是直接用的别人的jar包跟so文件,别人的Java层声明代码就是写在jar包上),那应该怎么声明方法呢。这个是有一定的要求的,Java层申明getStringFromNative方法,他的类名,包名要跟.so中的想对应。我们这里采用最简单的方法,因为so是我们自己编译的,我们其实已经有Java层声明代码了,看下图
我们直接将上面编译so工程的Math类及其包名目录拷贝到我们的T工程上就OK 了,其实我们可以将这个代码打出Jar包在T工程中引用,这样才是比较稳妥的。
好了,本文到此就完结了,上面只是用到的最简单的从so库取出一个字符串,以后我们再学习探讨下传参进native方法中处理后return到Java层的方法以及调用其他现成so库的方法。大家一起学习进步吧。
本文如果有存在什么错误的话,请指明下,谢谢!