一、前言
在平时的Android开发的过程当中,我们可能会接触到需要要与底层交互的场景,例如对数据的加密和解密我们一般会将这部分工作放在jni层来做,这样减少了别人对我们APK反编译的威胁,在jni层我们再对so库进行加壳后更进一步的保证了我们核心东西的安全,现在大部分的开发都是用Android Studio了,本文简单的介绍一下在Android Studio中开发jni的方法。
在Android Studio 2.2及更高的版本中,推出了Cmake来进行Jni的开发,这个工具非常的方便,下面就介绍一下它的使用。
二、CMake的使用
1.环境的搭建
要使用CMake的功能,我们首先需要安装该插件,我们可以通过如下方式进行安装:
1. 在打开的项目中,从菜单栏选择 Tools > Android > SDK Manager。
1. 点击 SDK Tools 标签。
1. 选中 LLDB、CMake 和 NDK 旁的复选框,如图下图所示。
Cmake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。
LLDB:一种调试程序,Android Studio 使用它来调试原生代码。
NDK:是开发JNI少不了的,不管你是用ndk-build 还是CMake。
2.创建项目
我们来到创建项目页面,勾选对C++的支持,如下图所示:
接下来就和一直点next就行,但是到最后一个页面会出现一些选项需要我们选择,如下:
- C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
- Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
- Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
项目创建完成后,我们会看到如下目录结构:
在我们的工程目录下,系统为我们自动生成了一个cpp的文件夹,里面有一个native-lib.cpp的文件,该文件就是一个c++变现的native文件。并且项目的根目录下多了一个CMakeLists.txt文件,该文件就是我们的构建脚本,给CMake编译的时候使用的。
图左边是我们项目的build.gradle文件,里面多了两项设置,这两项设置是Gradle进行构建是对CMake的配置,在cppFlags中有两个参数,是因为我们创建项目的时候勾选了Exception Support和RTTI。
此时我们运行项目,我们就可以查看到页面上显示了从so文件中传递过来的Hello from C++。那么我们的so文件在什么位置了?
我们将项目切换为Project的方式显示,so的路径为:项目目录\app\build\intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main\lib\arm64-v8a\libnative-lib.so 这只是一个目录,系统默认为我们生成了好几种CPU架构的so文件,题外话,这个特别的重要,如果我们在arm的cpu下加载了一个x86的so库,从名字上我们看不出差别,但是系统会加载失败。
接下来我们就来查看下,我们生成的apk中是否将该so文件打进了我们的apk压缩包中,我们使用AS的Build下的Analyze apk功能选中我们Build目录下的outputs中的debug标签的apk,如下图所示:
可以看到我们的so文件已经打包进了apk当中,并且每一种架构都有,这样的好处是适配了多种cpu的架构,但是我们的apk体积也随之增加了。下面介绍如何指定只打包指定的CPU架构的so文件。
3.CMake脚本文件的配置
上面简单的介绍了我们如何创建一个项目,下面我们就来分析一下CMakeLists.txt脚本文件,系统的初始文件如下:
- 第一行设置一个CMake的最小版本,使其知道我们构建的特性,默认就行。
- add_library()函数用于添加我们创建的native代码依赖,例如(.cpp/.c)结尾的文件
add_library( # Specifies the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
例如这句代码,我们添加了一个native-lib的库,它在src/main/cpp/目录下的名字为native-lib.cpp,当我们编译完成后,打包apk中的lib目录下是,它的名字会变成libnative-lib.so
我们可以在程序中这样加载代码:
System.loadLibrary("native-lib");
- 为了确保CMake在编译的时候定位到我们的头文件,我们可以使用如下函数:
add_library(...)
# Specifies a path to native header files.
include_directories(src/main/cpp/include/)
- 当我们需要一些 Android NDK 提供的原生 API 和库。可以通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,就能使用这些 API 中的任意一种,甚至一些NDK库已经存在Android的平台上,我们无需再构建打包到APK中。我们就可以通过如下的代码来引用它们,并与我们的原生库进行关联
find_library( # Defines the name of the path variable that stores the
# location of the NDK library.
log-lib
# Specifies the name of the NDK library that
# CMake needs to locate.
log )
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the log library to the target library.
${log-lib} )
- 上面的方法我们是关联一个现成的库,我们还可以关联一个源代码,如下所示:
add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )
在开发的时候我们可能会用到一些第三方的so库,这是我们该如何关联它们了?
1.我们首先在项目的src路径下使用AS的功能,创建一个jni目录File-》new Folder-》jni Folder
目录结构如下:
2.然后我们在项目的build.gradle中添加jni的目录信息,如下所示:
sourceSets { main { jniLibs.srcDirs = ['src/main/jni'] } }
3.接下来在Cmakelists.txt脚本文件中引入如下代码:
#设置引入库的路径 set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/jni) add_library( # Sets the name of the library. observer SHARED IMPORTED#标示我们引入的是外部库,无需编译 ) #设置外部库的路径 set_target_properties( # Specifies the target library. observer PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/${ANDROID_ABI}/libobserver.so )
接下来我们就可以进行编译,使用analyze apk查看我们的apk,就可以看见我们的so文件一起被打包进去了。
上面我们提过,如果我们把每一种架构都打进我们的apk的话,会增加我们apk的大小,有些时候我们是不需要这样的,例如内置应用只需要内置的一款手机就行了,那么我们就需要进行如下配置,来让gradle只打包我们指定的架构的so
ndk {
abiFilters 'armeabi'
}
这里我们只指定了armeabi一种类型,当然可以指定好几种,看个人需求了。
注意:在上面我们提到了,我们自己创建.c或则.cpp文件进行开发的时候,我们需要将add_library()函数分开写,不应该写在一个里面,意思就是几个库函数就有几个这个函数,但是target_link_libraries()函数可以写在一起