1. 程式人生 > >Mastering Android NDK Build System

Mastering Android NDK Build System

This article is not a “Hello world!”-type tutorial for NDK. Although I will still provide a quick walk-through of the very basic knowledge of ndk-build, but it is not the focus of this article. Instead, I will summarize some very useful NDK techniques and tips I have been using in my projects. Hope those tips can be very useful for anyone who wants to build some practical projects rather than a toy project to learn NDK. So, the target readers are medium or advanced Android developers. The article contains two parts:

  • Part 1: ndk-build
    In this part, we will discuss how to flexibly use ndk-build to build your projects, and how to organize the file structure of your projects.
  • Part 2: standalone toolchain
    In part 2, the set up and usage of standalone toolchain will be discussed.

Table of Contents

1. Introduction

Android NDK (Native Development Kit) is a power tool for Android application developers who want efficient and high performance native code, or who has to deal with low-level hardware details (such as OpenGL, OpenCL and so on).

The Android NDK official documents (an online version) are kind of OK, if you have been working with NDK for a while. However, it is really not designed for someone just starting with Android NDK development. The problem with the official documents is that there is no emphasis, so that important information might be overlooked easily.

There are also many online tutorials and articles showing the basics of NDK and the usage of NDK building tools. However, the information are distributed everywhere. There is no single place discussing these topics and techniques in depth. Hopefully, this article will cover some of them.

2. Prerequisites

In this article, I make the following assumptions:

  • you know what NDK is;
  • you know C/C++;
  • you have already installed Android NDK on your computer. In my setting, I install NDK under D:\development\android-ndk-r10d. In later part of this article, I will call that path NDK_ROOT.
  • to avoid long path name when using ndk-build, I added NDK_ROOT to system PATH environment variable.

3. Basics of ndk-build

Of course, the first step of using the Android NDK is downloading the NDK installation package from Android Developer network. After installing the NDK package, these are what you got:

  • NDK_ROOT\ndk-build.cmd script;
  • documents under NDK_ROOT\docs;
  • toolchains and compilers;
  • source code for some native libraries;
  • some sample codes.

The sample codes could be very useful if you want to learn the basic set up and the syntax of the makefiles for NDK. Going through the sample code provided by NDK, you will find that most of the code samples assume that you are working on an Android application project, and will use NDK to build the JNI part of the Android application. That’s why you notice that all project put C/C++ source code and makefiles under a jni folder.

The following is a typical file structure of NDK samples:

+-- project_root
|   +-- jni
|       +-- Android.mk
|       +-- Application.mk
|       +-- main.c
|   +-- obj
|   +-- libs

As you can see, the jni directory is the heart of the whole NDK project, which contains C/C++ source code, two makefiles Android.mk and Application.mk. As will be discussed later, you will see that the C/C++ source code is not necessary to sit inside jni folder. Moreover, you don’t need to have exactly the same name for makefiles. But as a starting point, using Android.mk and Application.mk will be easiest way to go and can save you a lot of effort, unless you really don’t like the current names of the makefiles. By default, the ndk-build will try to locate

The other two folders obj and libs are generated by the NDK building system, and contains the intermediate files and final binary code, respectively.

The Android.mk and Application.mk are the most important makefiles for a NDK project. The Android.mk is more like a traditional makefile, defining source code, path to include header files, path for the linker to locate the libraries, module name, build type, and so on. The Application.mk defines Android application related properties, such as the Android SDK version, debug or release mode, target platform ABI (architecture binary interface), standard C/C++ library, and so on.

A typical Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := <module_name>  # name your module here.
LOCAL_SRC_FILES := main.c
include $(BUILD_SHARED_LIBRARY)

A minimal Application.mk

APP_ABI := all

