我正在参加「启航计划」

引言

当谈到包体积优化时,网上不乏优异的计划与文章,如 混杂资源ReDexR8SO 优化等等。

但聊到 包体积监控 时,总是感觉会短少落地性,或许总是会下意识以为这可能比较费事,需求其他部门连同合作。一般关于有APM基础的团队而言,这倒不算什么,但往往关于小公司而言,到了这一步,可以说就戛然而止。

但回到问题本身,这并非难事。或许说,其实很简略 :)

核算差异告知汇总数据 ,三步即可。

按照最朴素的主意,不论多大的团队,也能至少完毕前两步,事实上,也确实如此。

故此,本篇将结合实践需求以及布景,运用 Kotlin 去写一个 APK差异化 对比的基础 CLI 东西,并分配 CI 完毕流水线监控。

本篇并不触及深度源码等,更多是实操,所以文章风格比较轻松,可定心食用

毕竟组件地址: Github

写在初步

关于 CLI(command-line interface) ,每个开发同学应该都非常了解,可以说底子就是日常操作,比如咱们常常在 指令行 里会去敲几个指令,触发几个操作等,常见的 gitgradle java 等。

在图形化(GUI)的现在,CLI 往往代表着一种 老派风格 ,有人抵触,觉得繁琐,当然也有同学觉得简略直接。

但全体上的趋势是,越来越多东西趋于图形化。不过两者仍然处于一种 互补 ,而非比赛,不同场景也有各自的优势以及差异化。比如在某些场景下,当咱们需求去 简化开发流程 时,此时 CLI 就会作为首选项就会映入眼前。

聊聊布景

最近在做 下厨房-懒饭App 的体积优化,优化做完了(后续出文章),那怎样做防劣化呢?

因为咱们的项目是在 Github 上保管,所以自然而然也有相应的 Action 作为check,所以此时首要最基础想的就是:

  • 直接拉上一个版其他 apk 作为基准包,然后和本次的包一个 diff ,并保存效果;
  • 假设效果中,某个类别(如 resdex 等)超出指定阈值,则在PR里加一个谈论,以及飞书告知一下。
  • 至于分版别核算效果等等,这些都是后话了…

先找轮子

思路有了,那关键的东西,diff东西 怎样搞?

作为一个正经的开发仔,所以此时首选肯定是去 Github Action 市场上找现成的(没事就别乱造轮子,你造的又没人家好)。

效果发现,还真有,真不戳!

落地包体积监控,用Kotlin写一个APK差异剖析CLI

来自微软的开源,那肯定有保证啊!

集成看看效果:

落地包体积监控,用Kotlin写一个APK差异剖析CLI

嗯,看着还不错,不过这个输出怎样改呢,官方只要MD格式,而且看着过糙,作为一个稍微有点审美的同学。

那就考虑先 fork 改一下呢,fork 前看了一下库房:

落地包体积监控,用Kotlin写一个APK差异剖析CLI

我是辣鸡,这下触摸到知识盲区了,压根不知道怎样改,无疑大大添加了后续迭代本钱,以及看看上一次的版别时间(此处无声胜有声)。

那已然没有适合的 Action ,那就自己找一个 jar 东西也行啊,于是又去找了一下现有的jar东西,发现只要腾讯的 matrix-apk-canary 可用,但是这也太顶了吧。虽然功用强壮,但是不符合咱们现在的需求啊,我还得去手动算两次,然后再拿着json效果去对比,想想就杂乱。

回到咱们现在,咱们彻底不需求这么杂乱,咱们仅仅需求一个 diff东西 算了。

已然没有适合,那就自己造一个,横竖diff逻辑也并不杂乱。

万事开头难

Jar怎样写?

是的,我也没写过这玩意,但天性觉得很简略。

先去 IDE 直接创立个项目,感觉应该选 JVM ,依托配备上 Gradle 也更靠近 Android 开发者的运用习惯,详细如下:

落地包体积监控,用Kotlin写一个APK差异剖析CLI

凭着曾经用 IDEKotlin 时的记忆,Jvm 参数应该是在这儿进行传递:

落地包体积监控,用Kotlin写一个APK差异剖析CLI

输出也没啥问题,正常打印了出来:

Hello World!
Program arguments: Petterp,123

但这不是我要的样子啊,我的 抱负情况 下是这种操作:

java -jar xxx.jar -x xxx

不过就算现在能直接这样运用,也不能进行快速开发,首要调试就是个费事事。

再回到原点,我乃至不知道怎样在指令行传参呢

说说CLIKT

此时就不得不提一个开款库,用 KotlinCLI 的最强库: CLIKT ,也是无意之间发现的一个结构,可以说是神器不足为过。

简介

Clikt(发音为“clicked”)是一个多渠道的 Kotlin 库,可以使编写指令行界面变得简略和直观,它是“Kotlin 的指令行界面”。

