Android NDK 编译原理

一、gradle

gradle是Android Studio指定的自动编译脚本,它有点像 Linux C开发中的 Makefile。下面我来看下 Android Studio 是如何使用 Gradle 的。

在 Android 项目中,包括三个 gradle 文件, 它分分别是:

  • build.gradle是Android 项目的顶级脚本;
  • setting.gradle里记录了 Android 项目里都包括了哪些子项目;
  • app/build.gradle里记录了每个子模块应该如何进行编译。
     

下面我们详细介绍一下每个文件的作用。

1、build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

这个文件是整个项目的gradle基础配置文件,默认有三个“节点”,分别是 buildscript、allprojects 和 task clean。

  • buildscript下的第一个子节点”repositories”是声明仓库源,默认是jcenter。jcenter可以理解成是一个新的中央远程仓库。第二个子节点是dependencies声明Android gradle plugin的版本。
  • allprojects下是声明了所有project默认的仓库源。
  • task clean声明了一个叫clean的作务,该任务类型是Delete(也可以是Copy),就是每当修改settings.gradle文件后点击同步,就会删除rootProject.buildDir下的文件。

注意 :我们看到 allprojects 中也同样声明了jcenter,它是不是与buildscript重复了呢?其实allprojects中设置的jcenter作用域与buildscript是完全不一样的,allprojects设置的是project默认的仓库源,buildscript设置的是gradle使用的源。

2、setting.gradle

include ':app'

如果在project里添加了子项目(Module),按理需要在settings.gradle里添加相应子项目名称。

3、app/build.gradle

//声名使用的是 android gradle 插件
apply plugin: 'com.android.application'