To build such project, we can go to the project_root, and type ndk-build (assuming you have the NDK_ROOT in the system PATH, the NDK building script will automatically find the native code under jni folder.

$ cd project_root
$ ndk-build

If there is no bug in the code, the compiled shared library lib<module_name>.so will be generated under libs/<abi>/ directory. Once you get this shared library file, your Android application building system will pack it into the final APK installer file. You will be able to call the native functions using JNI in your JAVA code.

In this article, we focus more on how to build executable binaries using NDK, since we will be easier to test our results immediately in that way. But remember that all the techniques talked here will be the same and can be directly applied to a shared library project without any change.

4. Building Native Executable Using NDK

Let’s first build a “Hello World!” test. The project structure will be like this:

+-- ex1_helloworld
|   +-- jni
|       +-- Android.mk
|       +-- Application.mk
|       +-- hello.c

Hello.cpp

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}

Android.mk
Please notice that a comment starts with "#" in .mk files.

LOCAL_PATH:= $(call my-dir) # Get the local path of the project.
include $(CLEAR_VARS) # Clear all the variables with a prefix "LOCAL_"

LOCAL_SRC_FILES:=hello.cpp # Indicate the source code.
LOCAL_MODULE:= hello # The name of the binary.
include $(BUILD_EXECUTABLE) # Tell ndk-build that we want to build a native executable.

Application.mk

APP_OPTIM := debug    # Build the target in debug mode. 
APP_ABI := armeabi-v7a # Define the target architecture to be ARM.
APP_STL := stlport_static # We use stlport as the standard C/C++ library.
APP_CPPFLAGS := -frtti -fexceptions    # This is the place you enable exception.
APP_PLATFORM := android-19 # Define the target Android version of the native application.

You may already found out that, the major difference between a shared library project and a native project is only one line in the Android.mk.

For a shared library, we use:

include $(BUILD_SHARED_LIBRARY)

For an executable binary, we use:

include $(BUILD_EXECUTABLE)

To this point, we can build our “Hello World!” NDK project:

$ cd project_root
$ ndk-build
[armeabi-v7a] Cygwin         : Generating dependency file converter script
[armeabi-v7a] Compile++ thumb: hello <= hello.cpp
[armeabi-v7a] Executable     : hello
[armeabi-v7a] Install        : hello => libs/armeabi-v7a/hello

Then, we can push the hello native program to an Android device and run it (to achieve this, we need Android ADB tool. Please install Android SDK, and set the ANDROID_SDK_ROOT/platform-tools to system PATH. Of course, you may also find ADB install package from internet if you don’t want to bother to install Android SDK.)

$ adb root
$ adb shell "mkdir -p /data/mastering_ndk && chmod 777 /data/mastering_ndk"
$ adb push ./libs/armeabi-v7a/hello /data/mastering_ndk
$ adb shell "cd /data/mastering_ndk && chmod 777 ./hello && ./hello"
$ Hello World!

Tip: The above command only works for rooted devices. If you have a stock version device without root permission, you can utilize the Android Native Program Launcher tool to launch the native executable on any Android device.

We have recalled the basics of ndk-build. From the next section, we will show some techniques to better utilize the NDK build system for some larger projects.

5. Useful Techniques

5.1 How to compile source code not in jni directory

Assume you have a big project, presumably a cross-platform project, so chances are high that you cannot easily move all your source code to under the jni folder. This is actually not to do with some small modification to the existing Android.mk makefile. The following example will show how to achieve that. You can find the complete project in example 2: ex2_src_not_in_jni_folder.

Project structure:

+-- ex2_src_not_in_jni_folder
|   +-- jni
|       +-- Android.mk
|       +-- Application.mk
|   +-- src
|       +-- hello.c

Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= ../src/hello.cpp
LOCAL_MODULE:= hello
include $(BUILD_EXECUTABLE)

Application.mk remains the same. We can build the project and execute the binary using the same way as in example 1.

5.2 Get rid of jni folder

The jni folder makes more sense for a JNI native project in an Android application project. If we want something more meaningful to our project, we can get rid of that specific folder which is used by ndk-build by default. To achieve that, a few variables in the makefiles should be set appropriately. The following steps will achieve that goal. The complete example project can be found in example 3.

Project structure:

+-- ex3_get_rid_of_jni_folder
|   +-- Android.mk
|   +-- Application.mk
|   +-- src
|       +-- hello.c

As you can see, we removed jni folder and moved the Android.mk and Application.mk up to the project_root folder.

