生成一个Android应用。 带星号的任务



哈Ha! 在夏天,我在Summer Droid Meetup上发表了有关构建Android应用程序的报告。 视频版本可以在以下位置找到: habr.com/ru/company/funcorp/blog/462825 。 对于那些喜欢阅读更多内容的人,我只是写了这篇文章。

关于它是什么-一个Android应用程序。 我们将以不同的方式收集Hello,world !:从控制台开始,看看在构建系统的幕后发生了什么,然后回到过去,记住Maven并了解现代的Bazel和Buck解决方案。 最后,所有这些都是可比的。

当我们开始一个新项目时,我们考虑了组装系统的可能更改。 在我们看来,这是寻找Gradle替代品的好机会。 而且,从一开始就比翻译现有项目容易。 以下Gradle缺陷将我们推向了这一步:

  • 他肯定会遇到增量组装方面的问题,尽管他可以看到这方面的进展。
  • 他在大型整体项目上做得不好。
  • 恶魔开始很长时间了。
  • 对运行机器的要求很高。

APK


首先,请记住Android应用程序由什么组成:编译后的代码,资源和AndroidManifest.xml。



来源以Android虚拟机可以使用的特殊dex格式包含在classes.dex文件中(可能有多个文件,具体取决于应用程序的大小)。 今天,它是在较旧的设备-Dalvik上的ART。 此外,您可以找到lib文件夹,其中本地源位于子文件夹中。 它们的名称取决于目标处理器的体系结构,例如x86,arm等。 如果您使用exoplayer,则您可能拥有lib。 还有aidl文件夹,其中包含进程间通信接口。 如果您需要访问在另一个进程中运行的服务,它们将派上用场。 此类接口既可以在Android本身中使用,也可以在GooglePlayServices内部使用。

res文件夹中包含各种未编译的资源,例如图片。 所有已编译的资源(例如样式,线条等)都将合并到resource.arsc文件中。 通常,他们在资产文件夹中放置所有不适合资源的内容,例如自定义字体。

除了所有这些,APK还包含AndroidManifest.xml。 在其中,我们描述了应用程序的各种组件,例如活动,服务,不同的权限等。 它采用二进制形式,并且要查看内部,首先必须将其转换为人类可读的文件。

控制台


现在我们知道了应用程序的组成,我们可以尝试构建Hello,world! 从控制台使用Android SDK提供的工具。 这是理解构建系统如何工作的非常重要的一步,因为它们在某种程度上都依赖于这些实用程序。 由于该项目是用Kotlin编写的,因此我们需要命令行的编译器。 单独下载很容易。

应用程序的组装可以分为以下步骤:

  • 下载并解压缩项目所依赖的所有库。 在我的情况下,这是appcompat向后兼容库,该库又依赖于appcompat-core,因此我们也将其抽出。
  • 生成R.java。 这个奇妙的类包含应用程序中所有资源的标识符,并用于通过代码访问它们。
  • 我们将源代码编译为字节码,然后将其转换为Dex,因为Android虚拟机不知道如何使用常规字节码;
  • 我们将所有内容打包到APK中,但首先将所有不可压缩的资源(例如图片)相对于文件的开头对齐。 这使得APK大小完全微不足道的价格大大提高了其工作速度。 因此,系统可以使用mmap()函数将资源直接映射到RAM。
  • 签署申请。 此过程可保护APK的完整性并确认作者身份。 并因此,例如,Play Market可以验证该应用程序是您构建的。

