MAC环境下使用AS生成so包总结 --- JNI基础篇

前言

在Android开发中,我们经常会用到.so文件。原因有很多,比如部分方法不想暴露,如加密规则。比如部分秘钥需要存储,哪怕最简单的一个加盐的String。我们使用.so调用获取这个String,也比直接明文写在代码中要来的安全。所以就需要我们安卓开发的同学,要知道简单的.so怎么编写。今天为大家带来一篇,如何通过Native方法,从.so中获取一个字符串(可以存储秘钥哦)。

一、Native开发,那就需要对应的NDK的环境。

我的as版本3.5.1,下载NDK,LLDB,CMake工具包。
MAC环境下使用AS生成so包总结 --- JNI基础篇_第1张图片

下载完成后并配置,如图:
MAC环境下使用AS生成so包总结 --- JNI基础篇_第2张图片
NDK下载完成后最好将~/.bash_profile也一起配置下,如图:
MAC环境下使用AS生成so包总结 --- JNI基础篇_第3张图片
环境配置完成后即可进行下一步了。

二、开始编写我们的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文件在不同的版本位置可能略有不同,根据自己版本去找就好~)
MAC环境下使用AS生成so包总结 --- JNI基础篇_第4张图片
看到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
.h文件生成命令
不出意外的话这时候main目录下已经生成了jni包和.h文件
MAC环境下使用AS生成so包总结 --- JNI基础篇_第5张图片
打开.h文件看下内容,大概瞄一眼,里面放的就是我们的Native方法
MAC环境下使用AS生成so包总结 --- JNI基础篇_第6张图片

四、创建.c文件

先不用管.h文件,接着继续在jni目录下创建一个c++文件,命名为我们之前定义的JniUtil.cpp(这里注意网上大多数创建的是c文件:如JniUtil.c,但是我的as版本默认创建出来的就是c++文件,无所谓了,只要注意下后缀名是一样的),接着把文件头和extern的内容粘过去并且改造下返回我们需要的东西,如:

#include 

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

直接粘贴改下配置就好,目录如图:
MAC环境下使用AS生成so包总结 --- JNI基础篇_第7张图片

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包目录如下图
MAC环境下使用AS生成so包总结 --- JNI基础篇_第8张图片
2、再接着在main目录下创建jniLibs包,并将刚生成lib文件夹下的文件粘贴进去,如图
MAC环境下使用AS生成so包总结 --- JNI基础篇_第9张图片
OK,到了这里就大功告成了。
3、接下来就可以在代码中尽情调用了。至于我们创建的jni目录已经用不到了,可以删了随便处理吧。有的人会奇怪怎么直接就用了,不是有一个System.loadLibrary()方法吗?哈哈~还记得我们在创建JniUtils类时就已经放在了static方法体里了吗。
MAC环境下使用AS生成so包总结 --- JNI基础篇_第10张图片

六、创建过程中可能遇到的问题。

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"
        }

你可能感兴趣的:(JNI,Android)