The new Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= src/hello.cpp # This has changed!
LOCAL_MODULE:= hello
include $(BUILD_EXECUTABLE)

The new Application.mk

APP_OPTIM := debug
APP_ABI := armeabi-v7a
APP_STL := stlport_static 
APP_CPPFLAGS := -frtti -fexceptions
APP_PLATFORM := android-19
APP_BUILD_SCRIPT := Android.mk  # this line is new!

Please notice that the APP_BUILD_SCRIPT indicates the major makefile entry for the whole application. In our case, it is Android.mk. Everything looks good so far, then, we use the following command to build the project, please notice that we add NDK_APPLICATION_MK variable to the ndk-build command to tell the ndk-build where to find the Application.mk. In this example, we use the following command:

$ ndk-build NDK_APPLICATION_MK=./Application.mk

However, we will have the following error:

$ ndk-build NDK_APPLICATION_MK=./Application.mk
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
/cygdrive/d/development/android-ndk-r10d/build/core/build-local.mk:148: *** Android NDK: Aborting    .  Stop.

The NDK_PROJECT_PATH is a system environmental variable. Let’s define it to where the Application.mk is located and re-build.

$ export NDK_PROJECT_PATH=.
$ ndk-build NDK_APPLICATION_MK=./Application.mk
[armeabi-v7a] Cygwin         : Generating dependency file converter script
[armeabi-v7a] Compile++ thumb: hello <= hello.cpp
[armeabi-v7a] Executable     : hello
[armeabi-v7a] Install        : hello => libs/armeabi-v7a/hello

There is another way to fix the above build error. The solution is to create an empty AndroidManifest.xml file in the same folder with Application.mk.

After adding this dummy AndroidManifest.xml file, we can build the project without defining NDK_PROJECT_PATH variable. The final project structure is:

+-- ex3_get_rid_of_jni_folder
|   +-- Android.mk
|   +-- AndroidManifest.xml
|   +-- Application.mk
|   +-- src
|       +-- hello.c

5.3 Use custom names for makefiles

We can push the previous technique even further to define our own makefiles. The following example can be found in example 4.

In the following example, we rename the application makefile to MyApplication.mk, and the module makefile to MyAndroid.mk.

Project structure:

+-- ex4_custom_make_files
|   +-- MyAndroid.mk
|   +-- AndroidManifest.xml
|   +-- MyApplication.mk
|   +-- src
|       +-- hello.c

MyAndroid.mk is the same as the previous Android.mk.
The new MyApplication.mk

APP_OPTIM := debug
APP_ABI := armeabi-v7a
APP_STL := stlport_static 
APP_CPPFLAGS := -frtti -fexceptions
APP_PLATFORM := android-19
APP_BUILD_SCRIPT := MyAndroid.mk

And the build command becomes:

$ ndk-build NDK_APPLICATION_MK=./MyApplication.mk
[armeabi-v7a] Compile++ thumb: hello <= hello.cpp
[armeabi-v7a] Executable     : hello
[armeabi-v7a] Install        : hello => libs/armeabi-v7a/hello

Everything builds well and we successful got the binary hello.

5.4 Using include to embed .mk files

To better handle large projects containing multiple submodules, in the form of static libraries, shared libraries, or pre-built files, the NDK build system allows a makefile to include another makefiles. The following is the syntax:

include PATH_TO_MK_FILE/Android.mk

This will include Android.mk file under PATH_TO_MK_FILE directory to the current makefile. This “include” feature provides us tremendous flexibility to create some very creative way to utilize the building system.

Let’s look at a simple example (example 5) and see how it works. Please notice that this example contains a very simple makefiles. With the power of “include”, I am confident to say that you can create much more complicated building scripts, which can almost do anything for any project, no matter how complex that project is.

In the following example project, we have a main() function inside source file compute.cpp which calls add() and mul() functions to perform addition and multiplication on the input numbers. We define add() and mul() functions in two submobules, and compile them into two static libraries. Finally, when build the executable, the linker will link everything together and generate the final executable binary.

