通过CMake来进行ndk开发之补充篇

2,795 阅读5分钟

前言

 之前有一篇的文章介绍了通过CMake来进行Android NDK开发的入门文章。写那篇文章的时候由于内容较多,  
 所以有的内容没有细说,就一笔带过了。这篇文章算是一个小小的补充。所以,建议看这篇文章的小伙伴,先阅读之前的博文。  

使用CMake来进行Android NDK 开发


内容

使用CMake来进行Android NDK 开发这篇博文说道,向现有的项目里添加原生代
码的步骤分为三大步
1. 创建新的原生源文件。
2. 创建CMake构建脚本。
3. 将Gradle关联到你的原生库。
第一步比较简单就不说了,第二步对CMake构建脚本即CMakeLists.txt文件的构建
说明,先看一张图:
这里写图片描述

这张图片是系统自动给我们生成的CMakeLists.txt内容,当时我们只说明了add_library命令的作用,
那现在我们看一下find_library 代表什么,根据Google文档,find_library表示定位NDK库,图片上关于这个命令的描述说明还挺好理解的,
查找一个特殊的预构建库并且把其存储路径设置为一个变量。那这个命令里面第一个值是路径变量的名称,
第二个就是你想要CMake去定位的库的具体名字。我们为什么要定位一个NDK库呢?是这样的,Android平台上有一些系统的预构建库,这些系统的库不需要再构建,打包到apk包中的。直接指定其的路径就可以使用了。那现在用了find_library就可以直接使用系统的预构建库了么?还不行,我们还差一个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} )

如图这样设置后,native-lib库中就可以调用log库的函数了。
其实在我们创建支持C/C++的新项目时,系统就自动的创建了CMakeLists.txt文件,里面有默认的定位 Android 特定的日志支持库

添加其他的预构建库

刚刚说了是添加系统的预构建库,使用了find_library命令,那添加其他的非系统的预构建库,还是使用find_library 么?不是的了,是使用add_library。咦?之前我们指定要构建的库就是用这个命令啊,现在添加其他的预构建库也用这个命令哇~~确实使用这个命令,不过呢,还是有区别的。在使用add_libraray 添加其他的预构建库时需要,用IMPORTED这个作为标志。格式如下:

add_library( imported-lib
             SHARED
             IMPORTED )

然后,我们需要使用 set_target_properties() 命令指定库的路径,如下所示。 某些库为特定的 CPU 架构提供了单独的软件包,并将其组织到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让你仅使用所需的库版本。要向 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命令文档cmake命令文档

将Gradle关联到原生仓库的第二种方式

第二步说完,我们现在看第三步,之前的博文中介绍了,gradle关联原生仓库的第一种方式。是通过as的快捷键来实现的,右键点击你想要关联到原生库的模块(例如 app 模块),
并从菜单中选择 Link C++ Project with Gradle。
BuildSystem选择CMake,projectpath就是CMakeLists.txt文件的路径。点击ok完成。 如下图:
这里写图片描述
这样呢,就会在应用模块下的build.gradle文件中,android闭包下出现:

externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }

这样的语块。
那我们的第二种方式就是手动的来修改build.gradle文件夹,来实现关联的目的。时光倒退到向现有的项目里添加原生代码的步骤三大步中的第二步。我们在第二步完成之后,不通过as 的快捷键,我们直接在build.gradle的文件写入

externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }

也是可以的。。。当然,可以是可以,我们这里肯定不止这个。我们刚刚有到上面出现代码是在android闭包内的。事实上呢,在build.gradle文件的defaultConfig块中也可以设置externalNativeBuild {}块,为 CMake 指定可选参数和标志。先上代码:

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 ndkBuild {}
      cmake {

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

        // Sets optional flags for the C compiler.
        cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"

        // 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 {...}
  }
}

这个代码是我从官网山得到sample,我们看到defaultConfig闭包内是存在了externalNativeBuild {},并且配置了一些属性。arguments “-DANDROID_ARM_NEON=TRUE”, “-DANDROID_TOOLCHAIN=clang”

"-DANDROID_ARM_NEON=TRUE"表示:是否让CMake构建原生库时支持“NEON”,默认的是不支持。  
DANDROID_TOOLCHAIN=clang" 这个参数不知道大家还不记得这个图  

   这是当时创建支持原生代码的新项目时,我们见到界面,C++ Standard这个复选框,我们选择  
   Toolchain Default选项。这与DANDROID_TOOLCHAIN=clang意思一样,支持CMake构建原生库的意思。

cFlags “-D_EXAMPLE_C_FLAG1”, “-D_EXAMPLE_C_FLAG2”
cppFlags “-D__STDC_FORMAT_MACROS”
这两个配置,虽说能根据英文翻译出个大概,但是有点不确定含义,没用过。从网上也没搜到满意的回答。如果知道的大佬,可以说明一下,(^__^) 嘻嘻……


接着看上面的代码,我们发现externalNativeBuild {}块,不仅仅出现在defaultConfig闭包内,也出现在了,productFlavor块中。这是什么情况!!??仔细想想,其实,也好理解,productFlavor闭包中其他的渠道之前都是可以重写defaultConfig内的属性的。所以他自然也可以了区分各个渠道,重写externalNativeBuild {}内的内容哇。这样,每个渠道的包就有他自己的属性配置。多和谐~~ 上面的代码中我们发现了,targets属性,这个是面对这种情况的。如果你的 CMake 或 ndk-build 项目定义多个原生库,你可以使用 targets 属性仅为给定渠道构建和打包这些库中的一部分。

好了,补充内容补充的差不多了,在国庆放假前一天静下心来补充这个,真是难熬/(ㄒoㄒ)/~~。。
最后再补充一个,之前博文在最后展示打包进入apk的so文件的时候,给出了下面的一张图:
这里写图片描述

我们看到了x86,armeabi、armeabi-v7a,mips只有这四个ABI。你们跑的时候可能出现了:
这里写图片描述

7个ABI,看一些size,apk大小的45.9%。多么可怕,这是不允许的。而且也不需要适配这么多的ABI。所以我们需要指定ABI。如何做呢?在build.gradle文件的defaultConfig闭包下加入如下语句即可:

ndk {

      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',

    }

上诉语句就表明会打出对应与’x86’, ‘x86_64’, ‘armeabi’, ‘armeabi-v7a’,这四个ABI的库。

好了,补充篇到此结束,安心过节啦~

最后列出与此篇博文对应的上篇博文地址:
使用CMake来进行Android NDK 开发
PalmRead