編輯:關於Android編程
引言
使在Android N的系統上,初次使用了Ninja的編譯系統。對於Ninja,最初的印象是用在了Chromium open source code的編譯中,在chromium的編譯環境中,使用ninja -C out/Default chrome命令,就可以利用源碼編譯出chrome的apk。對使用者而言,拋開對原理的探究,最直觀的印象莫過於可以清楚的看到自己當前編譯的進度。同時,對android而言,也可以感受到編譯速度的提升帶來的便捷。本文將深入分析Ninja的編譯原理,以及android上面的編譯改變。
正因為這個改變,所以在編譯android N的code的時候需要使用OpenJDK8
編譯系統的內存最少需要12G,建議16G,否則會出現JVM不足的錯誤。
8G內存的機器可以通過增大JVM默認值的方法來解決,但是經過測試,還是會偶爾出現JVM不足的錯誤
exportJAVA_OPTS='-Xmx4096M'
概念簡介
名詞:
Ninja
Blueprint
Soong
Ninja
Ninja是一個致力於速度的小型編譯系統(類似於Make);
如果把其他編譯系統比做高級語言的話,Ninja就是匯編語言
主要有兩個特點:
1、可以通過其他高級的編譯系統生成其輸入文件;
2、它的設計就是為了更快的編譯;
使用Kati把makefile轉換成Ninja files,然後用Ninja編譯
在不久的將來,當不再用Makefile(Android.mk)時,Kati將被去掉
ninja核心是由C/C++編寫的,同時有一部分輔助功能由python和shell實現。由於其開源性,所以可以利用ninja的開源代碼進行各種個性化的編譯定制。
Github地址: https://github.com/ninja-build/ninja
Blueprint, Soong
Blueprint和Soong是用於一起把Blueprint 文件轉換為Ninja文件。 將來需要寫Blueprint文件(Android.bp),轉換為Android.soong.mk(也可以直接寫),然後轉換為Ninja文件(build.ninja)然後用Ninja編譯。
如果Android.mk和Android.bp同時存在,Android.mk會被忽略。
如果Android.bp的同級目錄下有Android.soong.mk也會被include
1.ckati可執行文件的生成
在android系統中,目前還未完全切換到Ninja編譯,編譯的入口仍然是make命令, 如下commands以nexus為例:
source build/envsetup.sh
choosecombo
make -j4
在這邊可以看到,最終編譯使用的命令仍然是make.
既然是make,那就在編譯中首先include到的就是build/core/main.mk了,在main.mk中,我們可以清楚的看到對Ninja的調用:
relaunch_with_ninja := ifneq ($(USE_NINJA),false) ifndef BUILDING_WITH_NINJA relaunch_with_ninja := true endif endif
由於USE_NINJA默認沒有定義,所以一定會進入到這個選項中,並且將relaunch_with_ninja置為true。這樣的話,就會進入到下面的重要操作語句,去include ninja的makefile. 並且在out目錄下生成ninja_build的文件,顯示當前是使用了ninja的編譯系統。
ifeq ($(relaunch_with_ninja),true) # Mark this is a ninjabuild. $(shell mkdir -p $(OUT_DIR)&& touch $(OUT_DIR)/ninja_build) includebuild/core/ninja.mk else # !relaunch_with_ninja ifndef BUILDING_WITH_NINJA # Remove ninja build mark ifit exists. $(shell rm -f $(OUT_DIR)/ninja_build) endif
在include build/core/ninja.mk的語句執行後,我們就可以看到真正定義ninja的地方了。由於前面簡介講了ninjia是基於開源項目編譯出來的輕便的編譯工具,所以這邊google肯定也對ninjia進行了修改,編譯,並且最終生成了一個可執行的應用程序。在simba6項目中,我們可以在prebuilts/ninja/linux-x86下面找到這個可執行的應用程序ninja。我們可以簡單的運行這個ninja的命令,比如ninja –h, 就可以了解到這個command的基本用法, 也可以看到本版本的ninja使用的base version為1.6.0。
./ninja -h
usage: ninja [options][targets...]
if targets are unspecified,builds the 'default' target (see manual).
options:
--versionprint ninja version ("1.6.0")
-C DIRchange to DIR before doing anything else
-f FILEspecify input build file [default=build.ninja]
-j Nrun N jobs in parallel [default=6, derived from CPUs available]
-k Nkeep going until N jobs fail [default=1]
-l Ndo not start new jobs if the load average is greater than N
-ndry run (don't run commands but act like they succeeded)
-vshow all command lines while building
-d MODEenable debugging (use -d list to list modes)
-t TOOLrun a subtool (use -t list to list subtools)
terminates toplevel options; further flagsare passed to the tool
-w FLAGadjust warnings (use -w list to list warnings)
---------------------------------------------------------------------------------------------------
在聲明了ninjia可執行程序的目錄之後,緊接著在mk中就提及了kati的定義。
KATI ?= $(HOST_OUT_EXECUTABLES)/ckati
目標KATI是利用源碼編譯生成的一個可執行的程序。源碼在build/kati文件夾中。這同樣是一個開源的代碼。
開源網站的地址為:
https://github.com/google/kati
我們可以clone下來最新的code,或者在源碼build/kati中直接按下面的步驟來編譯ckati的可執行程序。
一、添加軟件源
sudoadd-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update
二、安裝版本的命令:
sudo apt-get install gcc-4.8 g++-4.8
三、查看本地版本
四、切換版本
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.660 sudo update-alternatives --install /usr/bin/gcc gcc/usr/bin/gcc-4.8 40 sudo update-alternatives --install /usr/bin/g++ g++/usr/bin/g++-4.6 60 sudo update-alternatives --install /usr/bin/g++ g++/usr/bin/g++-4.8 40
這裡的4.6是你本機之前的版本。
sudo update-alternatives --config gcc sudo update-alternatives --config g++
選擇你需要的版4.8.
在選擇好了之後,執行make,即可開始編譯。編譯後會在根目錄生成ckati的可執行程序。
當然,android源碼是利用makefile來做的,我們可以看到ninja.mk中對ckati的makefile進行了調用。
文件地址:include build/kati/Makefile.ckati
在kati的makefile中,我們可以看到真正去編譯kati的過程。
# Rule to build ckati intoKATI_BIN_PATH $(KATI_BIN_PATH)/ckati:$(KATI_CXX_OBJS) $(KATI_CXX_GENERATED_OBJS) @mkdir -p $(dir $@) $(KATI_LD) -std=c++11 $(KATI_CXXFLAGS) -o$@ $^ $(KATI_LIBS) # Rule to build normal sourcefiles into object files in KATI_INTERMEDIATES_PATH $(KATI_CXX_OBJS) :$(KATI_INTERMEDIATES_PATH)/%.o: $(KATI_SRC_PATH)/%.cc @mkdir -p $(dir $@) $(KATI_CXX) -c -std=c++11 $(KATI_CXXFLAGS)-o $@ $< # Rule to build generatedsource files into object files in KATI_INTERMEDIATES_PATH $(KATI_CXX_GENERATED_OBJS):$(KATI_INTERMEDIATES_PATH)/%.o: $(KATI_INTERMEDIATES_PATH)/%.cc @mkdir -p $(dir $@) $(KATI_CXX)-c -std=c++11 $(KATI_CXXFLAGS) -o $@ $<
這個調用簡單解釋一下,就是編譯kati是需要依賴與KATI_CXX_OBJS和 KATI_CXX_GENERATED_OBJS這兩個變量。 KATI_CXX_OBJS的生成依賴於 KATI_INTERMEDIATES_PATH下的.o,而.o文件的生成又依賴與KATI_INTERMEDIATES_PATH下的.cc文件。在生成了所有依賴的.o文件之後,會link成編譯所需的ckati文件。具體的命令為:$(KATI_LD) -std=c++11$(KATI_CXXFLAGS) -o $@ $^ $(KATI_LIBS)
這樣的話,就完成了ckati可執行文件的生成。
流程圖可以簡單歸結如下
2.解析並使用ninja
ckati文件生成之後,我們接著來看是如何使用的。
接著回到ninja.mk文件中,如下是具體的調用。
$(KATI_BUILD_NINJA): $(KATI) $(MAKEPARALLEL)$(DUMMY_OUT_MKS) $(SOONG_ANDROID_MK) FORCE @echo Running kati to generatebuild$(KATI_NINJA_SUFFIX).ninja... +$(hide)$(KATI_MAKEPARALLEL) $(KATI) --ninja --ninja_dir=$(OUT_DIR)--ninja_suffix=$(KATI_NINJA_SUFFIX) --regen --ignore_dirty=$(OUT_DIR)/%--no_ignore_dirty=$(SOONG_ANDROID_MK) --ignore_optional_ include=$(OUT_DIR)/%.P--detect_android_echo $(KATI_FIND_EMULATOR) -f build/core/main.mk $(KATI_GOALS)--gen_all_targets BUILDING_WITH_NINJA=true SOONG_ANDROID_MK=$(SOONG_ANDROID_MK)
可以看到使用kati,並且將很多的參數傳入到了ckati中。
在kati文件的main函數中,可以看到接受了這些參數並且進行處理。
文件地址:build/kati/main.cc
main函數:
int main(int argc, char*argv[]) { if (argc >= 2 && !strcmp(argv[1],"--realpath")) { HandleRealpath(argc - 2, argv + 2); return 0; } Init(); string orig_args; for (int i = 0; i < argc; i++) { if (i) orig_args += ' '; orig_args += argv[i]; } g_flags.Parse(argc, argv); FindFirstMakefie(); if (g_flags.makefile == NULL) ERROR("*** No targets specified and nomakefile found."); // This depends on command line flags. if (g_flags.use_find_emulator) InitFindEmulator(); int r = Run(g_flags.targets, g_flags.cl_vars,orig_args); Quit(); return r; }
argv接受到了傳入的參數後,經過處理,轉化為了string,傳入orig_args變量,並且調用Run函數來進行後續的處理。Run函數是kati程序的核心,用於各種文件的生成,流程的執行以及處理。我們這邊只對重點內容進行分析。
任何的編譯都脫離不了環境變量的支持,在編譯的第一步,肯定要對環境變量進行設置。
在run函數的開始,就利用Linux標准C接口來進行了環境變量的讀取和設置。
具體操作為:
extern "C" char**environ; … for (char** p = environ; *p; p++) { SetVar(*p, VarOrigin::ENVIRONMENT); }
如果我們prinf打印*p的值,可以很清楚的看到該環境變量的設置。這邊只截取部分環境變量用於說明該問題:
printf("*p = %s \n", *p); /* *p =XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0 *p =BUILD_ENV_SEQUENCE_NUMBER=10 *p =XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0 *p =ANDROID_BUILD_PATHS=/data/android_N/out/host/linux-x86/bin:/data/android_N/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin:/data/android_N/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin:/data/android_N/development/scripts:/data/android_N/prebuilts/devtools/tools:/data/android_N/external/selinux/prebuilts/bin:/data/android_N/prebuilts/android-emulator/linux-x86_64: *p = SSH_AUTH_SOCK=/tmp/keyring-941KY1/ssh *p =MAKELEVEL=1 *p =DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path *p =SESSION_MANAGER=local/chao:@/tmp/.ICE-unix/1835,unix/chao:/tmp/.ICE-unix/1835 *p =TARGET_BUILD_APPS= * */ 在設置完環境變量以後,就會開始對makefile進行部分的解析。這邊有個重要函數為 static voidReadBootstrapMakefile(const vector& targets, vector* stmts) { … }
函數初始定義了一些基本的變量,比如GCC,G++,SHELL,MAKE等。並且會去解析當前編譯機器所擁有的cpu的核數,且進行合理分配。
以下是一些具體初始化的變量:
/* * bootstrap = * * CC?=cc * CXX?=g++ * AR?=ar * MAKE_VERSION?=3.81 * KATI?=ckati * SHELL=/bin/sh * .c.o: *$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $< *.cc.o: *$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $< *MAKE?=make -j2 *MAKECMDGOALS?= ·CURDIR:=/data/android_N 並且會將這些字符串轉換為對應的node結構體,保存在內存變量中,方便編譯的時候使用。 for (Stmt* stmt :bootstrap_asts) { printf("stmt = %s\n\n", stmt->DebugString().c_str()); stmt->Eval(ev); } 以下截取部分log: /* stmt =AssignStmt(lhs=CC rhs=cc (cc) opstr=QUESTION_EQ dir= loc=*bootstrap*:0) stmt = AssignStmt(lhs=CXX rhs=g++ (g++) opstr=QUESTION_EQ dir=loc=*bootstrap*:0) stmt =AssignStmt(lhs=AR rhs=ar (ar) opstr=QUESTION_EQ dir= loc=*bootstrap*:0) stmt =AssignStmt(lhs=MAKE_VERSION rhs=3.81 (3.81) opstr=QUESTION_EQ dir= loc=*bootstrap*:0) stmt = AssignStmt(lhs=KATI rhs=ckati (ckati) opstr=QUESTION_EQ dir=loc=*bootstrap*:0) stmt =AssignStmt(lhs=SHELL rhs=/bin/sh (/bin/sh) opstr=EQ dir=loc=*bootstrap*:0) stmt =RuleStmt(expr=.c.o: term=0 after_term=(null) loc=*bootstrap*:0) stmt =CommandStmt(Expr(SymRef(CC), ,SymRef(CFLAGS), , SymRef(CPPFLAGS), , SymRef(TARGET_ARCH), -c -o , SymRef(@), , SymRef(<)), loc=*bootstrap*:0) stmt =RuleStmt(expr=.cc.o: term=0 after_term=(null) loc=*bootstrap*:0) */
在環境變量,編譯參數都設置成功後,就會開始GenerateNinja的重要操作。
GenerateNinja的函數定義在了ninja.cc中,以下是函數的具體實現。
void GenerateNinja(constvector& nodes, Evaluator* ev, const string&orig_args, double start_time) { NinjaGenerator ng(ev, start_time); ng.Generate(nodes, orig_args); }
該函數初始化了一個 NinjaGenerator的結構體,並且繼續調用 Generate的方法。
void Generate(const vector&nodes, const string& orig_args){ unlink(GetNinjaStampFilename().c_str()); PopulateNinjaNodes(nodes); GenerateNinja(); GenerateShell(); GenerateStamp(orig_args); }
Generate 函數非常的重要, PopulateNinjaNodes會對前面include的makefile進行解析,並且將node進行整理。正如前面分析的link的程序會依賴.o一樣,這裡基本會將所依賴的.o;.a; .so進行歸類,包含了所有文件下面的目錄。這裡舉一些簡單截取的例子:
node =out/host/linux-x86/obj/STATIC_LIBRARIES/libcutils_intermediates/strlcpy. node =out/host/linux-x86/obj/STATIC_LIBRARIES/libcutils_intermediates/threads.o node =out/host/linux-x86/obj/STATIC_LIBRARIES/libcutils_intermediates/dlmalloc_stubs.o ….. node =out/host/linux-x86/obj/SHARED_LIBRARIES/libcryptohost_intermediates/src/crypto/evp/sign.o node =out/host/linux-x86/obj/SHARED_LIBRARIES/libcrypto-host_intermediates/src/crypto/ex_data.o node = out/host/linux-x86/obj/SHARED_LIBRARIES/libcrypto-host_intermediates/src/crypto/hkdf/hkdf.o node = out/host/linux-x86/obj/SHARED_LIBRARIES/libcrypto-host_intermediates/src/crypto/hmac/hmac.o …. node = out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/IBackupAgent.java node = out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/IInstrumentationWatcher.java node = out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/INotificationManager.java node = out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/IProcessObserver.java node = out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/ISearchManager.java …. node = out/host/linux-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/base64.o node = out/host/linux-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/base64url.o node = out/host/linux-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/base_switches.o node = out/host/linux-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/bind_helpers.o node = out/host/linux-x86/obj32/SHARED_LIBRARIES/libchrome_intermediates/base/build_time.o … node = out/target/product/generic/obj/SHARED_LIBRARIES/libhardware_intermediates/hardware.o node = out/target/product/generic/obj/SHARED_LIBRARIES/libhardware_intermediates/import_includes node = out/target/product/generic/obj/lib/libandroidfw.so.to node = out/target/product/generic/obj/lib/libandroidfw.so node = out/target/product/generic/symbols/system/lib/libandroidfw.so... GenerateNinja() { .... fp_ = fopen(GetNinjaFilename().c_str(), "wb");... fprintf(fp_, "# Generated by kati %s\n",kGitVersion); fprintf(fp_, "\n"); … for (const ostringstream& buf : bufs) { fprintf(fp_, "%s", buf.str().c_str()); } … fclose(fp_); }
在整理好了依賴之後,會將所有的步驟寫入文件中。具體的操作為 GenerateNinja函數所實現。
GenerateNinja() { .... fp_ = fopen(GetNinjaFilename().c_str(), "wb");... fprintf(fp_, "# Generated by kati %s\n",kGitVersion); fprintf(fp_, "\n"); … for (const ostringstream& buf : bufs) { fprintf(fp_, "%s", buf.str().c_str()); } … fclose(fp_); }
在GenerateNinja函數中,會創建並寫入一個文件, 這個文件依賴於build target的制定。比如在nexus的編譯中,會在out目錄下生成
Build-aosp_arm.ninja文件,
該文件會非常大,但是這個也就是編譯的基礎和ninja可以明確知道自己所編譯的操作步數的由來。
具體的流程圖:
3.總結
Ninja編譯帶來的改變是巨大的,但是通過本文的分析,可以預見到後續的變化會更大且會一直存在。Android.bp何時可以完全取代makefile,ninja編譯時的test目錄的編譯其實對普通開發者來說都有些優化的空間。對這部分的研究將會持續存在
做Android應用中,最缺少不了的就是自定義Dialog,對於系統默認提供的Dialog樣式,一般都不復合我們應用的樣式。自定義Dialog需要3步驟即可:1、主要的重
onTach介紹ontach是Android系統中整個事件機制的基礎。Android中的其他事件,如onClick、onLongClick等都是以onTach為基礎的。o
前言因為工作需要可能要用到JNI開發,本篇文章就分享一下我在這方面的實踐,以前我們使用Eclipse做NDK開發,非常麻煩,需要配cygwin的編譯環境,後面NDK功能完
隨著Android5.0的發布,google帶來了Material Design,俗稱:材料設計。並帶來了一些新的東西,這裡就一一介紹這些新的設計元素。 1、P