热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Kati详解Android10.0编译系统(五)

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

摘要:本节主要来讲解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&#64;mkdir -p $(dir $&#64;)$(KATI_CXX) -c -std&#61;c&#43;&#43;11 $(KATI_CXXFLAGS) -o $&#64; $<# 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&#64;mkdir -p $(dir $&#64;)$(KATI_CXX) -c -std&#61;c&#43;&#43;11 $(KATI_CXXFLAGS) -o $&#64; $<

  ckati的编译log:

g&#43;&#43; -c -std&#61;c&#43;&#43;11 -g -W -Wall -MMD -MP -O -DNOLOG -march&#61;native -o main.o main.cc
g&#43;&#43; -c -std&#61;c&#43;&#43;11 -g -W -Wall -MMD -MP -O -DNOLOG -march&#61;native -o ninja.o ninja.cc
g&#43;&#43; -c -std&#61;c&#43;&#43;11 -g -W -Wall -MMD -MP -O -DNOLOG -march&#61;native -o parser.o parser.cc
g&#43;&#43; -c -std&#61;c&#43;&#43;11 -g -W -Wall -MMD -MP -O -DNOLOG -march&#61;native -o regen.o regen.cc
g&#43;&#43; -c -std&#61;c&#43;&#43;11 -g -W -Wall -MMD -MP -O -DNOLOG -march&#61;native -o rule.o rule.cc

 


4.4.2 [/build/kati/main.cc]

    ckati的入口在main.cc中

    调用栈如下&#xff1a;

 


