Android Studio添加原生库并自动构建

[时间:2017-09] [状态:Open]
[关键词:Android,Android Studio,gradle,native,c,c++,cmake,原生开发,ndk-build]

0 引言

最近在工作中遇到了升级Android Studio 2.3.3稳定版之后,无法编译jar包的问题。之后寻找AS文档-探索 Android Studio发现。可以通过AS创建和编译jar包,顺便看到支持原生开发,可以直接在AS中调试c/c++代码,这是非常不错的功能。终于可以摆脱纯打日志的开发环境了。

本系列文章也就因此而出现。我希望阅读文本文之后,大家能够基本了解如何使用新版的Android Studio开发原生引用。
这里说明一点,本文是参考谷歌的Android Studio官网来的,也可以认为是一种翻译版。可以访问的话你可以直接查看对应内容。

这是第二篇:现有AS项目中添加原生库。

1 环境准备

请参考本系列的第一篇:原生库示例创建及验证中的要求,按照和更新Android Studio。本文需要事先创建一个AS工程,可以不支持原生库。
比如我使用默认的blank Activity创建了一个名为ExistingApplication的项目。

2 主要步骤

如果需要在现有项目中添加原生代码,需要参考下面步骤:(下列步骤推荐用于添加全新的库)

  1. 创建native源文件,并添加到工程中;
    如果你仅仅是导入预构建的原生库或者源码可以跳过此步骤。
  2. 创建一个CMake构建脚本,用于构建你的源代码并创建库。如果是导入或者链接预构建的库也需要该脚本。
    如果你的原生库已经使用CMakeLists.txt构建脚本,或者使用ndk-build构建并包含Android.mk构建脚本,可以跳过此步骤。
  3. 将Gradle与原生库链接起来,只提供下CMake或ndk-build脚本文件所在路径即可。
    Gradle会自动使用构建脚本导入源代码,并将其以SO文件的形式打包到APK中。

通过上面三个步骤,你就可以在AS中使用JNI框架调试和验证原生代码了。

下面分开介绍下各个步骤。

步骤一:创建native源文件,并添加到工程中

在app模块中创建cpp目录用于保存新的原生代码,步骤如下:

  1. 切换到Project Files视图,并找到模块>src,在main目录上右键点击,选择新建>目录。
  2. 输入一个目录名字(比如输出cpp),并点击确定。
  3. 右键点击你刚创建的目录,并选择新建 > C/C++ 源文件。
  4. 输入要创建的源文件的名字,比如native_lib。
  5. 从下拉列表框中,选择源文件的扩展名,比如.cpp。
  6. 如果需要创建头文件,勾选对应的复选框。点击确定即可。

步骤二:创建CMake构建脚本

CMake构建脚本是一个名为CMakeLists.txt的纯文本文件。本部分仅包含在CMake构建本地库时常用的基础命令,更详细的介绍建议参考CMake官方文档

请注意:如果使用ndk-build,无需提供CMake构建脚本。你可以通过提供你的Android.mk文件所在的目录将其与Gradle链接起来。

以下步骤是如何创建CMake构建脚本:

  1. 打开IDE左侧的工程面板,并选择Project视图。
  2. 右键点击模块的根目录,并选择新建 > 文件。
  3. 输入CMakeLists.txt作为文件名,并点击确定。

创建之后,你就可以在构建脚本中添加CMake命令了。为了使用CMake构建原生库,需要在构建脚本中添加cmake_minimum_required()add_library()命令:(如下所示)

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # 指定库的名称
             native-lib

             # 设置库类型为共享库
             SHARED

             # 提供源码所在的路径
             src/main/cpp/native-lib.cpp )

在构建脚本中使用add_library()添加源文件或库时,AS会在Project视图中自动显示对应的头文件。然而,为了让CMake可以在编译时定位头文件的目录,需要在CMake构建脚本中添加include_directories()命令,并指定头文件所在目录。

add_library(...)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

CMake用于命名库文件的格式如下:liblibrary-name.so
例如,如果你在构建脚本中指定共享库的名字为"native-lib",CMkae将创建一个名为libnative-lib.so的文件。但在Java代码载入库时,需要使用你在CMake构建脚本中指定的名字:

static {
    System.loadLibrary("native-lib");
}

添加NDK APIs

Android NDK中提供了一系列有用的原生API及库。你可以在CMakeLists.txt脚本文件中使用这些API,只需要包含NDK库即可。

预构建的NDK库已经在安卓平台上存在,你不需要将其构建或打包到你的APK中。由于NDK库是CMake搜索路径的一部分,你也无须指定你的NDK安装目录,你只需要提供在你的原生库中所依赖的ndk名称即可。
在你的CMake构建脚本中添加find_library()命令,用于定位NDK库所在位置,并将其存储在变量中。你可以构建脚本的其他部分使用该变量来引用NDK库。例如:下面示例代码中定位了安卓相关的log支持库,并将其路径保存在log-lib中:

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 )

为了让你的原生库可以调用log库中的函数,你需要使用target_link_libraries()来链接该库。

find_library(...)

# 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} )

NDK中包含一些以源码形式存在的库,如果需要的话,你需要将其编译为原生库,通过在CMake构建脚本中添加相应的add_library()命令即可。其中你可以使用ANDROID_NDK的环境变量,通常Android Studio会自动定义该变量。

下面命令会让CMake构建android_native_app_glue.c(该文件主要管理NativeActivity的生命周期时间和触屏输入)到一个静态库中,并将其链接到native-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} )

添加其他预构建的库

添加预构建库和使用CMake构建一个原生库类似。不过你需要使用IMPORTED标志来告知CMake:你仅需要将该库导入到你的工程中:

