Kati详解-Android10.0编译系统(五)
摘要:本节主要来讲解Kati把Makefile编译成build-xxx.ninja,那么Kati是什么? 是如何工作的呢?
阅读本文大约需要花费24分钟。
文章首发微信公众号:IngresGe
专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!
欢迎关注我的公众号!
[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析
[Android取经之路] 系列文章:
《系统启动篇》
- Android系统架构
- Android是怎么启动的
- Android 10.0系统启动之init进程
- Android10.0系统启动之Zygote进程
- Android 10.0 系统启动之SystemServer进程
- Android 10.0 系统服务之ActivityMnagerService
- Android10.0系统启动之Launcher(桌面)启动流程
- Android10.0应用进程创建过程以及Zygote的fork流程
- Android 10.0 PackageManagerService(一)工作原理及启动流程
- Android 10.0 PackageManagerService(二)权限扫描
- Android 10.0 PackageManagerService(三)APK扫描
- Android 10.0 PackageManagerService(四)APK安装流程
《日志系统篇》
- Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
- Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
- Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
- Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现
《Binder通信原理》:
- Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
- Android10.0 Binder通信原理(二)-Binder入门篇
- Android10.0 Binder通信原理(三)-ServiceManager篇
- Android10.0 Binder通信原理(四)-Native-C\C++实例分析
- Android10.0 Binder通信原理(五)-Binder驱动分析
- Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
- Android10.0 Binder通信原理(七)-Framework binder示例
- Android10.0 Binder通信原理(八)-Framework层分析
- Android10.0 Binder通信原理(九)-AIDL Binder示例
- Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
- Android10.0 Binder通信原理(十一)-Binder总结
《HwBinder通信原理》
- HwBinder入门篇-Android10.0 HwBinder通信原理(一)
- HIDL详解-Android10.0 HwBinder通信原理(二)
- HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
- HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
- HwServiceManager篇-Android10.0 HwBinder通信原理(五)
- Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
- Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
- JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
- JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
- HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
- HwBinder原理总结-Android10.0 HwBinder通信原理(十一)
《编译原理》
- 编译系统入门篇-Android10.0编译系统(一)
- 编译环境初始化-Android10.0编译系统(二)
- make编译过程-Android10.0编译系统(三)
- Image打包流程-Android10.0编译系统(四)
- Kati详解-Android10.0编译系统(五)
- Blueprint简介-Android10.0编译系统(六)
- 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编译系统(五)相关推荐
- 编译系统总结篇-Android10.0编译系统(十一)
摘要:Android10.0编译系统总结 阅读本文大约需要花费20分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关 ...
- Ninja提升编译速度的方法-Android10.0编译系统(十)
摘要:如何通过Ninja来提升Android编译速度 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢 ...
- Ninja简介-Android10.0编译系统(九)
摘要:Ninja具体干了什么? 阅读本文大约需要花费16分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢迎关注我的公众 ...
- Android.bp 语法浅析-Android10.0编译系统(八)
摘要:Blueprint解析Android.bp到ninja的代码流程时如何走的? 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...
- Blueprint代码详细分析-Android10.0编译系统(七)
摘要:Blueprint解析Android.bp到ninja的代码流程时如何走的? 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Andro ...
- Blueprint简介-Android10.0编译系统(六)
摘要:Android.bp由Blueprint进行解析翻译,最终通过soong build编译成ninja文件,那么Blueprint是什么呢? 阅读本文大约需要花费10分钟. 文章首发微信公众号:I ...
- Image打包流程-Android10.0编译系统(四)
摘要:本节主要来进行Android10.0 Image打包流程,理解system.img是如何打包的 阅读本文大约需要花费28分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源 ...
- make编译过程-Android10.0编译系统(三)
摘要:本节主要来进行Android10.0 编译系统的make过程 阅读本文大约需要花费29分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计, ...
- android增加内置存储分区,详解Android10的分区存储机制(Scoped Storage)适配教程
1. 简介 大家应该都有过这样的体会,手机用着用着里面就充斥着各种不懂的文件夹和文件.甚至是连已经删除的软件的文件夹还存在. 为什么会发生的这样的问题呢? 因为google的缺席,导致android生 ...
最新文章
- c语言编程加密和解密,请问有学长做过这个程序设计的吗?C语言写加密解密问题,跪求代码!...
- vim java 注释_centOS7 下的vim java补全
- 这部计算机27厘米宽英语怎么说,24/27/32寸电脑显示器尺寸多大?长宽多少厘米?显示屏长宽与面积的算法...
- 泛型字典 0104 c#
- 行为型模式:模板方法 1
- 给页面加上loading加载效果
- 南昌大学百年校庆游戏彩球传说开源项目
- 为何汇新云汇聚了如此多的产品经理?
- Android-弹窗AlterDialog对话框使用全解析
- 经典语录(个人喜欢)
- Java生成临时文件
- 隐形窗口_建立网站时要考虑的隐形(但至关重要)注意事项
- 由pytorch中的super().__init__到python中的测试
- 第7章 集成方法、随机森林
- IO流_IO流小结图解
- 码农必备,一款超好用Json编辑工具
- 39.html5的动画(animation)
- android根据url加载图片路径,初学Android——通过URL加载图片
- Cookie、sessionStorage和localStorage
- Compareprice多平台跟得物比价
热门文章
- SDL 库安装、环境配置与使用
- linux文件类型为ext4怎么扩展,如何扩展ext4分区和文件系统?
- 7.1 pdo 宝塔面板php_小白入门基础傻瓜式操作PHP面板安装使用_Bt宝塔面板
- cds.data:=dsp.data赋值有时会出现AV错误剖析
- Maven 使用代理下载依赖
- 一些常用字符串操作函数的内部实现
- 设计模式系列(一)单例模式
- HEVC算法和体系结构:环路滤波技术
- 服务器安全股v4.0正式版发布 防火墙效能更强
- 道家遁甲式(又名道家奇门)