4.4.2.1 main(&#xff09;

main()主要步骤&#xff1a;


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

  • 解析ckati传入的参数内容&#xff0c;例如&#xff1a;"--ninja"\"--regen"等

  • 执行编译&#xff0c;最终生成build-xxxx.ninja文件

  • 退出ckati

接下来针对相关的函数&#xff0c;进行分析。

[/build/kati/main.cc]
int main(int argc, char* argv[]) {if (argc >&#61; 2 && !strcmp(argv[1], "--realpath")) {HandleRealpath(argc - 2, argv &#43; 2);return 0;}Init();string orig_args;for (int i &#61; 0; i }

 


4.4.2.2 Flags::Parse()

解析ckati传入的参数内容&#xff0c;例如&#xff1a;"--ninja"\"--regen"等

void Flags::Parse(int argc, char** argv) {
...for (int i &#61; 1; i ...}
}

 


4.4.2.3 Run()

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

static int Run(const vector& targets,const vector& cl_vars,const string& orig_args) {double start_time &#61; GetTime();//传入参数包含--ninja 和 (--regen 或者--dump_kati_stamp)时&#xff0c;进入该流程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时&#xff0c;需要执行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的结构&#xff0c;然后解析之前的makefile&#xff0c;并且将node进行整理&#xff0c;会将所依赖的.o;.a; .so进行归类&#xff0c;在整理好了依赖之后&#xff0c;会将所的步骤写入文件build-xxxx.ninja中。

void GenerateNinja(const vector& 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& nodes, const string& orig_args) {unlink(GetNinjaStampFilename().c_str());PopulateNinjaNodes(nodes); //对前面include的makefile进行解析&#xff0c;并且将node进行整理&#xff0c;会将所依赖的.o;.a; .so进行归类GenerateNinja(); //在整理好了依赖之后&#xff0c;会将所的步骤写入文件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_ &#61; fopen(GetNinjaFilename().c_str(), "wb");if (fp_ &#61;&#61; 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&#61;%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 &#61; %s\n\n", g_flags.ninja_dir);}fprintf(fp_, "pool local_pool\n");fprintf(fp_, " depth &#61; %d\n\n", g_flags.num_jobs);fprintf(fp_, "build _kati_always_build_: phony\n\n");}unique_ptr tp(NewThreadPool(g_flags.num_jobs));CHECK(g_flags.num_jobs);int num_nodes_per_task &#61; nodes_.size() / (g_flags.num_jobs * 10) &#43; 1;int num_tasks &#61; nodes_.size() / num_nodes_per_task &#43; 1;vector bufs(num_tasks);for (int i &#61; 0; i Submit([this, i, num_nodes_per_task, &bufs]() {int l &#61;min(num_nodes_per_task * (i &#43; 1), static_cast(nodes_.size()));for (int j &#61; num_nodes_per_task * i; j 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 &#61; EscapeBuildTarget(default_target_->output);} else {for (Symbol s : g_flags.targets) {if (!default_targets.empty())default_targets &#43;&#61; &#39; &#39;;default_targets &#43;&#61; EscapeBuildTarget(s);}}if (!g_flags.generate_empty_ninja) {fprintf(fp_, "\n");fprintf(fp_, "default %s\n", default_targets.c_str());}fclose(fp_);}

 

Kati认为&#xff0c;当更改以下任一项时&#xff0c;需要重新生成Ninja文件&#xff1a;


  • 传递给Kati的命令行标志

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

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

  • $(wildcard ...)的结果

  • $(shell ...)的结果

 


5 kati执行过程

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

 


5.1 soong_ui build调用栈

    在之前的编译过程中&#xff0c;其中第三步和第四步&#xff0c;运行runKatiBuild()和runKatiPackage()&#xff0c;加载core/main.mk和packaging/main.mk&#xff0c;搜集所有的Android.mk文件&#xff0c;分别生成out/build-aosp_arm.ninja 和out/build-aosp_arm-package.ninja&#xff0c;这就是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 :&#61; []string{"--writable", config.OutDir() &#43; "/","-f", "build/make/core/main.mk",}// PDK builds still uses a few implicit rulesif !config.IsPdkBuild() {args &#61; append(args, "--werror_implicit_rules")}if !config.BuildBrokenDupRules() {args &#61; append(args, "--werror_overriding_commands")}if !config.BuildBrokenPhonyTargets() {args &#61; append(args,"--werror_real_to_phony","--werror_phony_looks_real","--werror_writable")}args &#61; append(args, config.KatiArgs()...)args &#61; append(args,"SOONG_MAKEVARS_MK&#61;"&#43;config.SoongMakeVarsMk(),"SOONG_ANDROID_MK&#61;"&#43;config.SoongAndroidMk(),"TARGET_DEVICE_DIR&#61;"&#43;config.TargetDeviceDir(),"KATI_PACKAGE_MK_DIR&#61;"&#43;config.KatiPackageMkDir())runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
}

这里的参数args&#xff0c;通过fmt打印后&#xff0c;内容为&#xff1a;

[--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&#61;out/soong/make_vars-aosp_arm.mk SOONG_ANDROID_MK&#61;out/soong/Android-aosp_arm.mk TARGET_DEVICE_DIR&#61;build/target/board/generic KATI_PACKAGE_MK_DIR&#61;out/target/product/generic/obj/CONFIG/kati_packaging]

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


func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {executable :&#61; config.PrebuiltBuildTool("ckati")args &#61; append([]string{"--ninja","--ninja_dir&#61;" &#43; config.OutDir(),"--ninja_suffix&#61;" &#43; config.KatiSuffix() &#43; extraSuffix,"--no_ninja_prelude","--regen","--ignore_optional_include&#61;" &#43; 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 &#61; append(args, "--empty_ninja_file")}cmd :&#61; Command(ctx, config, "ckati", executable, args...)cmd.Sandbox &#61; katiSandboxpipe, err :&#61; cmd.StdoutPipe()if err !&#61; nil {ctx.Fatalln("Error getting output pipe for ckati:", err)}cmd.Stderr &#61; cmd.StdoutenvFunc(cmd.Environment)if _, ok :&#61; cmd.Environment.Get("BUILD_USERNAME"); !ok {u, err :&#61; user.Current()if err !&#61; nil {ctx.Println("Failed to get current user")}cmd.Environment.Set("BUILD_USERNAME", u.Username)}if _, ok :&#61; cmd.Environment.Get("BUILD_HOSTNAME"); !ok {hostname, err :&#61; os.Hostname()if err !&#61; nil {ctx.Println("Failed to read hostname")}cmd.Environment.Set("BUILD_HOSTNAME", hostname)}cmd.StartOrFatal()status.KatiReader(ctx.Status.StartTool(), pipe)cmd.WaitOrFatal()
}

调用Command()&#xff0c;根据传入的参数&#xff0c;生成一个cmd的结构&#xff0c;其中相关参数如下&#xff1a;

args:

[--ninja --ninja_dir&#61;out --ninja_suffix&#61;-aosp_arm --no_ninja_prelude --regen --ignore_optional_include&#61;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&#61;out/soong/make_vars-aosp_arm.mk SOONG_ANDROID_MK&#61;out/soong/Android-aosp_arm.mk TARGET_DEVICE_DIR&#61;build/target/board/generic KATI_PACKAGE_MK_DIR&#61;out/target/product/generic/obj/CONFIG/kati_packaging]

config:

{%!s(*build.configImpl&#61;&{[] 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封装方法如下&#xff1a;

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

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

 


6.总结

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

 

参考&#xff1a;

《kati-INTERNALS.md》

我的微信公众号&#xff1a;IngresGe

 

 

 

 


推荐阅读
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 微信商户扫码支付 java开发 [从零开发]
    这个教程可以用作了解扫码支付的整体运行过程,已经实现了前端扫码,记录订单,回调等一套完整的微信扫码支付。相关链接:微信支 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的步骤和方法
    本文介绍了在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的详细步骤和方法。首先需要下载最新的Java SE Development Kit 9发行版,然后按照给出的Shell命令行方式进行安装。详细的步骤和方法请参考正文内容。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Windows 7 部署工具DISM学习(二)添加补丁的步骤详解
    本文详细介绍了在Windows 7系统中使用部署工具DISM添加补丁的步骤。首先需要将光驱中的安装文件复制到指定文件夹,并进行挂载。然后将需要的MSU补丁解压并集成到系统中。文章给出了具体的命令和操作步骤,帮助读者完成补丁的添加过程。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
author-avatar
一粒星尘ch
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有