android {
    //编译 SDK 我本号
    compileSdkVersion 24
    //build 工具版本号 
    buildToolsVersion "25.0.0"
    //默认配置参数
    defaultConfig {
        //应用的包名
        applicationId "com.example.garrylea.testjni"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            //是否进行混淆
            minifyEnabled false
            // 混淆文件的位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    //编译libs目录下的所有jar包
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}

这里需要说明几点:

  • apply plugin: 指明 gradle wrapper应该使用那个 gradle 插件。一般情况下我们都应该使用 android 插件。
  • buildToolsVersion: 指明你本地安装的 build tools 版本。很多人导入新的第三方库,失败的原因之一是build version的版本不对导致的。可以手动更改成你本地已有的版本或者打开 SDK Manager 去下载对应版本。
  • applicationId: 代表应用的包名。

二、交叉编译

由于目标环境中各种资源都相对有限,所以很难直接进行本地编译,所以需要交叉编译。

交叉工具链是指可以生成目标环境可运行代码的工具集。我们知道,在Linux系统中的编译工具链包括以下一些工具, 编译器gcc, 链接器ld, 归档工具ar等等,如果在 Linux 环境中交叉编译 Android 系统环境可运行的代码,是不能直接使用Linux下的编译工具的,而应该使用 Android NDK 里的工具链,它们对应的名子为 arm-linux-android-gcc, arm-linux-android-ld等等。
交叉编译工具的命名规则 arch-[vendor]-kernel-system-toolname。它由五部分组成,下面分别解释一下每部分的含义:

arch,指的是CPU架构, 一般包括如下几种架构: arm, mips, powerpc, x86, x86_64。
verdor, 一般指的是生产厂商, 如果没有生产厂商可以为空。
kernel, 指的目标环境使用的 kernel,以android为例,它使用的是 linux内核,所以在这部分会填写为linux。
system, 指的是那个系统, 如androideabi, android
toolname: 指的是 gcc, ld, ar等。

所以我们可以看到 android 的编译工具的名子会写成 arm-linux-andirod-gcc。

在做交叉编译时,我们常会在脚本中定义一些环境变量以方便我们命名用,常用的环境变量如下:

PREFIX: 指明交叉编译后输出的目录。
ARCH: 指明交叉编译后输出的CPU架构。
CROSS-PREFIX:指明交叉编译前辍 arch-vender-kernel-system
SYSROOT: 指明交叉编译目标机器的头文件和库文件目录
TOOLCHAIN: 指明交叉编译工具链的位置。
PLATFROM: 指明交叉编译时使用的是哪个版本的的头文件和库文件。它是 SYSROOT的一部分。
ANDROID_NDK: 指明 Android NDK 所在目录。

举例,以ffmpeg的交叉编译为例,我们可以来看一下如果生成交叉编译的Makefile。

通过设置configure 参数来生成交叉编译的 Makefile 文件
—arch 指定架构
—cross-prefix 交叉编译工具链前辍
—sys root 交叉编译树的根

./configure –target-os=linux –arch=arm –enable-cross-compile –cross-prefix=arm-linux-androideabi- –sysroot=~Library/Android/sdk/ndk-bundle/platforms/android-9/arch-arm
执行后的结果如下:

install prefix            /usr/local
source path               .
C compiler                arm-linux-androideabi-gcc
C library                 bionic
host C compiler           gcc
host C library            
ARCH                      arm (armv5te)

三、NDK编译环境

上面的方式比较原始,技术门槛稍高,对之前没有做过嵌入式开发的同学来说难度还是蛮大的。

为了解决这个问题,Google 提供了一系列脚本工具,以方便大家做 Android NDK方面的开发,其中最重要的是 ndk-build 脚本。有了这些脚本,开发者就不必再定义各种环境变量,只需要提供两个 Makefile 片段指明要编译哪些C/C++文件,生成哪个目标环境的程序就好了。 ndk-build脚本工具会自动检测各种环境变量、目标环境编译器等,最终完成交叉编译。
先让我们来看一下 ndk-build 吧。通过查看ndk-build源码我们可以看到,它执行的是类似于 $GNUMAKE -f /build/core/build-local.mk 这样的命令,实际就是自动检测并设置环境变量。我执行 ndk-build NDK_LOG=1 命令,会看到下面的信息:

Android NDK: NDK installation path auto-detected: 
Android NDK: GNU Make version 3.81 detected
Android NDK: Host OS was auto-detected: darwin
Android NDK: Host operating system detected: darwin
Android NDK: Host CPU was auto-detected: x86
Android NDK: HOST_TAG set to darwin-x86
Android NDK: Host tools prebuilt directory: 
**#这里检测到编译器地址**
~/Library/Android/sdk/ndk-bundle/prebuilt/darwin-x86_64/bin 
Android NDK: Found platform root directory: 
**#这里检测到 platforms 目录地址** 
~/Library/Android/sdk/ndk-bundle/platforms 
**#下面列出了所有支持的 platform**
Android NDK: Found supported platforms: android-12 android-13 android-14 android-15 android-16 android-17 android-18 android-19 android-21 android-22 android-23 android-24 android-26 android-9 
**#列出支持的目标环境** 
Android NDK: PLATFORM android-12 supports: arm mips x86 
**#下面是每个目标环境的的环境树**
Android NDK:   ABI arm sysroot is: /Users/lichao/Library/Android/sdk/ndk-bundle/platforms/android-12/arch-arm
Android NDK:   ABI mips sysroot is: /Users/lichao/Library/Android/sdk/ndk-bundle/platforms/android-12/arch-mips
Android NDK:   ABI x86 sysroot is: /Users/lichao/Library/Android/sdk/ndk-bundle/platforms/android-12/arch-x86 

通过上面的分析我们应该已经很清楚 ndk-build 主要作什么事情了。既然 Google已经给我们提供了这么方便的交叉编译工具,那作为开发者我们还需要做哪些事情呢?
作为开发者的我们还需要做下面两件事儿:

  • 编写 C/C++ 代码。
  • 编写 Android.mk 和 Application.mk 两个 Makefile 片段。

下面我们就来看一下两个Makefile片段 Android.mk 和 Application.mk 是做什么用的。

  • Android.mk: 目的是用于向构建系统描述源文件和共享库,它位于 $PROJECT/ni/目录中。
  • Application.mk: 目的是描述在你的应用程序中所需要的模块(即静态库或动态库),它也位于 $PROJECT/jni/ 目录中。

首先我们先来看一下 Android.mk的例子:

LOCAL_PATH       :=  $(call my-dir)
include              $(CLEAR_VARS)
LOCAL_MODULE     :=  hello_jni
LOCAL_SRC_FILES  :=  hello_jni.c
include              $(BUILD_SHARED_LIBRARY)

  • 在Android.mk中必须首先定义 LOCAL_PATH 变量,此变量表示源文件在开发树中的位置。构建系统提供的宏函数 my-dir 将返回当前目录(包含 Android.mk 文件本身的目录)的路径。
  • CLEAR_VARS 变量指向特殊 GNU Makefile,可为您清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。 请注意,它不会清除 LOCAL_PATH。此变量必须保留其值,因为系统在单一 GNU Make 执行环境(其中所有变量都是全局的)中解析所有构建控制文件。 在描述每个模块之前,必须声明(重新声明)此变量。
  • LOCAL_MODULE 变量将存储您要构建的模块的名称
  • LOCAL_SRC_FILES 枚举源文件,以空格分隔多个文件。LOCAL_SRC_FILES 变量必须包含要构建到模块中的 C 和/或 C++ 源文件列表。
  • 最后一行帮助系统将所有内容连接到一起。

我们再来看一下Application.mk的例子:

APP_PLATFORM := android-9
APP_STL      := gnustl_static
APP_CFLAGS   := -Wno-error=format-security
APP_ABI      := armeabi armeabi-v7a arm64-v8a
APP_OPTION   := release

APP_PLATFORM 变量包含目标 Android 平台的名称。NDK API 级别与 Android 版本对照表如下:

NDK 支持的 API 级别Android 版本
92.3 到 3.0.x
123.1.x
133.2
144.0 到 4.0.2
154.0.3 和 4.0.4
164.1 和 4.1.1
174.2 和 4.2.2
184.3
194.4
214.4W 和 5.0

APP_STL      默认情况下,NDK 构建系统为 Android 系统提供的最小 C++ 运行时库 (system/lib/libstdc++.so) 。 该指令可以让您在自己的应用中使用或链接的替代 C++ 实现。

APP_CFLAGS构建系统在仅构建 C++ 源文件时传递到编译器的一组 C++ 编译器标志。

您可以使用 APP_ABI 选择编译出不同的 ABI 目标环境代码。

可定义变量为 release 或 debug。在构建应用的模块时可使用它来更改优化级别。

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值