摘要:本节主要来讲解Kati把Makefile编译成build-xxx.ninja,那么Kati是什么? 是如何工作的呢?

阅读本文大约需要花费24分钟。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

欢迎关注我的公众号!

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

  1. Android系统架构
  2. Android是怎么启动的
  3. Android 10.0系统启动之init进程
  4. Android10.0系统启动之Zygote进程
  5. Android 10.0 系统启动之SystemServer进程
  6. Android 10.0 系统服务之ActivityMnagerService
  7. Android10.0系统启动之Launcher(桌面)启动流程
  8. Android10.0应用进程创建过程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及启动流程
  10. Android 10.0 PackageManagerService(二)权限扫描
  11. Android 10.0 PackageManagerService(三)APK扫描
  12. Android 10.0 PackageManagerService(四)APK安装流程

《日志系统篇》

  1. Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
  2. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
  3. Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
  4. Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入门篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++实例分析
  5. Android10.0 Binder通信原理(五)-Binder驱动分析
  6. Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework层分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
  11. Android10.0 Binder通信原理(十一)-Binder总结

  《HwBinder通信原理》

  1. HwBinder入门篇-Android10.0 HwBinder通信原理(一)
  2. HIDL详解-Android10.0 HwBinder通信原理(二)
  3. HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
  4. HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
  5. HwServiceManager篇-Android10.0 HwBinder通信原理(五)
  6. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
  7. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
  8. JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
  9. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
  10. HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
  11. HwBinder原理总结-Android10.0 HwBinder通信原理(十一)

《编译原理》

  1. 编译系统入门篇-Android10.0编译系统(一)
  2. 编译环境初始化-Android10.0编译系统(二)
  3. make编译过程-Android10.0编译系统(三)
  4. Image打包流程-Android10.0编译系统(四)
  5. Kati详解-Android10.0编译系统(五)
  6. Blueprint简介-Android10.0编译系统(六)
  7. Blueprint代码详细分析-Android10.0编译系统(七)

1 概述

kati是Google专门为了Android而开发的一个小项目,基于Golang和C++。目的是为了把Android中的Makefile,转换成Ninja文件。

在最新的Android R(11)中,Google已经移除了/build/kati目录,只保留了一个预先编译出来的可执行文件:prebuilts/build-tools/linux-x86/bin/ckati,这意味着Google在逐渐从编译系统中移除kati,预计1-2个Android大版本,*.mk文件全部都切换成*.bp文件后,kati将会正式退出Android历史舞台。

2 kati 、ckati区别

kati是go语言写的,而ckati是c++写的。kati官方文档对它的描述是:kati is an experimental GNU make clone。也就是说,kati是对等make命令的。只不过kati并不执行具体的编译工作,而是生成ninja文件。

这里有个疑惑?为什么有两个版本的kati:kati/ckati?

kati刚开始是使用Golang编写的,但是后来验证下来发现编译速度不行,于是改成C++编写,所以现在存在两个版本:kati、ckati。我们在Android10.0编译过程中,是通过ckati来把makefile文件转换成ninja文件的。

关于Go版本kati编译速度问题,可以通过kati自带文档:build/kati/INTERNALS.md来查看:

Go版本比C++版本有更多的不必要的字符串分配。至于Go本身,似乎GC是主要的展示器。例如,Android的构建系统定义了大约一百万个变量,缓冲区将永远不会被释放。,这种分配格局对于非代际GC(non-generational)是不利的。

因此采用C++编译会减少缓冲区分配问题,提高编译速度,因此我们现在主要还是使用ckati进行mk文件的转换。

3 Kati整体架构

Kati由以下组件组成:

  • 解析器(Parser)

  • 评估器(Evaluator)

  • 依赖构建器(Dependency builder)

  • 执行器(Executor)

  • Ninja生成器(Ninja generator)

Makefile有一些由零个或多个表达式组成的语句。有两个解析器和两个评估器, 一个用于Makefile的语句,另一个用于Makefile的表达式。