构建脚本
function preparedir() { rm -r -f $1 mkdir $1 } PROJ="src/main" LIBS="libs" LIBS_OUT_DIR="$LIBS/out" BUILD_TOOLS="$ANDROID_HOME/build-tools/28.0.3" ANDROID_JAR="$ANDROID_HOME/platforms/android-28/android.jar" DEBUG_KEYSTORE="$(echo ~)/.android/debug.keystore" GEN_DIR="build/generated" KOTLIN_OUT_DIR="$GEN_DIR/kotlin" DEX_OUT_DIR="$GEN_DIR/dex" OUT_DIR="out" libs_res="" libs_classes="" preparedir $LIBS_OUT_DIR aars=$(ls -p $LIBS | grep -v /) for filename in $aars; do DESTINATION=$LIBS_OUT_DIR/${filename%.*} echo "unpacking $filename into $DESTINATION" unzip -o -q $LIBS/$filename -d $DESTINATION libs_res="$libs_res -S $DESTINATION/res" libs_classes="$libs_classes:$DESTINATION/classes.jar" done preparedir $GEN_DIR $BUILD_TOOLS/aapt package -f -m \ -J $GEN_DIR \ -M $PROJ/AndroidManifest.xml \ -S $PROJ/res \ $libs_res \ -I $ANDROID_JAR --auto-add-overlay preparedir $KOTLIN_OUT_DIR compiledKotlin=$KOTLIN_OUT_DIR/compiled.jar kotlinc $PROJ/java $GEN_DIR -include-runtime \ -cp "$ANDROID_JAR$libs_classes"\ -d $compiledKotlin preparedir $DEX_OUT_DIR dex=$DEX_OUT_DIR/classes.dex $BUILD_TOOLS/dx --dex --output=$dex $compiledKotlin preparedir $OUT_DIR unaligned_apk=$OUT_DIR/unaligned.apk $BUILD_TOOLS/aapt package -f -m \ -F $unaligned_apk \ -M $PROJ/AndroidManifest.xml \ -S $PROJ/res \ $libs_res \ -I $ANDROID_JAR --auto-add-overlay cp $dex . $BUILD_TOOLS/aapt add $unaligned_apk classes.dex rm classes.dex aligned_apk=$OUT_DIR/aligned.apk $BUILD_TOOLS/zipalign -f 4 $unaligned_apk $aligned_apk $BUILD_TOOLS/apksigner sign --ks $DEBUG_KEYSTORE $aligned_apk 


根据这些图,结果表明,一个干净的程序集需要7秒钟,而增量程序集也不会落后于此,因为我们不缓存任何内容,每次都不重建任何内容。

马文


它是由Apache软件基金会的人员开发的,用于构建Java项目。 XML的构建配置描述。 Maven的早期版本是由Ant收集的,现在它们已切换到最新的稳定版本。

Maven的优点:

  • 它支持缓存程序集工件,即 增量构建应比干净构建快;
  • 能够解决第三方依赖性。 即 当您在Maven或Gradle配置中指定对第三方库的依赖时,您无需担心它所依赖的内容。
  • 有很多详细的文档,因为它已经投放市场很长时间了。
  • 如果您最近从后端进入了Android开发领域,那么它可能是一种熟悉的构建机制。

Maven的缺点:

  • 取决于进行组装的计算机上安装的Java的版本。
  • 现在,第三方开发人员支持Android插件:就我个人而言,我认为这是一个非常重大的缺陷,因为有一天他们可能会停止这样做。
  • 由于XML的冗长和繁琐,因此它不太适合描述构建配置。
  • 好了,正如我们稍后将看到的,至少在测试项目上,它的运行速度比Gradle慢。

要进行构建,我们需要创建pom.xml,其中包含对我们项目的描述。 在标题中,指示有关所收集工件的基本信息以及Kotlin的版本。

构建配置pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>myapplication</artifactId> <version>1.0.0</version> <packaging>apk</packaging> <name>My Application</name> <properties> <kotlin.version>1.3.41</kotlin.version> </properties> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>4.1.1.4</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>process-sources</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> <extensions>true</extensions> <configuration> <sdk> <platform>28</platform> <buildTools>28.0.3</buildTools> </sdk> <failOnNonStandardStructure>false</failOnNonStandardStructure> </configuration> </plugin> </plugins> </build> </project> 


就数字而言,一切都不太乐观。 干净的程序集大约需要12秒,而增量程序则需要10秒。这意味着Maven会以某种方式重用先前程序集的工件,或者,我认为,用于构建Android项目的插件很可能阻止它执行此操作

现在,他们正在使用所有这些功能,我认为,首先,该插件的创建者是来自简单性的人。 找不到有关此问题的更可靠的信息。

淡褐色


Google肠子里的工程师发明了Bazel来构建他们的项目,并在最近才将其转移到开源中。 对于构建配置的描述,使用了类似Python的Skylark或Starlark,两个名称都有一个地方。 它使用其自己的最新稳定发行版进行组装。

Bazel的优点:

  • 支持不同的编程语言。 如果您相信文档,那么他就会知道如何为iO,Android甚至后端收集项目。
  • 可以缓存以前收集的工件
  • 能够处理Maven依赖关系;
  • 我认为,Bazel对分布式项目的支持非常酷。 他可以将git仓库的特定修订版指定为依赖项,并在构建过程中将其卸载并缓存。 为了支持可伸缩性,Bazel可以例如在基于云的构建服务器上分发各种目标,这使您可以快速构建庞大的项目。

