我正在参加「启航计划」
引言
当谈到包体积优化时,网上不乏优异的计划与文章,如 混杂、资源、ReDex
、R8
、SO
优化等等。
但聊到 包体积监控 时,总是感觉会短少落地性,或许总是会下意识以为这可能比较费事,需求其他部门连同合作。一般关于有APM基础的团队而言,这倒不算什么,但往往关于小公司而言,到了这一步,可以说就戛然而止。
但回到问题本身,这并非难事。或许说,其实很简略 :)
核算差异
、告知
、汇总数据
,三步即可。
按照最朴素的主意,不论多大的团队,也能至少完毕前两步,事实上,也确实如此。
故此,本篇将结合实践需求以及布景,运用 Kotlin
去写一个 APK差异化 对比的基础 CLI
东西,并分配 CI
完毕流水线监控。
本篇并不触及深度源码等,更多是实操,所以文章风格比较轻松,可定心食用
毕竟组件地址: Github
写在初步
关于 CLI
(command-line interface) ,每个开发同学应该都非常了解,可以说底子就是日常操作,比如咱们常常在 指令行 里会去敲几个指令,触发几个操作等,常见的 git
、gradle
、java
等。
在图形化(GUI
)的现在,CLI
往往代表着一种 老派风格 ,有人抵触,觉得繁琐,当然也有同学觉得简略直接。
但全体上的趋势是,越来越多东西趋于图形化。不过两者仍然处于一种 互补 ,而非比赛,不同场景也有各自的优势以及差异化。比如在某些场景下,当咱们需求去 简化开发流程 时,此时 CLI
就会作为首选项就会映入眼前。
聊聊布景
最近在做 下厨房-懒饭App 的体积优化,优化做完了(后续出文章),那怎样做防劣化呢?
因为咱们的项目是在 Github
上保管,所以自然而然也有相应的 Action
作为check,所以此时首要最基础想的就是:
- 直接拉上一个版其他
apk
作为基准包,然后和本次的包一个diff
,并保存效果;- 假设效果中,某个类别(如
res
、dex
等)超出指定阈值,则在PR里加一个谈论,以及飞书告知一下。- 至于分版别核算效果等等,这些都是后话了…
先找轮子
思路有了,那关键的东西,diff东西 怎样搞?
作为一个正经的开发仔,所以此时首选肯定是去 Github Action
市场上找现成的(没事就别乱造轮子,你造的又没人家好)。
效果发现,还真有,真不戳!
来自微软的开源,那肯定有保证啊!
集成看看效果:
嗯,看着还不错,不过这个输出怎样改呢,官方只要MD格式,而且看着过糙,作为一个稍微有点审美的同学。
那就考虑先 fork
改一下呢,fork
前看了一下库房:
我是辣鸡,这下触摸到知识盲区了,压根不知道怎样改,无疑大大添加了后续迭代本钱,以及看看上一次的版别时间(此处无声胜有声)。
那已然没有适合的 Action
,那就自己找一个 jar
东西也行啊,于是又去找了一下现有的jar东西,发现只要腾讯的 matrix-apk-canary 可用,但是这也太顶了吧。虽然功用强壮,但是不符合咱们现在的需求啊,我还得去手动算两次,然后再拿着json效果去对比,想想就杂乱。
回到咱们现在,咱们彻底不需求这么杂乱,咱们仅仅需求一个 diff东西 算了。
已然没有适合,那就自己造一个,横竖diff逻辑也并不杂乱。
万事开头难
Jar怎样写?
是的,我也没写过这玩意,但天性觉得很简略。
先去 IDE
直接创立个项目,感觉应该选 JVM
,依托配备上 Gradle
也更靠近 Android
开发者的运用习惯,详细如下:
凭着曾经用 IDE
学 Kotlin
时的记忆,Jvm
参数应该是在这儿进行传递:
输出也没啥问题,正常打印了出来:
Hello World!
Program arguments: Petterp,123
但这不是我要的样子啊,我的 抱负情况 下是这种操作:
java -jar xxx.jar -x xxx
不过就算现在能直接这样运用,也不能进行快速开发,首要调试就是个费事事。
再回到原点,我乃至不知道怎样在指令行传参呢
说说CLIKT
此时就不得不提一个开款库,用 Kotlin
写 CLI
的最强库: 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,非常便当。
示例效果
代码也非常简略,咱们定义了两个参数,count
与 name
,其间 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
、资源
、dex
、lib
等差异,所以直接选用手动解压Apk的办法,手动核算,反而更加直接。
中心代码
思路如下:
- 解压
apk
,初步进行遍历; - 按照自定义的规则进行分类,然后得到apk的实践文件类型映射
Map
; - 遍历进程中,一同 分类核算 各类型大小以及子集;
匹配与模型规划
自定义规则 | 文件Model |
一些小Tips
关于分层的主意
一个合格 CLI
规划,底子应该包含下面的流程:
配备 -> 剖析 -> 输出
-
配备
顾名思义,就是指的是开发者友好,即对用户而言,报错详细,配备灵活,藏杂乱于内部。
比如在阈值的设定上,除了最底子的分类,也要供应统一默许配备,一同要对用户键入的
key-value
做底子的 check ,这些凭仗CLIKT
结构能很低本钱的结束。 -
剖析
拿到上一步的配备效果后,接下来就要初步进行剖析,此时咱们要考虑规划上的分层,比如匹配规则怎样定义,选用怎样的数据结构比较好,规则是否谨慎,乃至假设要替换基础结束思路,改动会不会仍然低本钱;
-
输出
输出理论上应该包含多个途径,比如
json
、md
、指令行
等等,不同的用户场景也必定不同。比如应用于CI
、或许自定义效果核算等;在详细的规划上,开发者也应该考虑进行分层,比如输出这儿只接受数据源,直接按照规则处理即可,而非再次对数据源进行修正。
活络运用言语技巧
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里。
效果如下:
毕竟效果
毕竟分配 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) ,期待与你一同前进 :)