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 pathNDK_ROOT
. - to avoid long path name when using
ndk-build
, I addedNDK_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:
- Build static library
add
usingadd.cpp
; - Build static library
mul
usingmul.cpp
; - Build main module
compute
usingcompute.cpp
; - Link
compute
to static librarieslibadd.a
andlibmul.a
, generate executablecompute
.
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_INCLUDES
, LOCAL_CFLAGS
, LOCAL_LDFLAGS
, LOCAL_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_FILES
, LOCAL_C_INCLUDES
, LOCAL_CFLAGS
, LOCAL_LDFLAGS
, LOCAL_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-build的Android.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