GNU make的大部分用户可能不太关心评估器。但是,GNU make的评估器非常强大,并且是图灵完整的。对于Android的空构建,大部分时间都花在这个阶段。其他任务,例如构建依赖关系图和调用构建目标的stat函数,并不是瓶颈。这将是一个非常具体的Android特性。Android的构建系统使用了大量的GNU make黑魔法。

评估器输出构建规则(build rules)和变量表(variable table)的列表。依赖构建器从构建规则列表中创建依赖图(dependency graph)。注意这一步不使用变量表。

然后将使用执行器或Ninja生成器。无论哪种方式,Kati再次为命令行运行其评估器。该变量表再次用于此步骤。

4 kati是如何生成的

4.1 代码位置

Android10.0中kati的代码位置:build/kati,AOSP中自带编译好的ckati。

prebuilts/build-tools/linux-x86/asan/bin/ckati
prebuilts/build-tools/linux-x86/bin/ckati
prebuilts/build-tools/darwin-x86/bin/ckati

kati也是它也是一个独立发布的项目,在GitHub上的位置是google/kati。

git clone https://github.com/google/kati.git

4.2 Kati的使用方法

在Android的编译过程中,ckati会自动被使用,无须开发者担心。

单独使用时,在包含Makefile的目录下,执行ckati,效果与make基本相同。执行ckati --ninja,可以根据Makefile生成build.ninja文件,并且附带env-aosp_arm.sh和ninja-aosp_arm.sh 。通过env-aosp_arm.sh来配置环境,通过执行./ninja-aosp_arm.sh来启动Ninja、使用build.ninja编译。

除了--ninja以外,ckati支持很多其它参数。比如,和make一样,可以通过-f指定Makefile位置,通过-j指定线程数。另外,在kati项目的m2n脚本中,就可以看到以下的复杂用法:

${kati} --ninja ${ninja_suffix_flag} --ignore_optional_include=out/%.P --ignore_dirty=out/% --use_find_emulator --detect_android_echo --detect_depfiles --gen_all_targets ${goma_flag} ${extra_flags} ${targets}

4.3 生成kati

Android10.0编译时都是使用编译好的ckati(prebuilts/build-tools/linux-x86/bin/ckati)进行makefile的转换,不会再编译一下ckati,但是我们可以看看ckati是如何被编译出来的。

    ckati的编译方法:

cd  .../build/kati
make ckati

会在build/kati的目录中生成一个二进制文件ckati

4.4 KATI生成过程

在build/kati 中有个Makefile,执行make时,会编译其中的内容。

[build/kati/Makefile]
all: ckati ckati_testsinclude Makefile.kati
include Makefile.ckatitest: all ckati_testsgo test --ckati --ninjaclean: ckati_clean.PHONY: test clean ckati_tests

Makefile中有两个目标:ckat和ckati_tests,其中ckati就是我们要编译出来的内容,它对应的Makefile为 Makefile.ckati。

4.4.1 Makefile.ckati

从Makefile.ckati中可以看出,ckati通过C++进行编译,而且依赖于KATI_CXX_OBJS和KATI_CXX_GENERATED_OBJS。

# Makefile.ckati
# Rule to build ckati into KATI_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 source files into object files in KATI_INTERMEDIATES_PATH
$(KATI_CXX_OBJS) $(KATI_CXX_TEST_OBJS): $(KATI_INTERMEDIATES_PATH)/%.o: $(KATI_SRC_PATH)/%.cc@mkdir -p $(dir $@)$(KATI_CXX) -c -std=c++11 $(KATI_CXXFLAGS) -o $@ $<# Rule to build generated source 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 $@ $<

  ckati的编译log:

g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o main.o main.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o ninja.o ninja.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o parser.o parser.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o regen.o regen.cc
g++ -c -std=c++11 -g -W -Wall -MMD -MP -O -DNOLOG -march=native -o rule.o rule.cc

4.4.2 [/build/kati/main.cc]

ckati的入口在main.cc中

调用栈如下:

4.4.2.1 main()