Bazel的缺点:

  • 所有这些魅力都很难维护,因为构建配置非常详细,并且在较低层次上描述了程序集;
  • 其中,Bazel似乎正在积极发展。 因此,一些示例不会被收集,并且所收集的示例可以使用已过时的功能(已标记为已弃用)。
  • 该文档现在也有很多不足之处,特别是与Gradle相比时;
  • 在小型项目中,预热和分析build-config可能比程序集本身花费更长的时间,我认为这不好。

从概念上讲,基本的Bazel配置由WORKSPACE和BUILD组成,在WORKSPACE中,我们描述了项目的各种全局事物;在BUILD中,直接包含了组装目标。
让我们描述一下WORKSPACE。 由于我们有一个Android项目,因此我们首先配置的是Android SDK。 此外,此处还导入了用于卸载配置的规则。 然后,由于该项目是用Kotlin编写的,因此我们必须为其指定规则。 在这里,我们直接从git存储库中引用特定的修订版。

工作空间
 android_sdk_repository( name = "androidsdk", api_level = 28, build_tools_version = "28.0.3" ) load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # # KOTLIN RULES # RULES_KOTLIN_VERSION = "990fcc53689c8b58b3229c7f628f843a60cb9f5c" http_archive( name = "io_bazel_rules_kotlin", url = "https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % RULES_KOTLIN_VERSION, strip_prefix = "rules_kotlin-%s" % RULES_KOTLIN_VERSION ) load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") kotlin_repositories() kt_register_toolchains() 


现在让我们开始构建。

首先,我们导入组装Kotlin的规则并描述我们要收集的内容。 在我们的例子中,这是一个Android应用程序,因此我们使用android_binary,在其中设置清单,最小SDK等。 我们的应用程序将取决于来源,因此我们在部门中提到它们,然后继续介绍它们是什么以及在哪里找到它们。 该代码还将取决于资源和appcompat库。 对于资源,我们使用通常的目标来组装android源,但是我们仅向其分配资源而没有java类。 并且我们描述了导入第三方库的一些规则。 它还提到了appcompat_core,appcompat依赖于此。

建立
 load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") android_binary( name = "app", custom_package = "com.example.myapplication", manifest = "src/main/AndroidManifest.xml", manifest_values = { "minSdkVersion": "15", }, deps = [ ":lib", ], ) kt_android_library( name = "lib", srcs = glob(["src/main/java/**/*"]), deps = [ ":res", ":appcompat", ], ) android_library( name = "res", resource_files = glob(["src/main/res/**/*"]), manifest = "src/main/AndroidManifest.xml", custom_package = "com.example.myapplication", ) aar_import( name = "appcompat", aar = "libs/appcompat.aar", deps = [ ":appcompat_core", ] ) aar_import( name = "appcompat_core", aar = "libs/core.aar", ) 


就这么小的项目而言,一切看起来都很可悲。 超过半分钟的时间来构建一个干净的世界,您好! -很多 增量构建时间也远非完美。

Bazel被其创建者(Google)用于他们的某些项目,包括服务器项目以及Dropbox和华为,后者为他们收集了移动应用程序。 而臭名昭著的Dagger 2也将进入Bazel。

巴克


它是由叛逃者从Google到Facebook发明的。 他使用Python来描述配置,然后迁移到今天提到的Skylark。 他突然要使用Ant系统。

降压优点:

  • 支持不同的编程语言,并且可以构建Andriod和iOS;
  • 可以缓存以前收集的工件
  • Buck制作了自己的dex实现,该实现比标准实现快,并且挂在系统守护程序上。 因此,它们节省了dex初始化的时间。 工程师确实做了很多优化。 例如,如果在更改库内部时接口没有更改,则Buck不会收集依赖于库的代码。 对于资源也是如此:如果标识符未更改,则在更改资源时,不会重新汇编代码。
  • 有一个可以将Buck藏在Gredlovsky配置后面的插件。 即 您会得到一个类似于正常的Gradle项目的内容,该项目实际上是通过Buck构建的。

缺点:

  • 它与Bazel一样难以维护。 即 在这里还必须描述清楚描述组装过程的低级规则;
  • 除其他事项外,Buck无法自行解决Maven依赖关系。