add_library( imported-lib
             SHARED
             IMPORTED )

你需要指定库所在的路径,使用set_target_properties()命令:
一些库针对不同的CPU架构或ABI(应用程序二进制接口)提供了不同的版本,并将其保存到单独目录下。这种方法可以帮助库仅加载需要版本的库。如果需要在CMake构建脚本中添加多ABI版本的库,不需要为每个版本的库编写对应的命令,只需要使用ANDROID_ABI环境变量即可。这个变量中包含了NDK支持的默认ABI列表,或者包含一个你在Gradle中手工配置的ABI列表。示例如下:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

CMake中头文件路径的添加,可以通过include_directories()命令实现,这样就可包含你自己的头文件了:

include_directories( imported-lib/include/ )

为了将预构建的库连接到你自己的原生库中,需要在CMake构建脚本中将其添加到target_link_libraries()命令中:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

为了将预构建的库打包到你的APK中,你需要手工配置Gradle,在sourceSets块中包含你的.so文件所在路径。在构建APK之后,你可以使用APK Analyzer验证Gradle究竟打包了那个库。

包含其他CMake工程

如果你想构建多个CMake工程并将其输出导入到你的Android工程中,你可以将CMakeLists.txt作为顶级CMake构建脚本(同时在Gradle中指定该文件),然后将需添加的CMake工程作为该脚本的依赖项。下面的顶级CMake构建脚本使用add_subdirectory()命令来指定另一个CMakeLists.txt文件作为构建依赖项,然后将其输出链接到当前工程中,这样工作机制和预构建库类似。

# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )

# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                  ${lib_src_DIR}

                  # Specifies the directory for the build outputs.
                  ${lib_build_DIR} )

# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )

# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )

步骤三:Gradle链接原生库

为了在Gradle中了链接你的原生库,你需要提供你的CMake或nkd-build脚本文件的路径。配置好之后,在实际构建APP时,Gradle会将CMake/ndk-build作为依赖项,并将共享库打包到APK中。Gradle也会参考该构建脚本,将源文件加载到Android Studio工程,以便你可以直接访问。如果你的native源代码没有对应的CMake构建脚本,你需要参考上一个步骤创建一个。
Android工程总的每个module只能链接一个CMake/ndk-build脚本文件。因此,假如你想同时构建多个CMake工程并打包其输出,你可用一个CMakeLists.txt作为顶层的CMake构建脚本(并在Gradle中指定该文件路径),然后将其他CMake工程作为该构建脚本的依赖项。同样的,如果你使用ndk-build,你可以在顶层的Android.mk脚本文件中包含其他Makefile。
当你在Gradle中配置好native项目之后,Android Studio将会更新Project面板来显示你的源代码文件和原生库(cpp),在External Build Files中显示外部构建脚本。

使用Android Studio UI配置

你可以使用Android Studio UI来实现将Gradle链接到CMake/ndk-build。

  1. 打开IDE左侧的Project面板,并选择Android视图。
  2. 右键点击需要链接原生库的module,比如app module,然后从菜单中选在Link C++ Project with Gradle。你将看到类似图1的对话框。
  3. 从下拉列表框中选择CMake或ndk-build。
    1. 如果使用CMake,在Project Path右侧可以选择你需要添加的CMakeLists.txt脚本文件。
    2. 如果使用ndk-build, 使用Project Patch右侧的选择对话框选择Android.mk脚本文件。如果Application.mk位于和Android.mk相同的目录,也会被自动加载。

图1 使用Android Studio对话框链接外部C++工程

  1. 点击确定即可。完成配置。

手动配置Gradle

手动配置Gradle,需要在你的module层的build.gradle文件中添加externalNativeBuild块,并将其使用cmakendkBuild块配置。

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }

  // If you want Gradle to package prebuilt native libraries
  // with your APK, modify the default source set configuration
  // to include the directory of your prebuilt .so files as follows.
  sourceSets {
      main {
          jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
      }
  }
}

指定可选配置项

你可以为CMake或ndk-build指定可选参数和标志,通过配置你的module层的build.gradle文件中的defaultConfig块下的externalNativeBuild子块。和defaultConfig块中其它属性类似,你可以在你的产品级构建配置中覆盖这些属性。
例如,如果你的CMake或ndk-build工程定义了多个原生库,你可以使用target属性来为特定产品构建和打包一个这些库的子集。下面代码展示了如何通过配置该属性实现上述需求:

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets optional flags for the C compiler.
        cFlags "-fexceptions", "-frtti"

        // Sets a flag to enable format macro constants for the C++ compiler.
        cppFlags "-D__STDC_FORMAT_MACROS"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries to build and package for this
          // product flavor. If you don't configure this property, Gradle
          // builds and packages all shared object libraries that you define
          // in your CMake or ndk-build project.
          targets "native-lib-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid"
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

了解更多相关信息,请参考Configure Build Variants。对于CMake支持的参数属性,请参考CMake Variables

指定ABIs

默认情况下,Gradle会按照NDK支持的ABI将你的原生库编译为独立的.so文件。并将它们全部打包到APK中。如果你希望Gradle仅构成和打包指定ABI版本的原生库,你就需要在module层的build.gradle文件中指定ndk.abiFilters标志,如下所示:

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your APK.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

如果你希望减小APK的大小,请参考基于ABI的多APK打包配置

3 小结及后续

本文作为Android创建的第二篇,整体比较简单,内容主要是翻译部分,整理并介绍了源代码和gradle、CMake构建脚本。接下来我们将关注如何使用AS构建JAR包。

posted @ 2017-10-31 16:52  Tocy  阅读(3360)  评论(0编辑  收藏  举报