main()主要步骤:

  • 进行环境的初始化,初始化makefile解析器,包括include、define、ifndef等语法规则

  • 解析ckati传入的参数内容,例如:"--ninja"\"--regen"等

  • 执行编译,最终生成build-xxxx.ninja文件

  • 退出ckati

接下来针对相关的函数,进行分析。

[/build/kati/main.cc]
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 no makefile 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;
}

4.4.2.2 Flags::Parse()

解析ckati传入的参数内容,例如:"--ninja"\"--regen"等

void Flags::Parse(int argc, char** argv) {
...for (int i = 1; i < argc; i++) {const char* arg = argv[i];bool should_propagate = true;int pi = i;if (!strcmp(arg, "-f")) {makefile = argv[++i];should_propagate = false;} else if (!strcmp(arg, "-c")) {is_syntax_check_only = true;} else if (!strcmp(arg, "-i")) {is_dry_run = true;} else if (!strcmp(arg, "-s")) {is_silent_mode = true;} else if (!strcmp(arg, "-d")) {enable_debug = true;} else if (!strcmp(arg, "--kati_stats")) {enable_stat_logs = true;} else if (!strcmp(arg, "--warn")) {enable_kati_warnings = true;} else if (!strcmp(arg, "--ninja")) {generate_ninja = true;} else if (!strcmp(arg, "--empty_ninja_file")) {generate_empty_ninja = true;} else if (!strcmp(arg, "--gen_all_targets")) {gen_all_targets = true;} else if (!strcmp(arg, "--regen")) {// TODO: Make this default.regen = true;}
...}
}

4.4.2.3 Run()

根据传入的参数包含--ninja时,需要执行GenerateNinja(),Kati如果指定了--regen标志,则Kati会检查你的环境中的任何内容是否在上次运行后发生更改。如果Kati认为它不需要重新生成Ninja文件,它会很快完成。对于Android,第一次运行Kati需要接近30秒,但第二次运行只需要1秒。

static int Run(const vector<Symbol>& targets,const vector<StringPiece>& cl_vars,const string& orig_args) {double start_time = GetTime();//传入参数包含--ninja 和 (--regen 或者--dump_kati_stamp)时,进入该流程if (g_flags.generate_ninja && (g_flags.regen || g_flags.dump_kati_stamp)) {ScopedTimeReporter tr("regen check time");if (!NeedsRegen(start_time, orig_args)) {fprintf(stderr, "No need to regenerate ninja file\n");return 0;}if (g_flags.dump_kati_stamp) {printf("Need to regenerate ninja file\n");return 0;}ClearGlobCache();}...
//传入参数包含--ninja时,需要执行GenerateNinja()if (g_flags.generate_ninja) {ScopedTimeReporter tr("generate ninja time");GenerateNinja(nodes, ev.get(), orig_args, start_time);ev->DumpStackStats();return 0;}...return 0;
}

4.4.2.4  GenerateNinja()

GenerateNinja()会先初始化一个 NinjaGenerator的结构,然后解析之前的makefile,并且将node进行整理,会将所依赖的.o;.a; .so进行归类,在整理好了依赖之后,会将所的步骤写入文件build-xxxx.ninja中。

void GenerateNinja(const vector<NamedDepNode>& nodes,Evaluator* ev,const string& orig_args,double start_time) {NinjaGenerator ng(ev, start_time);        //初始化了一个 NinjaGenerator的结构ng.Generate(nodes, orig_args);
}void Generate(const vector<NamedDepNode>& nodes, const string& orig_args) {unlink(GetNinjaStampFilename().c_str());PopulateNinjaNodes(nodes); //对前面include的makefile进行解析,并且将node进行整理,会将所依赖的.o;.a; .so进行归类GenerateNinja();  //在整理好了依赖之后,会将所的步骤写入文件build-xxxx.ninja中GenerateShell();GenerateStamp(orig_args);}

GenerateNinja() 会产生build-aosp_arm.ninja

GenerateShell()会产生env-aosp_arm.sh、ninja-aosp_arm.sh

这里我们主要关注build-aosp_arm.ninja的生成过程。

void GenerateNinja() {ScopedTimeReporter tr("ninja gen (emit)");fp_ = fopen(GetNinjaFilename().c_str(), "wb");if (fp_ == NULL)PERROR("fopen(build.ninja) failed");fprintf(fp_, "# Generated by kati %s\n", kGitVersion);fprintf(fp_, "\n");if (!used_envs_.empty()) {fprintf(fp_, "# Environment variables used:\n");for (const auto& p : used_envs_) {fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());}fprintf(fp_, "\n");}if (!g_flags.no_ninja_prelude) {if (g_flags.ninja_dir) {fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);}fprintf(fp_, "pool local_pool\n");fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);fprintf(fp_, "build _kati_always_build_: phony\n\n");}unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));CHECK(g_flags.num_jobs);int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;int num_tasks = nodes_.size() / num_nodes_per_task + 1;vector<ostringstream> bufs(num_tasks);for (int i = 0; i < num_tasks; i++) {tp->Submit([this, i, num_nodes_per_task, &bufs]() {int l =min(num_nodes_per_task * (i + 1), static_cast<int>(nodes_.size()));for (int j = num_nodes_per_task * i; j < l; j++) {EmitNode(nodes_[j], &bufs[i]);}});}tp->Wait();if (!g_flags.generate_empty_ninja) {for (const ostringstream& buf : bufs) {fprintf(fp_, "%s", buf.str().c_str());}}SymbolSet used_env_vars(Vars::used_env_vars());// PATH changes $(shell).used_env_vars.insert(Intern("PATH"));for (Symbol e : used_env_vars) {StringPiece val(getenv(e.c_str()));used_envs_.emplace(e.str(), val.as_string());}string default_targets;if (g_flags.targets.empty() || g_flags.gen_all_targets) {CHECK(default_target_);default_targets = EscapeBuildTarget(default_target_->output);} else {for (Symbol s : g_flags.targets) {if (!default_targets.empty())default_targets += ' ';default_targets += EscapeBuildTarget(s);}}if (!g_flags.generate_empty_ninja) {fprintf(fp_, "\n");fprintf(fp_, "default %s\n", default_targets.c_str());}fclose(fp_);}