那么,您好,世界的装配配置如何? 通过降压? 在这里,我们描述了一个配置文件,其中表明我们要构建一个将用调试密钥签名的Android项目。 该应用程序将同样依赖于源-deps数组中的lib。 接下来是带有签名设置的目标。 我正在使用Android SDK随附的借记密钥。 紧随目标之后,目标将为我们收集Kotlin的资源。 与Bazel一样,它取决于资源和兼容性库。

我们描述它们。 Buck中有一个单独的资源目标,因此自行车没有用。 以下是下载的第三方库的规则。

建立
 android_binary( name = 'app', manifest = 'src/main/AndroidManifest.xml', manifest_entries = { 'min_sdk_version': 15, }, keystore = ':debug_keystore', deps = [ ':lib', ], ) keystore( name = 'debug_keystore', store = 'debug.keystore', properties = 'debug.keystore.properties', ) android_library( name = 'lib', srcs = glob(['src/main/java/*.kt']), deps = [ ':res', ':compat', ':compat_core', ], language = 'kotlin', ) android_resource( name = 'res', res = "src/main/res", package = 'com.example.myapplication', ) android_prebuilt_aar( name = 'compat', aar = "libs/appcompat.aar", ) android_prebuilt_aar( name = 'compat_core', aar = "libs/core.aar", ) 


这整个事情进展得非常快。 干净的装配体会花费7秒钟多一点的时间,而增量装配体将在200毫秒内完全不可见。 我认为这是一个很好的结果。

这就是Facebook所做的。 除了旗舰应用程序外,他们还为他们收集Facebook Messenger。 还有使用Lyft为Gradle和Airbnb制作插件的Uber。

结论


现在我们已经讨论了每个构建系统,我们可以使用示例Hello,world将它们彼此进行比较。 控制台组件的稳定性令人满意。 可以将来自终端的脚本执行时间视为组装干净版本的参考,因为在这里解析脚本的第三方成本很小。 在这种情况下,我将Maven视为明显的局外人,因为增量组装的增加微不足道。 Bazel解析了很长时间的配置并进行了初始化:有一种想法,它以某种方式缓存了初始化结果,因为它执行的增量构建比干净的运行快得多。 巴克是该系列中无可争议的领导者。 清洁和增量组装都非常快。



现在比较一下优缺点。 我不会在比较中加入Maven,因为它显然输给了Gradle并且几乎从未在市场上使用过。 我将Buck和Bazel结合起来,因为它们具有大致相同的优点和缺点。

因此,关于Gradle:

  • 首先,在我看来,最重要的是它很简单。 很简单;
  • 开箱即用,卸载和卸载依赖项;
  • 对他来说,有很多不同的训练和记录;
  • 得到Google和社区的积极支持。 与当前旗舰开发工具Android Studio的高度集成。 而且,用于构建Android应用程序的所有新功能都首先出现在Gradle中。

关于巴克/巴兹尔:

  • 与Gradle相比绝对可以非常快。 我认为这在大型项目中尤其明显
  • 您可以保留一个项目,其中将包含iOS和Android的源代码,并将它们与一个构建系统组装在一起。 这允许应用程序的某些部分在平台之间混乱。 例如,铬就是这样。
  • 被迫详细描述依赖关系,因此从字面上迫使开发者是多模块的。

不要忘记缺点。

Gradle缓慢且效率低下,为其简单性付出了代价。
相反,Buck / Bazel由于其速度快,因此需要在配置中更详细地描述构建过程。 好吧,由于它们是相对较新的上市产品,因此没有太多的文档和备忘单。

搞笑


也许您有一个疑问,我们如何收集iFunny。 就像很多一样-使用Gradle。 这是有原因的:

  1. 尚不清楚这将给我们带来怎样的组装速度提高。 完整构建iFunny大约需要3分钟,并且需要增量-大约一分钟,这实际上不是很长时间。
  2. Buck或Bazel构建配置更难以维护。 对于Buck,还需要监视所连接的库及其依赖的库的相关性。
  3. 将现有项目从Gradle转移到Buck / Bazel的成本非常昂贵,尤其是在利润难以理解的情况下。

如果您的项目将花费超过45分钟,并且Android开发团队中大约有20个人,那么考虑更改构建系统是很有意义的。 如果您和您的朋友正在创办一家公司,请使用Gradle并放弃这些想法。



我将很乐意在评论中讨论Gradle替代产品的前景!
链接到项目

Source: https://habr.com/ru/post/zh-CN469771/


All Articles