该库旨在使编写指令行东西的进程变得轻松,一同支撑各种用例,并在需求时答应高档自定义。

Clikt 具有以下特色:

  • 指令的任意嵌套;
  • 可组合、类型安全的参数值;
  • 生成帮忙输出和 shell 主动完毕脚本;
  • 针对 JVM、NodeJS 和本地 Linux、Windows 和 MacOS 的多渠道包;

简而言之,Clikt 是一个功用丰厚的库,可以帮忙开发者快速构建指令行东西,一同具有活络的自定义和多渠道支撑。

以上来自官网文档。

依托办法

因为咱们是运用 Gradle 来进行依托管理,所以直接添加相应的依托即可:

implementation("com.github.ajalt.clikt:clikt:3.5.2")

一同因为运用的是 Gradle ,所以默许会带有一个 application 插件,因此供应一个 Gradle 使命,来将咱们的 jar和脚本 控绑在一同发动(run Main时),然后清除了每次调试都要在指令行 java -jar xxx,非常便当。

示例效果

落地包体积监控,用Kotlin写一个APK差异剖析CLI
落地包体积监控,用Kotlin写一个APK差异剖析CLI

代码也非常简略,咱们定义了两个参数,countname,其间 count 存在默许参数,而 name 没有,故需求咱们有必要传递,直接工作run办法,然后根据提示键入value即可,就这么简略。

在往常的jar指令里,一般都只存在一次性输入的场景。比如有必要直接输入悉数kay-value,假设输入差错,或许反常,日志或许输出全凭jar包开发者的自觉程度。可以说大多数jar包并不易用,当然这首要的原因是,传统的cli开发确实比较费事,并不是一切开发者都能完善好距离。

运用 CLIKT 之后,上面的问题可以说非常低本钱解决,咱们可以提前配备提示句子,报错句子等等。它可以做到提示运用者接下来该输入什么,也可以做到对输入进行check,乃至假设输入差错或许不符合要求,直接会进行提示,也可以选择持续让用户输入。

上述的示例仅仅非常简略的一个常见,CLIKT 官网有更多的用法以及高档示例,假设感兴趣,也可以看看。

常见问题

怎样打jar包

上面咱们结束了 jar包 的编写和本地调试,那该怎样打成 jar包 在指令行工作呢?

因为咱们运用了 Gradle 进行依托配备,那么相应的,也可以运用顺便的指令即可,默许有这几个指令可供选择:

  • jar

    直接打成jar包,后续直接在指令行java -jar 的办法驱动。

  • distTar || distZip

    简略来说就是,一同会顺便可实行程序 exec 的办法,然后清除 java -jar 的硬编码,直接点击实行或许在指令行输入 文件名+顺便的参数 即可。不过从打包办法上而言,其毕竟也需求依附于jar使命。

这儿感谢 虾哥(: 究极逮虾户) 解惑,原本以为 exec 这种办法会导致传参时的部分默许值无法设置问题。

jar包没有主清单特色

上面打完jar包,在指令行工作时,报错如下:

xxx.jar中没有主清单特色

这是什么鬼,不是现已配备过了吗?直接 run main 办法没有什么问题啊?

application {
    mainClassName = 'HelloKt'
}

经过一顿查阅,发现配备需求这样改一下,build.gradle 添加以下配备:

jar {
    exclude("**/module-info.class")
    from {
        configurations.runtimeClasspath.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    manifest {
        attributes 'Main-Class': "HelloKt"
    }
}

原理也很简略,你打出来的 jar 包得配备途径啊。咱们调试时走的 application 插件里的 run 。而打 jar 包, jar 指令没配备,导致其并不知道你的配备,所以不难理解为啥找不到主清单特色。

再聊结束思路

要对比 Apk 的差异,最简略的思路莫过于直接解压Apk。因为 Apk 其实是一种 Zip 格式,所以咱们只需求遍历解压文件,根据文件后缀以及不同的文件夹分类核算大小即可,比较简略粗犷。

当然假设要做的更精细一点,比如要核算 资源文件差异代码添加aar改变 等,就要凭仗其他办法,比如 Android 团队就为咱们供应了 apkanalyzer,或许可以经过 META-INF/MANIFEST.MF 文件作为基准进行对比。

业界开源的比较好的有腾讯的 matrix-apk-canary,其规划灵活,功用也更加强壮,详细在结束上,咱们也可以学习其规划思想。

因为本次咱们的需求无需上述那么杂乱,只需求介意 apk资源dexlib 等差异,所以直接选用手动解压Apk的办法,手动核算,反而更加直接。

中心代码

落地包体积监控,用Kotlin写一个APK差异剖析CLI

思路如下:

  • 解压 apk ,初步进行遍历;
  • 按照自定义的规则进行分类,然后得到apk的实践文件类型映射 Map;
  • 遍历进程中,一同 分类核算 各类型大小以及子集;

匹配与模型规划

落地包体积监控,用Kotlin写一个APK差异剖析CLI
落地包体积监控,用Kotlin写一个APK差异剖析CLI
自定义规则 文件Model

一些小Tips

关于分层的主意

一个合格 CLI 规划,底子应该包含下面的流程:

配备 -> 剖析 -> 输出

  • 配备

    顾名思义,就是指的是开发者友好,即对用户而言,报错详细,配备灵活,藏杂乱于内部。

    比如在阈值的设定上,除了最底子的分类,也要供应统一默许配备,一同要对用户键入的 key-value 做底子的 check ,这些凭仗 CLIKT 结构能很低本钱的结束。

  • 剖析

    拿到上一步的配备效果后,接下来就要初步进行剖析,此时咱们要考虑规划上的分层,比如匹配规则怎样定义,选用怎样的数据结构比较好,规则是否谨慎,乃至假设要替换基础结束思路,改动会不会仍然低本钱;

  • 输出

    输出理论上应该包含多个途径,比如 jsonmd指令行 等等,不同的用户场景也必定不同。比如应用于 CI 、或许自定义效果核算等;在详细的规划上,开发者也应该考虑进行分层,比如输出这儿只接受数据源,直接按照规则处理即可,而非再次对数据源进行修正。

活络运用言语技巧

落地包体积监控,用Kotlin写一个APK差异剖析CLI

Kotlin 内联类 是一个很棒的特性,不论是功用仍是可读性方面,假设咱们有某个字段,是运用底子类型作为定义,那么此时就可以考虑将其定义为内联类。

比如咱们本篇中的 file大小(size字段),一般咱们会运用 Long 类型进行代表,但是 Long 类型用于展示而言,可读性并不好,所以此时运用内联类对其进行包装,并分配 操作符重载 ,使得开发中的体会度会进步不少。

关于CI方面

关于 CI 方面,首选就是 Github Action,详细 Github 也有专门的教程,上手难度也很低,几分钟足以,关于常常写开源库的作者而言,这个应该也算是底子技巧。相应的,已然咱们也是产出了一个 CLI 组件,那么每次 release 时都手动上传jar包,或许版其他定义上,假设每次都手动修正,怎样都显得 不高雅

故此,咱们可以考虑每次 发布新的release版别 之后,就触发一次 Action,然后打一个 jar 包,并将其上传到咱们最新的 release 里。相应的,主动化的版别也可以在这儿进行匹配,都比较简略。

这儿,以主动化发布jar为例:

name: Cli Release
on:
  release:
    types: [ published ]
permissions: write-all
jobs:
  build_assemble:
    runs-on: ubuntu-latest
    env:
      OUTPUT_DIR: build/libs
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: gradle
      - uses: burrunan/gradle-cache-action@v1
        name: Cache gradle
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Build jar
        run: ./gradlew jar
      - uses: AButler/upload-release-assets@v2.0
        with:
          files: build/libs/apk-size-diff-cli.jar
          repo-token: ${{ github.token }}
          release-tag: ${{ github.event.release.tag_name}}

全体步骤仍然非常简略,咱们定义这个工作流的触发机遇为每次 release 时,然后 拉代码配备gradle打jar包、上传到最新release-assets里。

效果如下:

落地包体积监控,用Kotlin写一个APK差异剖析CLI

毕竟效果

落地包体积监控,用Kotlin写一个APK差异剖析CLI

毕竟分配 Github CI 结束的效果如上,开源地址 apk-size-diff-cli。

运用办法也非常简略,本地运用的话,实行 jar 指令(或许运用 exec 的办法,清除 java -jar) 即可,如下示例所示:

java -jar apk_size_diff_cli.jar -b base.apk -c current.apk -d outpath/result -tss 102400

默许会在指定的输出途径,如 outpath/result 输出一个名为 apk_size_diff.md 的文档。

其间 -tss 指的是默许各类其他阈值大小,比如 apk、dex 等假设某一项本次对比前次超过102400,则输出效果里会有相应提示。

假设咱们对这个组件比较感兴趣,也无妨点个Star,全体结束较为干净利落,fork更改也非常简略。

结语

本篇到这儿就算完毕了,全体也并不算什么深邃技巧或许深度文章,更多的是站在一个 技术需求 的布景下,由0到1,完毕一个 CLI 组件的全流程开发,希望整个进程以及考虑会对咱们有所帮忙。

参看

  • Clikt 文档
  • 老派浪漫:用 Kotlin 寫 Command Line 东西

关于我

我是 Petterp ,一个 Android工程师 ,假设本文对你有所帮忙,欢迎 点赞、谈论、收藏,你的支撑是我持续发明的最大鼓舞!

欢迎重视我的 大众号(Petterp) ,期待与你一同前进 :)