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

news2024/10/5 7:45:01

引言

当谈到包体积优化时,网上不乏优秀的方案与文章,如 混淆资源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 市场上找现成的(没事就别乱造轮子,你造的又没人家好😅)。

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

image-20230429223207105

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

集成看看效果:

image-20230501232508815

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

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

image-20230429224231548

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

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

回到我们现在,我们完全不需要这么复杂,我们只是需要一个 diff工具 而已。

既然没有合适,那就自己造一个,反正diff逻辑也并不复杂。🤔

万事开头难

Jar怎么写?😅

是的,我也没写过这玩意,但本能觉得很简单。

先去 IDE 直接创建个项目,感觉应该选 JVM ,依赖配置上 Gradle 也更接近 Android 开发者的使用习惯,具体如下:

image-20230430113323152

凭着以前用 IDEKotlin 时的记忆,Jvm 参数应该是在这里进行传递🤔:

image-20230430113659826

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

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,非常方便。

示例效果

image-20230430121553523Kapture 2023-04-30 at 12.00.12

代码也非常简单,我们定义了两个参数,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的方式,手动统计,反而更加直接。

核心代码

image-20230430232551506

思路如下:

  • 解压 apk ,开始进行遍历;
  • 按照自定义的规则进行分类,从而得到apk的实际文件类型映射 Map;
  • 遍历过程中,同时 分类统计 各类型大小以及子集;

匹配与模型设计

image-20230430232857348image-20230430233015109
自定义规则文件Model

一些小Tips

关于分层的想法

一个合格 CLI 设计,基本应该包含下面的流程:

配置 -> 分析 -> 输出

  • 配置

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

    比如在阈值的设定上,除了最基本的分类,也要提供统一默认配置,同时要对用户键入的 key-value 做基本的 check ,这些借助 CLIKT 框架能很低成本的实现。

  • 分析

    拿到上一步的配置结果后,接下来就要开始进行分析,此时我们要考虑设计上的分层,比如匹配规则如何定义,采用怎样的数据结构比较好,规则是否严谨,甚至如果要替换基础实现思路,改动会不会依然低成本;

  • 输出

    输出理论上应该包含多个途径,比如 jsonmd命令行 等等,不同的用户场景也必然不同。比如应用于 CI 、或者自定义结果统计等;在具体的设计上,开发者也应该考虑进行分层,比如输出这里只接受数据源,直接按照规则处理即可,而非再次对数据源进行修改。

灵活运用语言技巧

image-20230430233321713

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里。

效果如下:

image-20230501194707110

最终效果

image-20230502001351177

最终搭配 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) ,期待与你一同前进 😃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/553733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring的定义和创建,使用

前言: 我们常说的Spring就是Spring Framework(Spring框架),Spring支持广泛的应用场景,可以让Java企业级的应用程序开发起来更简单。 目录 一.Spring的定义: 1.1:IOC 1.2:DI 二:Spring的创建 三&…

【JavaEE进阶】第五节.第一个SpringBoot项目的创建

作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:JavaEE进阶 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!! 文章目录 前…

赛宁首创革新赛制助力人才培养 | 第十六届全国大学生信息安全竞赛-创新实践能力赛全面启动!

​​为积极响应国家网络空间安全人才战略,加快攻防兼备创新人才培养步伐,实现以赛促学、以赛促教、以赛促用,推动网络空间安全人才培养和产学研用生态发展,由国防科技大学与中国科学技术大学联合承办的第十六届全国大学生信息安全…

你是不是在担心,ChatGPT写出的文字被其它AI识别?

文 / 高扬(微信公众号:量子论) 最近不少人问我一个道听途说的事情:听说已经有AI工具能够识别是不是ChatGPT写的文字了? 看来必要写篇文章,通过摆事实,为大家压压惊。 在赌场里,能抓老千的人一定…

2023年湖北中级工程师职称申请全流程,你知道吗?启程别告诉你!

2023年湖北中级工程师职称申请全流程,你知道吗?启程别告诉你! 中级职称评审其实是有几个阶段的,职称申请全过程,启程别告诉你,让你评职称少走弯路哟: 第一阶段:准备阶段 第二阶段&…

【JavaEE进阶】——第六节.SpringBoot配置文件介绍

作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:JavaEE进阶 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!! 文章目录 前…

深度学习框架-Caffe:特点、架构、应用和未来发展趋势

引言 深度学习是一种新兴的技术,已经在许多领域中得到广泛的应用,如计算机视觉、自然语言处理、语音识别等。在深度学习中,深度学习框架扮演着重要的角色。Caffe是一种广泛使用的深度学习框架,它在许多方面都有所改进&#xff0c…