So, to better handle the submodules and separate every submobules, in this project, we create a Android.mk for each module. As you will see soon, by organizing the makefiles this way, the project now has a very scalable structure. To be more specific, if you want to add one more submobule to the same project, you just simply add another submodule folder (whatever it is called, let’s say, divide), and create a new Android.mk for that new submodule “divide”. Then, you just need to change one line in the makefile of the main module. All the existing submodules remain untouched. The project is very easy to maintain and extend to support more functions.

Top level

First, let’s look at the project structure and have a overall picture:

+-- ex5_using_include_to_embed_make_files
|   +-- makefiles
|       +-- Android.mk
|       +-- Application.mk
|   +-- src
|       +-- main
|           +-- compute.cpp
|           +-- Android.mk
|       +-- submodules
|           +-- add
|               +-- add.cpp
|               +-- Android.mk
|           +-- mul
|               +-- mul.cpp
|               +-- Android.mk   
|           +-- Android.mk          
|   +-- AndroidManifest.xml

makefiles/Application.mk

APP_OPTIM := debug
APP_ABI := armeabi-v7a
APP_STL := stlport_static 
APP_CPPFLAGS := -frtti -fexceptions
APP_PLATFORM := android-19
APP_BUILD_SCRIPT := makefiles/Android.mk

makefiles/Android.mk

TOP_LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

include $(TOP_LOCAL_PATH)/../src/submodules/Android.mk
include $(TOP_LOCAL_PATH)/../src/main/Android.mk

Here, this Android.mk serves as the top level makefile and includes another two Android.mk, one for the submodules, and the other for the main module.

Main module : compute

We first look at the main module.

src/main/compute.cpp

#include <iostream>

int add(int a, int b);
int mul(int a, int b);

int main()
{
    int a = 2;
    int b = 3;
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;
    std::cout << "add(a, b) = " << add(a, b) << std::endl;
    std::cout << "mul(a, b) = " << mul(a, b) << std::endl;

    return 0;
}

src/main/Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= compute.cpp
LOCAL_MODULE:= compute
LOCAL_STATIC_LIBRARIES:= add mul
include $(BUILD_EXECUTABLE)

We build the main module as an executable by defining include $(BUILD_EXECUTABLE). And we also define the LOCAL_STATIC_LIBRARIES to add mul, which means this main module depends on two static libraries with the module names add and mul, respectively. But where are these two modules are defined, let’s continue to look at the submobules.

Sub-modules: add and mul

src/submodules/Android.mk

include $(call all-subdir-makefiles)

Here, in the submodules folder, the makefiles only contains one line, which calls a function in the NDK build system. This command include $(call all-subdir-makefiles) is basically equivalent to including all the Android.mk files in all the sub-directories manually. In our case, this will help us include src/submodules/add/Android.mk and src/submodules/mul/Android.mk.

Let’s take a look at submodule add.

src/submodules/add/add.cpp

int add(int a, int b)
{
    return a + b;
}

src/submodules/add/Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= ./add.cpp
LOCAL_MODULE:= add
include $(BUILD_STATIC_LIBRARY)

We define module add to be a static library.

Similarly, we have the submodule mul, which almost has the same makefile as add module.

src/submodules/mul/mul.cpp

int mul(int a, int b)
{
    return a * b;
}

src/submodules/mul/Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= ./mul.cpp
LOCAL_MODULE:= mul
include $(BUILD_STATIC_LIBRARY)

So far, we have listed all the files in the projects. And the build flow is clearly shown as follows:

  1. Build static library add using add.cpp;
  2. Build static library mul using mul.cpp;
  3. Build main module compute using compute.cpp;
  4. Link compute to static libraries libadd.a and libmul.a, generate executable compute.

Build and Execute

We use the same command from the previous examples to build the project.

$ ndk-build NDK_APPLICATION_MK=./makefiles/Application.mk
[armeabi-v7a] Cygwin         : Generating dependency file converter script
[armeabi-v7a] Compile++ thumb: compute <= compute.cpp
[armeabi-v7a] Compile++ thumb: add <= add.cpp
[armeabi-v7a] StaticLibrary  : libadd.a
[armeabi-v7a] Compile++ thumb: mul <= mul.cpp
[armeabi-v7a] StaticLibrary  : libmul.a
[armeabi-v7a] Executable     : compute
[armeabi-v7a] Install        : compute => libs/armeabi-v7a/compute

