前言
在Android开发中,我们经常会用到.so文件。原因有很多,比如部分方法不想暴露,如加密规则。比如部分秘钥需要存储,哪怕最简单的一个加盐的String。我们使用.so调用获取这个String,也比直接明文写在代码中要来的安全。所以就需要我们安卓开发的同学,要知道简单的.so怎么编写。今天为大家带来一篇,如何通过Native方法,从.so中获取一个字符串(可以存储秘钥哦)。
一、Native开发,那就需要对应的NDK的环境。
我的as版本3.5.1,下载NDK,LLDB,CMake工具包。
下载完成后并配置,如图:
NDK下载完成后最好将~/.bash_profile也一起配置下,如图:
环境配置完成后即可进行下一步了。
二、开始编写我们的Native方法
创建我们需要的JniUtil类,用于加载JNI库并定义Native方法
public class JniUtils {
static {
System.loadLibrary("JniUtil");
}
public static native String getJniKey();
}
创建完成以后通过 Build --> Make Project 生成JniUtils的class文件,如果顺利的话可以在以下路径看到(我使用的as版本是3.5.1,class文件在不同的版本位置可能略有不同,根据自己版本去找就好~)
看到class文件说明我们定义的Native方法成功了,
三、通过控制台命令生成.h文件。
1、为了方便我们生成的.h文件到main的jni目录,我们需要先进app/src/main的路径下,执行命令:cd app/src/main
2、执行命令 javah -d jni -classpath xxx.class文件路径(因为as的版本不同,生成的目录也不同,自己取舍下,截取class文件包名之前的路径即可) 包名.类名(如:com.liuw.jnitest.util.JniUtils)
完整命令格式如:javah -d jni -classpath /Users/liuwei/androidWorkSpace/JniTest/app/build/intermediates/javac/debug/classes com.liuw.jnitest.util.JniUtils
不出意外的话这时候main目录下已经生成了jni包和.h文件
打开.h文件看下内容,大概瞄一眼,里面放的就是我们的Native方法
四、创建.c文件
先不用管.h文件,接着继续在jni目录下创建一个c++文件,命名为我们之前定义的JniUtil.cpp(这里注意网上大多数创建的是c文件:如JniUtil.c,但是我的as版本默认创建出来的就是c++文件,无所谓了,只要注意下后缀名是一样的),接着把文件头和extern的内容粘过去并且改造下返回我们需要的东西,如:
#include <jni.h>
extern "C" {
JNIEXPORT jstring JNICALL Java_com_liuw_jnitest_util_JniUtils_getJniKey
(JNIEnv *env, jclass obj) {
return env->NewStringUTF("password");//返回我们需要的秘钥之类的字段
}
}
五、在jni目录下再创建两个配置文件,Android.mk和Application.mk
直接粘贴改下配置就好,目录如图:
1、Android.mk文件内容
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniUtil
LOCAL_SRC_FILES := JniUtil.cpp
include $(BUILD_SHARED_LIBRARY)
2、Application.mk文件内容
APP_PLATFORM根据自己项目的版本配置,APP_ABI是生成适应不同处理器的so包,all是全部平台,也可根据自己需要填写如(armeabi-v7a)
APP_PLATFORM := android-19
APP_ABI := all
五、编译并生成.so包。
1、首先通过命令进入jni路径下,然后执行ndk-build,如果看到下面的日志就说明(大兄弟,你差不多算是成功了!)
[arm64-v8a] Compile++ : JniUtil <= JniUtil.cpp
[arm64-v8a] StaticLibrary : libstdc++.a
[arm64-v8a] SharedLibrary : libJniUtil.so
[arm64-v8a] Install : libJniUtil.so => libs/arm64-v8a/libJniUtil.so
[mips64] Compile++ : JniUtil <= JniUtil.cpp
[mips64] StaticLibrary : libstdc++.a
[mips64] SharedLibrary : libJniUtil.so
[mips64] Install : libJniUtil.so => libs/mips64/libJniUtil.so
[x86_64] Compile++ : JniUtil <= JniUtil.cpp
[x86_64] StaticLibrary : libstdc++.a
[x86_64] SharedLibrary : libJniUtil.so
[x86_64] Install : libJniUtil.so => libs/x86_64/libJniUtil.so
[armeabi] Compile++ thumb: JniUtil <= JniUtil.cpp
[armeabi] StaticLibrary : libstdc++.a
[armeabi] SharedLibrary : libJniUtil.so
[armeabi] Install : libJniUtil.so => libs/armeabi/libJniUtil.so
[armeabi-v7a] Compile++ thumb: JniUtil <= JniUtil.cpp
[armeabi-v7a] StaticLibrary : libstdc++.a
[armeabi-v7a] SharedLibrary : libJniUtil.so
[armeabi-v7a] Install : libJniUtil.so => libs/armeabi-v7a/libJniUtil.so
[mips] Compile++ : JniUtil <= JniUtil.cpp
[mips] StaticLibrary : libstdc++.a
[mips] SharedLibrary : libJniUtil.so
[mips] Install : libJniUtil.so => libs/mips/libJniUtil.so
[x86] Compile++ : JniUtil <= JniUtil.cpp
[x86] StaticLibrary : libstdc++.a
[x86] SharedLibrary : libJniUtil.so
[x86] Install : libJniUtil.so => libs/x86/libJniUtil.so
生成的so包目录如下图
2、再接着在main目录下创建jniLibs包,并将刚生成lib文件夹下的文件粘贴进去,如图
OK,到了这里就大功告成了。
3、接下来就可以在代码中尽情调用了。至于我们创建的jni目录已经用不到了,可以删了随便处理吧。有的人会奇怪怎么直接就用了,不是有一个System.loadLibrary()方法吗?哈哈~还记得我们在创建JniUtils类时就已经放在了static方法体里了吗。
六、创建过程中可能遇到的问题。
1、使用javah生成.h文件一定要注意我上面写的路径,并且包名最后不要加.class或者.java后缀名。
2、ndk-build时报错
No rule to make target needed by *.o
make: *** No rule to make target `x x x/xxxx/xxx/xx.c', needed by `x x x/xxxx/xxx/xx.c.o'. Stop.
原因是后缀名不对引起的,在创建JniUtil.cpp文件时,注意自己的as版本生成的到底是c文件还是c++文件,如果是c++文件后缀名是JniUtil.cpp,如果是c文件后缀名是JniUtil.c,同时根据自己的文件名修改Android.mk文件里的LOCAL_SRC_FILES文件名,如(LOCAL_SRC_FILES := JniUtil.cpp)。
3、java.lang.UnsatisfiedLinkError so库找不到
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.liuw.jnitest.util.JniUtils.getJniKey() (tried Java_com_liuw_jnitest_util_JniUtils_getJniKey and Java_com_liuw_jnitest_util_JniUtils_getJniKey__)
可能还有其他原因,但是我这里是因为生成的so文件不对引起的,虽然成功的生成了so包,但在运行起来时调用native方法直接崩溃,并报这个错误,原因就是我按网上的方法直接粘贴了方法,没有将extern “C” {}带着,所以一定要注意将.h文件内容复制到.cpp文件时,extern “C” {}一定要带着,我不知道网上其他教程是忘了还是他们的版本支持,反正我的这个版本方法体外层没有套extern “C” {}就会一直报错。
4、NDK_PROJECT_PATH=null 错误
网上教程有的在最后一步生成so包的时候是通过rebuild project,但是我这边一直会报NDK_PROJECT_PATH=null 错误,可能是因为缺少了project的配置,最后换了一种直接进入jni目录通过ndk-build的方法好用多了。
5、有的人在纠结so包生成以后放在哪?这里总结下,如果项目里有jniLibs,那就将armeabi等文件连同so一起放进去即可,如果是放在app目录下的libs里,那需要在build.gradle文件里的android{}里添加
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
6、有的教程把build.gradle里的ndk配置过早的放进去,其实没必要,生成不同平台的so包可通过Application.mk文件中的APP_ABI方法配置,而build.gradle里的ndk配置项是用来打包apk时用的,如果不添加此项,那么as打包默认会认为该项目有所有平台的so包,如果没有相应的so文件就会导致程序报错:找不到so文件在某个目录下,因此我们需要根据自己的需要添加ndk的配置,如下
ndk {
moduleName "JniUtil"
abiFilters "armeabi-v7a"
}