基于Android的运动健身减肥管理系统设计与实现

一、项目介绍​ Android客户端功能描述: 1:登录注册:第一次输入用户名密码之后,再次打开登录页面,页面上自动填充上次输入的用户名和密码,登录使用图形验证码验证,登录成功之后,再…

两种鲸鱼优化算法 (whale optimization algorithm, WOA)及仿真实验——附代码

目录 摘要: 算法设计: WOA总体流程图如下: 增强型WOA(E-WOA) 仿真运行效果: 完整程序: 摘要: 鲸鱼优化算法 (whale optimization algorithm,WOA)是 2016 年由澳大利亚格里菲斯…

PM861K01 3BSE018105R1协作机器人机械臂的每个轴上会安装扭矩传感器,用于测量轴电机和变速箱内的机械张力

​ PM861K01 3BSE018105R1协作机器人机械臂的每个轴上会安装扭矩传感器,用于测量轴电机和变速箱内的机械张力 机器人在工业领域已经存在了几十年的时间,但技术创新正在推动全新一轮的工厂自动化趋势。对于那些曾经负担不起(或者不需要&…

Spring 中 @NotEmpty、@NotBlank、@NotNull,傻傻分不清楚!

1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.0.5.RELEASE</version> </dependency>NotEmpty、NotBlank、NotNull 包的位置&#xff1…

智驾芯片市场要“变天”,这家中国厂商如何成为“量产先锋”?

中国智能驾驶芯片的量产之战&#xff0c;正在高歌猛进。 5月19日&#xff0c;黑芝麻智能宣布获得一汽红旗下一代FEEA3.0电子架构平台项目量产智驾芯片定点。据了解&#xff0c;基于黑芝麻智能华山二号A1000L系列芯片&#xff0c;一汽红旗将打造非分时复用的高性价比行泊一体自…

OpenHarmony社区运营报告(2023年4月)

本月快讯 • 2023年4月9日&#xff0c;OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;3.2 Release新版本发布。相比一年前的OpenHarmony 3.1 Release版本&#xff0c;新版本的系统能力、系统整体性能、稳定性和安全性都进一步得到提升和完善&#xff1b…

小兔鲜项目----vue3入门

目录 认识Vue3 为什么需要学Vue3? Vue3组合式API体验 Vue3更多的优势 使用create-vue搭建Vue3项目 认识 create-vue 使用create-vue创建项目 熟悉项目目录和关键文件 组合式API - setup选项 setup选项的写法和执行时机 setup选项中写代码的特点 组合式API - reactive和…

SEO优化需要天天更新文章吗?

SEO优化并不要求每天都更新文章。 更新频率取决于你网站的内容类型、目标受众和业务需求。以下是一些建议&#xff1a; 高质量内容&#xff1a;关键是提供有价值且吸引人的内容。不要为了更新而随意发布低质量或重复的内容。确保你的文章有深度、独特性和相关性&#xff0c;以…

出现小红书负面笔记怎么维护,需要注意什么

很多时候&#xff0c;我们在品牌发布的过程中&#xff0c;都会遭受到来自各方不同的恶意。比如&#xff0c;品牌在小红书遇到同行或者写手恶意笔记&#xff0c;这个时候&#xff0c;大家肯定想要的就是不要负面笔记。今天来和大家分享下出现小红书负面笔记怎么维护&#xff0c;…

C++程序在Windows系统上启动失败与运行卡死问题排查实战

目录 1、VS2017默认编译出来的程序&#xff0c;不支持XP系统 1.1、新版本软件为什么要选择VS2017&#xff1f; 1.2、VS2017如何配置才能编出支持XP系统的程序&#xff1f; 1.3、最终选择使用VS2010编出的版本 2、程序在XP系统中启动报错&#xff0c;无法启动 2.1、提示在…

浅谈元年方舟数据中台之标签管理平台

在现如今的大数据时代&#xff0c;相信大家一定了解或者听说过下列几个场景&#xff1a; 购物APP&#xff1a;千人千面&#xff0c;意思不同用户使用相关的产品感觉是不一样的&#xff0c;不同用户看到的购物APP首页推荐内容和其他相关推荐流信息可能是完全不同的。 社交APP&a…

【MySQL新手到通关】第一章 数据库概述

文章目录 1. 为什么要使用数据库2. 数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系 3. MySQL介绍3.1 概述3.2 关于MySQL 8.03.3 为什么选择 MySQL3.4 Oracle vs MySQL 4. RDBMS 与 非RDBMS4.1 关系型数据库4.1.1 实质4.1.2 优势 4.2 非关系型数据…