Kati认为,当更改以下任一项时,需要重新生成Ninja文件:

  • 传递给Kati的命令行标志

  • 用于生成上一个ninja文件的Makefile的时间戳

  • 评估Makefile时使用的环境变量

  • $(wildcard ...)的结果

  • $(shell ...)的结果

5 kati执行过程

在第三节的make编译过程中,我们知道soong_ui执行编译时,会调用ckati把makefile编译成*.ninja文件,这里我们就看看具体的流程是如何执行的。

5.1 soong_ui build调用栈

在之前的编译过程中,其中第三步和第四步,运行runKatiBuild()和runKatiPackage(),加载core/main.mk和packaging/main.mk,搜集所有的Android.mk文件,分别生成out/build-aosp_arm.ninja 和out/build-aosp_arm-package.ninja,这就是kati/ckati的编译过程。

下面我们来一起看看具体的执行过程。

5.2 runKatiBuild()


[/build/soong/ui/build/kati.go]
func runKatiBuild(ctx Context, config Config) {ctx.BeginTrace(metrics.RunKati, "kati build")defer ctx.EndTrace()args := []string{"--writable", config.OutDir() + "/","-f", "build/make/core/main.mk",}// PDK builds still uses a few implicit rulesif !config.IsPdkBuild() {args = append(args, "--werror_implicit_rules")}if !config.BuildBrokenDupRules() {args = append(args, "--werror_overriding_commands")}if !config.BuildBrokenPhonyTargets() {args = append(args,"--werror_real_to_phony","--werror_phony_looks_real","--werror_writable")}args = append(args, config.KatiArgs()...)args = append(args,"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),"SOONG_ANDROID_MK="+config.SoongAndroidMk(),"TARGET_DEVICE_DIR="+config.TargetDeviceDir(),"KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
}