We execute the binary.

$ adb shell "mkdir -p /data/mastering_ndk && chmod 777 /data/mastering_ndk"
$ adb push ./libs/armeabi-v7a/compute /data/mastering_ndk
$ adb shell "cd /data/mastering_ndk && chmod 777 ./compute && ./compute"
a = 2
b = 3
add(a, b) = 5
mul(a, b) = 6

We can see that the results are exactly what we expected. Clearly, by using “include”, the project becomes more structured. All the modules are built separately, but have the ability to share variables in the makefiles. One can imagine that in a big project, there must be much more settings, such as LOCAL_C_INCLUDESLOCAL_CFLAGSLOCAL_LDFLAGSLOCAL_LDLIBS and so on. Many submobules may have the same values for these settings. In those cases, we can extract the common part and put the common ones in a common makefile, and then include it in makefiles for each submodule. Doing that will save a lot of coding effort, and will make the makefiles easier to modify and maintain. When adding new submodules, the effort of writing new makefiles will be minimal.

5.5 About LOCAL_PATH and CLEAR_VARS

The following two NDK built-in functions are quite important:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

The first one (LOCAL_PATH:= $(call my-dir)) retrieves the current local path of the Android.mk file, so that all the variables in the same file can generate absolute path based on this local path. The command LOCAL_PATH:= $(call my-dir) clears all the NDK built-in variables starting with LOCAL_, such as LOCAL_SRC_FILESLOCAL_C_INCLUDESLOCAL_CFLAGSLOCAL_LDFLAGSLOCAL_LDLIBS and so on, except for the LOCAL_PATH.

This works perfectly fine when you have just a single Android.mk in the project. However, if you use “include” to put multiple makefiles together, you need to be careful about the above two commands.

The reason is that the LOCAL_PATH variable can be overwritten by the subsequent call to the command LOCAL_PATH:= $(call my-dir). For example, in the following case:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

include subfolder/Android.mk
LOCAL_SRC_FILES:= $(LOCAL_PATH)/test.cpp

The problem with the above makefiles is that after the “include”, in the subfolder/Android.mk, the LOCAL_PATH may be modified by the included Android.mk. Then, when you try to locate the test.cpp, the ndk-build will fail, since the path to the file is wrong now.

If you pay enough attention to example 5, you will see a small trick has been used there. Let’s take a look at the top level makefile of example 5.

ex5_using_include_to_embed_make_files/makefiles/Android.mk

TOP_LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

include $(TOP_LOCAL_PATH)/../src/submodules/Android.mk
include $(TOP_LOCAL_PATH)/../src/main/Android.mk

相關推薦

Mastering Android NDK Build System

This article is not a “Hello world!”-type tutorial for NDK. Although I will still provide a quick walk-through of the very basic knowledge

Android-new-build-system

1. make/core/main.mk ifndef KATI host_prebuilts := linux-x86 ifeq ($(shell uname),Darwin) host_prebuilts := darwin-x86 en

NDK筆記(二)-在Android Studio中使用ndk-build(轉)

