Android NDK开发,跨平台构建crypto++动态链接库并部署到Android Stdio中
一、基础
- 需要在AS中安装NDK、CMake,DDLB一般默认与AS一起安装。
- CMake与ndk-build不能同时使用(本文使用的是CMake)。
- CMake入门可以看这个:CMake 入门实战 | HaHack
- 安卓官网的部分资料: CMake | Android NDK | Android Developers (google.cn) 配置 CMake | Android 开发者 | Android Developers (google.cn)
二、如何新建项目支持C/C++
直接在AS新建项目时选择Native C++,这时项目会自动生成native-lib.cpp并配置好对应的CMakeLists.txt和build.gradle(:app)文件。可以仔细看看这两个文件中的配置,仔细理解,方便后面自己添加cpp源码时应该知道如何配置。
三、如何向已有的项目中添加新的C/C++文件
已有一个纯的Android项目,如何添加C++源代码并构建原生代码库。本质上是将第2节中AS帮我们做了的事情,我们自己做一下。
-
按照第二节的布置,自己新建cpp文件夹和CMakeLists.txt文件,并在cpp目录下创建源代码文件
-
配置CMake以将原生代码入库:在CMakeLists.txt中添加相应设置
#指定要求的最低版本 cmake_minimum_required(VERSION 3.10.2) #指定当前项目的名称 project("ndksecondtry") #将native-lib.cpp加入库,参数一是名称(全局唯一即可,一般是name-lib,参数2表示共享库,参数3指定目标cpp文件) add_library( native-lib SHARED src/main/cpp/native-lib.cpp) #specifies the directory that the cpp.head file would be include_directories(src/main/cpp/include/) #use the NDK library find_library(log-lib log) #将native-lib和log-lib连接起来 target_link_libraries(native-lib ${log-lib})
-
提供CMake或者ndk-build脚本文件的路径以配置Gradle,以将源代码导入Android项目并将SO库打包到应用(修改gradle之后要Sync Project)
//对比第2节中的gradle代码,可以发现主要修改三个部分 //在android下,compileSdk 31下面添加 buildToolsVersion "31.0.0" //在android.defaultConfig中加 externalNativeBuild{ cmake { cppFlags '' //指定cpp的版本 abiFilters "arm64-v8a" //指定要构建的.so的abi类型 } } ndk { abiFilters "arm64-v8a" //指定要打包的abi下的.so } //在android中加 externalNativeBuild{ cmake{ path = file("CMakeLists.txt")//注意我的CMakeLists就在app路径下,如果其他路径需要指明 } }
-
在native-lib.cpp文件中写下具体的jni代码,并在项目中进行加载调用。
//我专门写了一个类来加载native代码,需要用的时候直接调用NDKHelper().getSumFromJNI() class NDKHelper { companion object{ init { System.loadLibrary("native-lib") } } external fun getSumFromJNI(a: Int, b: Int ): Int//声明这个函数 }
//native-lib.cpp #include <jni.h> #include <string> #include <cstdio> #include <cstdlib> using namespace std; //函数名是固定格式的 extern "C" JNIEXPORT jint JNICALL Java_com_example_ndksecondtry_NDKHelper_getSumFromJNI(JNIEnv *env, jobject thiz, jint a, jint b) { return a+b; }
AS生成的.so文件可以拿出来给其他项目用(可以去app\build\outputs\apk\debug
下将.apk文件改为.zip并解压,拿出.so)。
四、利用gcc生成第三方so文件(无法在AS中使用)
- 生成一个demo的so文件:
- 打开C:\MinGW\msys\1.0\msys.bat,出现类似于cmd的界面(如果没有找到,就打开MinGW Installer去安装)。
- 将路径转到目标目录下:cd…(我的目标文件下有一个hello.cpp)
- 输入:g++ -fPIC -shared -o libhello.so hello.cpp,会在当前目录下生成libhello.so
- 给hello写一个头文件,再写一个测试文件
- g++ -o test test.cpp -L -m libhello.so 会生成一个a.exe文件(前一个-o test是为了指定输出文件的名称)
- 生成crypto++的so文件
- 打开C:\MinGW\msys\1.0下的msys.bat,出现类似于cmd的界面。
- 将路径转到目标目录下:cd…/cryptopp850
- make (该步生成的静态库)
- make libcryptopp.so (执行该步生成动态库)
remark:gcc生成的.so文件在命令下可以与test.cpp(自己调用.so中的函数接口写的测试程序)一起运行。但是该方法只能生成一个.so文件,而安卓要求为不同类型的cpu配置不同的.so(指令集不一样),所以这样生成的.so文件不能在AS中使用。经过尝试,我们发现可以在g++编译时指定好ABI,并且可以在对应型号的机子上运行,但是这样生成的.so会部署到AS中会出现运行时错误(由于没有调用NDK的工具链,少了一些针对Android的FLAG,没有进一步尝试了)。
五、使用VS生成.so文件并导入AS
利用vs为不同的ABI生成对应的.so文件
- 安装vs,安装的时候在工作负载中要选“使用C++的移动开发”
- 创建VS项目,选择创建“动态共享库(选择为安卓平台生成动态共享库的项目)”,创建结束后写一个.cpp源码和.h文件
- 右击项目,属性,选择“常规”,设置平台工具集:Clang 5.0; 目标API级别:android 27(目前VS中的最高级别); STL的使用:LLVM libc++ 静态库(c++_static)
- 右击项目,生成。(注意在release和debug下的生成貌似Android都能用)
- 在ARM,ARM64,X86,X64下分别执行3.4步,即可为不同的ABI生成不同的.so文件
5.1 将VS生成的.so文件导入AS
-
在当前模块目录下(app)新建文件夹libs用于存放.so 文件。(有的说可以去src/main/下新建文件夹jniLibs来存放.so 文件,并在CMakeLists.txt中指明路径就行,但是我试了不行)
-
在libs下建立四个文件夹:arm64-v8a、armeabi-v7a、x86、x86_64,并将前面生成的.so文件直接复制到对应的文件夹下
-
在/src/main/cpp下新建文件夹include,并将.so库中用到的头文件全部放到该文件夹下
-
修改CMakeLists.txt文件,gradle可以不用改
#与第2节和第3节中的CMake文件进行对比,主要加上下面的代码(其余已有的部分保持不变) #指明头文件放的位置 include_directories(src/main/cpp/include/) add_library( add-lib SHARED IMPORTED) set_target_properties( add-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libcryptoGenerate.so) #use the NDK library find_library(log-lib log) target_link_libraries(native-lib add-lib ${log-lib})#加上add-lib
-
在native-lib.cpp中调用
//这个写法和上面一致 class NDKHelper { companion object{ init { System.loadLibrary("native-lib") } } external fun add(a:Int, b:Int): Int }
#include <jni.h> #include <string> #include <cstdio> #include <cstdlib> #include <add.h>//注意加上头文件 using namespace std; extern "C" JNIEXPORT jint JNICALL Java_com_example_ndksecondtry_NDKHelper_add(JNIEnv *env, jobject thiz, jint a, jint b) { int c = add(a,b); return c; }
六、生成Crypto++加密动态连接库(.so)
由于crypto++工程较大,文件较多,直接用前面的几个方法都不适用(归根结底是需要写makefile,主要是编译规则和依赖关系)。makefile有多种,构建工具也很多,如GUNmake,nmake,qmake,makepp,ninja等,不同的工具需要编写的makefile也不尽相同,面对的平台也不同。为了实现跨平台开发,我们选cmake。cmake可以写一个适用于各种平台的CMakeLists.txt,在这个文档中只用指定项目文件的编译规则,无需考虑平台和ABI。然后调用cmake命令可以为不同的平台、不同ABI、不同的make工具生成不同类型的makefile,再由对应的make工具进行编译。
6.1 利用AS生成指定ABI下的.so文件
Android的NDK通过工具链文件支持CMake,其底层调用的make工具是ninja,C++编译工具是clang。为了实现在AS中成功编译出共享库,我们需要写CMakeLists文件。我在github上找到了一份(链接CMake files for Crypto++ project )。注意,crypto++官方不支持Cmake。
-
修改
CMakelists.txt
。可能是因为这个CMakeLists写的比较早了,我用的crypto++源码是最新的(cryptopp850),所以有些地方需要改动。- 源码中的
TestPrograms
文件夹下的代码文件名后缀是.cxx
,而CMakeLists
中写的是.cpp
,需要手动改一下。 - 如果想让
CMakeLists.txt
的某些信息在控制台显示,可以用message()
并将输出类型指定为SEND_ERROR
。详见message — CMake 3.21.2 Documentation
- 源码中的
-
在
src/main/cpp
下新建文件夹crptopp
和include
,将crypto++的所有源文件都考至cryptopp
下,并将步骤1中修改好的CMakeLists.txt
也考至该目录;将crypto++中的所有.h
头文件都考至include
下。 -
在项目的
app
目录下新建一个顶层的CMakeLists.txt文件,内容如下:cmake_minimum_required(VERSION 3.10.2) project("ndkfifthtry") #关联另一个CMake文件 #设置目标CMake项目的路径 set(cryptopp_src_DIR src/main/cpp/cryptopp) #将目标CMake指定为依赖 add_subdirectory( ${cryptopp_src_DIR}#directory of cmake file ) #specifies the directory that the cpp.head file would be include_directories(src/main/cpp/include/) add_library(native-lib SHARED src/main/cpp/native-lib.cpp) #use the NDK library find_library(log-lib log) target_link_libraries(native-lib ${log-lib})
-
在
app/build.gradle
中写好配置(和第三节的步骤3一样,但是ABI可以不指定,不指定的话默认生成4中ABI下的),在cpp
目录下创建C++源文件native-lib.cpp
,内容如下(另外需要向第二节一样在Activity中通过JNI进行调用):#include <jni.h> #include <stdlib.h> #include "aes.h" using namespace std; using namespace CryptoPP; extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndkfifthtry_NDKHelper_cryptoString(JNIEnv *env, jobject thiz, jstring s) { string a = to_string(AES::BLOCKSIZE); return env->NewStringUTF(a.c_str()); }
-
sync并构建,运行之后,可以在
NDKFifthTry/app/build/intermediates/cmake/debug/obj
目录下或者看到四种不同的ABI和对应的.so文件。可以拷贝出来在其他项目中调用啦。
6.2 直接利用CMake和ninja生成指定ABI的.so文件
根据AS 开发官网说明——从命令行调用 CMake,可以在调用CMake在Android Stdio之外生成ninja项目。之后调用ninja自然就可以构建处动态链接库了。
- 类似6.1节的步骤1进行修改,另外,我们将CMakeLists.txt中的
option(BUILD_TESTING "Build library tests" ON)
改为option(BUILD_TESTING "Build library tests" OFF)
。并将该文件放到cryptopp源码中。 - 在
ryptopp
目录下新建一个文件夹cryptopp_build
用做CMake的输出路径。打开cmd或者PowerShell(确保cmake和ninja已经加入环境变量了),并转到cryptopp_build
路径下。 - 输入命令
cmake .. -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30 -DANDROID_NDK=C:\Users\user01\AppData\Local\Android\Sdk\ndk\21.4.7075529 -DCMAKE_TOOLCHAIN_FILE=C:\Users\user01\AppData\Local\Android\Sdk\ndk\21.4.7075529\build\cmake\android.toolchain.cmake -G Ninja -DCMAKE_MAKE_PROGRAM=D:\Microsoft\ninja-win\ninja.exe
,开始构建(命令参考AS 开发官网说明)。可以看到该目录下生成了build.ninja
等文件。 - 调用命令:
ninja
,将在当前文件路径下生成libcryptopp.so
和libcryptopp.a
文件。这个.so文件就可以直接用在aem64-v8a
的架构下。其他ABI可以对应生成。
七、AS调用libcryptopp.so
库
-
新建一个Native C++项目,将四种ABI对应的.so文件放到
app/libs
目录下(也可以只放其中一些ABI的,此时需要在app/build.gradle
中指定ABI类型),在app/src/main/cpp
下新建文件夹include
,并将crypto++中所有的头文件放进去。 -
修改CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2) project("encryptofile01") add_library(cryptopp-lib SHARED IMPORTED) set_target_properties(cryptopp-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/libcryptopp.so)#这里必须是绝对路径 include_directories(include/) add_library(encryptofile01 SHARED native-lib.cpp) find_library(log-lib log) target_link_libraries(encryptofile01 cryptopp-lib ${log-lib})
-
在
native-lib
中调用加密方法即可。