这里的参数args,通过fmt打印后,内容为:

[--writable out/ -f build/make/core/main.mk --werror_implicit_rules --werror_overriding_commands --werror_real_to_phony --werror_phony_looks_real --werror_writable SOONG_MAKEVARS_MK=out/soong/make_vars-aosp_arm.mk SOONG_ANDROID_MK=out/soong/Android-aosp_arm.mk TARGET_DEVICE_DIR=build/target/board/generic KATI_PACKAGE_MK_DIR=out/target/product/generic/obj/CONFIG/kati_packaging]

这里指定了makefile的入口为build/make/core/main.mk,编译target的目录为build/target/board/generic


func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {executable := config.PrebuiltBuildTool("ckati")args = append([]string{"--ninja","--ninja_dir=" + config.OutDir(),"--ninja_suffix=" + config.KatiSuffix() + extraSuffix,"--no_ninja_prelude","--regen","--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),"--detect_android_echo","--color_warnings","--gen_all_targets","--use_find_emulator","--werror_find_emulator","--no_builtin_rules","--werror_suffix_rules","--warn_real_to_phony","--warn_phony_looks_real","--top_level_phony","--kati_stats",}, args...)if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {args = append(args, "--empty_ninja_file")}cmd := Command(ctx, config, "ckati", executable, args...)cmd.Sandbox = katiSandboxpipe, err := cmd.StdoutPipe()if err != nil {ctx.Fatalln("Error getting output pipe for ckati:", err)}cmd.Stderr = cmd.StdoutenvFunc(cmd.Environment)if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {u, err := user.Current()if err != nil {ctx.Println("Failed to get current user")}cmd.Environment.Set("BUILD_USERNAME", u.Username)}if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok {hostname, err := os.Hostname()if err != nil {ctx.Println("Failed to read hostname")}cmd.Environment.Set("BUILD_HOSTNAME", hostname)}cmd.StartOrFatal()status.KatiReader(ctx.Status.StartTool(), pipe)cmd.WaitOrFatal()
}

调用Command(),根据传入的参数,生成一个cmd的结构,其中相关参数如下:

args:

[--ninja --ninja_dir=out --ninja_suffix=-aosp_arm --no_ninja_prelude --regen --ignore_optional_include=out/%.P --detect_android_echo --color_warnings --gen_all_targets --use_find_emulator --werror_find_emulator --no_builtin_rules --werror_suffix_rules --warn_real_to_phony --warn_phony_looks_real --top_level_phony --kati_stats --writable out/ -f build/make/core/main.mk --werror_implicit_rules --werror_overriding_commands --werror_real_to_phony --werror_phony_looks_real --werror_writable SOONG_MAKEVARS_MK=out/soong/make_vars-aosp_arm.mk SOONG_ANDROID_MK=out/soong/Android-aosp_arm.mk TARGET_DEVICE_DIR=build/target/board/generic KATI_PACKAGE_MK_DIR=out/target/product/generic/obj/CONFIG/kati_packaging]

config:

{%!s(*build.configImpl=&{[] false 0xc00000ecc0 out/dist 16 1 false false false false [] [droid] -aosp_arm generic build/target/board/generic false false false false true})}

executable:

prebuilts/build-tools/linux-x86/bin/ckati

Command封装方法如下:

[/build/soong/ui/build/exec.go]
func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd {ret := &Cmd{Cmd:         exec.CommandContext(ctx.Context, executable, args...),Environment: config.Environment().Copy(),Sandbox:     noSandbox,ctx:    ctx,config: config,name:   name,}return ret
}

根据上述的相关参数可知,最终是调用系统准备好的prebuilts/build-tools/linux-x86/bin/ckati参与编译,其中传入的参数有 --ninja\ --regen\--detect_android_echo 等,最终编译出build-aosp_arm.ninja。具体的kati实现过程比较复杂,这里就不详细展开,有兴趣的朋友,可以把/build/kati中的C++源码详细的分析一下。

6.总结

Kati的主要功能就是把Makefile转换成build-xxx.ninja文件从而来参与系统编译,随着Android逐步消除版本中的Makefile文件,Kati最终也会退出Android的历史舞台。

参考:

《kati-INTERNALS.md》

我的微信公众号:IngresGe

Kati详解-Android10.0编译系统(五)相关推荐

  1. 编译系统总结篇-Android10.0编译系统(十一)

    摘要:Android10.0编译系统总结 阅读本文大约需要花费20分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关 ...

  2. Ninja提升编译速度的方法-Android10.0编译系统(十)

    摘要:如何通过Ninja来提升Android编译速度 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢 ...

  3. Ninja简介-Android10.0编译系统(九)

    摘要:Ninja具体干了什么? 阅读本文大约需要花费16分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众 ...

  4. Android.bp 语法浅析-Android10.0编译系统(八)

    摘要:Blueprint解析Android.bp到ninja的代码流程时如何走的? 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...

  5. Blueprint代码详细分析-Android10.0编译系统(七)

    摘要:Blueprint解析Android.bp到ninja的代码流程时如何走的? 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...

  6. Blueprint简介-Android10.0编译系统(六)

    摘要:Android.bp由Blueprint进行解析翻译,最终通过soong build编译成ninja文件,那么Blueprint是什么呢? 阅读本文大约需要花费10分钟. 文章首发微信公众号:I ...

  7. Image打包流程-Android10.0编译系统(四)

    摘要:本节主要来进行Android10.0 Image打包流程,理解system.img是如何打包的 阅读本文大约需要花费28分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源 ...

  8. make编译过程-Android10.0编译系统(三)

    摘要:本节主要来进行Android10.0 编译系统的make过程 阅读本文大约需要花费29分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计, ...

  9. android增加内置存储分区,详解Android10的分区存储机制(Scoped Storage)适配教程

    1. 简介 大家应该都有过这样的体会,手机用着用着里面就充斥着各种不懂的文件夹和文件.甚至是连已经删除的软件的文件夹还存在. 为什么会发生的这样的问题呢? 因为google的缺席,导致android生 ...

最新文章

  1. c语言编程加密和解密,请问有学长做过这个程序设计的吗?C语言写加密解密问题,跪求代码!...
  2. vim java 注释_centOS7 下的vim java补全
  3. 这部计算机27厘米宽英语怎么说,24/27/32寸电脑显示器尺寸多大?长宽多少厘米?显示屏长宽与面积的算法...
  4. 泛型字典 0104 c#
  5. 行为型模式:模板方法 1
  6. 给页面加上loading加载效果
  7. 南昌大学百年校庆游戏彩球传说开源项目
  8. 为何汇新云汇聚了如此多的产品经理?
  9. Android-弹窗AlterDialog对话框使用全解析
  10. 经典语录(个人喜欢)
  11. Java生成临时文件
  12. 隐形窗口_建立网站时要考虑的隐形(但至关重要)注意事项
  13. 由pytorch中的super().__init__到python中的测试
  14. 第7章 集成方法、随机森林
  15. IO流_IO流小结图解
  16. 码农必备,一款超好用Json编辑工具
  17. 39.html5的动画(animation)
  18. android根据url加载图片路径,初学Android——通过URL加载图片
  19. Cookie、sessionStorage和localStorage
  20. Compareprice多平台跟得物比价

热门文章

  1. SDL 库安装、环境配置与使用
  2. linux文件类型为ext4怎么扩展,如何扩展ext4分区和文件系统?
  3. 7.1 pdo 宝塔面板php_小白入门基础傻瓜式操作PHP面板安装使用_Bt宝塔面板
  4. cds.data:=dsp.data赋值有时会出现AV错误剖析
  5. Maven 使用代理下载依赖
  6. 一些常用字符串操作函数的内部实现
  7. 设计模式系列(一)单例模式
  8. HEVC算法和体系结构:环路滤波技术
  9. 服务器安全股v4.0正式版发布 防火墙效能更强
  10. 道家遁甲式(又名道家奇门)