路徑 width 能夠 jdk ide 代碼 目標 -1 adl 前面一篇我們接觸了CMake,這一篇寫寫關於ndk-build的使用過程。剛剛用到,想到哪兒寫哪兒。 環境背景 Android開發IDE版本:AndroidStudio 2.2以上版本(目前已經升級到2.

ndk-build配置、Android Studio jni的配置以及jni常見問題的解決

           最近專案用到了jni比較頻繁,android studio 配置jni也是必須的。但不知道是不是運氣問題,我在自己電腦使用jni一點問題都沒有,可以說是無障礙。 但是,一

關於 D:\BaiduYunDownload\android-ndk-r10d\ndk-build.cmd問題的解決方案

至於opencv 和android環境的配置問題可以參照 http://blog.csdn.net/pwh0996/article/details/8957764 經過一路的配置後,會發現一個問題就是 D:\BaiduYunDownload\android-ndk-r10d\ndk-b

解決build/core/build-local.mk:151: *** Android NDK: Aborting

從build-local.mk來看,是沒有定義NDK_PROJECT_PATH ifndef NDK_PROJECT_PATH NDK_PROJECT_PATH := $(call find-

Android Studio 3.2 JNI (ndk-build)

記錄下 Android Studio 嵌入 C 程式碼的過程,使用 ndk-build. 當前環境: Android Studio 3.2 NDK 18.1 建立 JNI 資料夾 直接在專案右鍵,選擇 New - Folder - JNI Folder ,對話方塊直接點選

Unity Android 中的Build System

Internal:Unity內建,僅需要Android SDK支援,不能匯出工程,適用於僅使用到Unity開發的專案。 Gradle:使用Gradle進行構建,需要Android SDK與Gradle支援,可以匯出Android Studio工程,適用於Unity與Android互動的專案。

使用NDK build android上的busybox.

在android上,為了某些需要,我們需要一個小而精減的busybox(如果不懂busybox是什麼),請跳過此文。當然我們也可以用gcc的toolchains來build, 但生成出來的那個二進位制檔案的size會讓你瘋狂。而用NDK生成出來的二進位制則是gcc生成的五分

使用ndk-build編譯android可執行檔案

target.c #include <stdio.h> int count = 0; void sevenWeapons(int number) { char* str = "Hello,11111111!"; printf("%s %d\n

( OK ) CentOS 7 + android-ndk-r10d-linux-x86_64 + Android (ARM)—ndk-build

可以以  http://blog.csdn.net/bupt073114/article/details/43114223  示例 ++++++++ 在CentOS 7 android-ndk-r10d-linux-x86_64.bin  --->  /opt/an

Android NDK-0.ndk-buildAndroid.mk和Android.mk簡介

文章目錄 ndk-build是什麼 ndk-build如何使用 什麼是Android.mk LOCAL_PATH CLEAR_VARS LOCAL_MODULE LOCAL_CFLAGS

Build Android NDK Toolchain From Source Code

Android NDK comes with a few toolchains under the toolchain directory. We can also build our own toolchain from the source code.0. Downloa

Android NDK編譯 ndk-build方式

因很少使用NDK編譯,每次在涉及到這一塊的時候都會忘記NDK開發的開發的環境配置和開發步驟。所以今天自己做下筆記記錄下開發步驟:環境:AndroidStudio2.3,Ubuntu14.0,android-ndk-r14b,java8;第一步:配置NDK環境,直接上圖:NDK

How to build Clang toolchains for Android NDK from source code

we have some source changes to LLVM/Clang need add into NDK. After download and change "external/llvm" and build NDK from source. It find

我的Android NDK之旅(一),不使用ndk-build命令來建立jni

最近閒來無事,想摸索下一下ndk,可是ndk不是塊好啃的骨頭,但作為一名程式設計師,什麼都要了解下,對吧╮( ̄▽ ̄)╭。首先我想吐槽一下,網上有些部落格寫的很亂,一上來就貼一段程式碼,也不告訴是要幹什麼,程式碼一寫完就完事,這讓初學者很難理解jni到底是個什

Android build system:構建系統的組成及其原理

Android build system 組成部分 Android build system 的組成部分:Gradle + Android plugin for Gradle android app打包流程(即構建流程): Gra

linux 下使用ndk-build編譯android使用的c++靜態庫

1)下載android-ndk-r4 下載地址 http://www.ideasandroid.com/android/sdk/android-ndk-r4-linux-x86.zip http://developer.android.com/sdk/ndk/overvi

【轉】Android ROM研究---Android build system增加模組

Android build system就是編譯系統的意思 在我們需要向自己編譯的原始碼中增加模組的時候,需要一些規則,當然這個規則都是類似的。 Android.mk檔案解析 讓我們來看一個 Android.mk 檔案的樣子 Java程式碼   L

Android Build System[一]

更多幹貨,請關注微信公眾號: tmac_lover 1. 寫在最開始 Android Build System是AOSP(Android Open Source Project)中既重要又複雜的一部分,且涉及的知識點非常多,比如shell scr