App极限瘦身姿势: png 打包自动化转换 webp

????????关注后回复 “进群” ,拉你进程序员交流群????????

来源:掘金 - 小木箱

https://juejin.cn/post/6897894068068876295

前言

大家都知道 png 是比较占用App体积的,有没有工具可以在打包前(比如 assembleDebug、assembleRelease)自动去转化所有的 png 图片,包括第三方依赖库里面的呢?

之前不经意间发现有一个神器cwebp 转化工具,是不是可以借鉴这种工具自己写个Plugin完成图片转换,同时支持检查大图片,图片大小可配置。话不多说,说干就干~

https://developers.google.com/speed/webp/docs/cwebp

编写插件前,需要思考几个业务痛点

  • 怎么拿到所有的 res 资源呢?

  • 自动化转换工具Task 的执行时机点?

  • 如何检查大图片,并配置图片大小,自动化开启图片转换开关?

鉴于问题1,我们可以参考McImage, 其实也很简单,就是一个 Gradle API,看链接文档的文档即可。

https://github.com/smallSohoSolo/McImage

鉴于问题2,该 Task 的执行时机其实是依赖于 MergeResources Task

鉴于问题3, 我们可以通过 Gradle API 自定义API 设置开关,图片最大体积,给图片添加白名单

convert2WebpConfig{    enableWhenDebug true    maxSize 1024*1024 // 1M    whiteList ["xxx.png","xxx.png"]    //...}

图片格式转换开发流程

第一步: 新建Gradle Plugin 工程

第二步: 添加Png转Webp配置

第三步: 针对com.android.application和com.android.library配置Plugin

实现自定义属性图片转换器开关配置,图片最大体积配置,图片添加白名单配置

第四步: 将mac版本和windows版本图片转换工具移到 tool/cwebp 目录下,并添加可执行程序

第五步 添加 auto.service 方便在编译期间动态添加依赖

kapt "com.google.auto.service:auto-service:1.0-rc4"implementation "com.google.auto.service:auto-service:1.0-rc4"compileOnly "com.android.tools.build:gradle:4.0.1"testCompileOnly "com.android.tools.build:gradle:4.0.1"

利用AutoService注解,使用的反射去实例化对象 VariantProcessor ,动态注册Convert2WebpTask任务,后期使用该注解处理器处理Convert2WebpTask任务

@AutoService(VariantProcessor::class)class Convert2WebpVariantProcessor : VariantProcessor {    override fun process(variant: BaseVariant) {        val variantData = (variant as ApplicationVariantImpl).variantData        val tasks = variantData.scope.globalScope.project.tasks        val convert2WebpTask = tasks.findByName("convert2Webp") ?: tasks.create(            "convert2Webp",            Convert2WebpTask::class.java        )        val mergeResourcesTask = variant.mergeResourcesProvider.get()        mergeResourcesTask.dependsOn(convert2WebpTask)    }}

第六步 Convert2WebpTask任务 执行

6.1 检查tools 路径下是否有 webp工具


6.2 如果配置属性配置开关为false,中断任务
if (!config.enableWhenDebug) {    return@all}
6.3 拿到所有的Android资源文件,遍历资源文件,将满足条件的大图添加到大图列表
val dir = variant.allRawAndroidResources.files    /** * 遍历资源文件目录 */        for (channelDir in dir) {            traverseResDir(channelDir, imageFileList, cacheList, object : IBigImage {                override fun onBigImage(file: File) {                    bigImageList.add(file.absolutePath)                }            })        }private fun traverseResDir(    file: File,    imageFileList: ArrayList<File>,    cacheList: ArrayList<String>,    iBigImage: IBigImage) {    if (cacheList.contains(file.absolutePath)) {        return    } else {        cacheList.add(file.absolutePath)    }    if (file.isDirectory) {        file.listFiles()?.forEach {            if (it.isDirectory) {                traverseResDir(it, imageFileList, cacheList, iBigImage)            } else {                filterImage(it, imageFileList, iBigImage)            }        }    } else {        filterImage(file, imageFileList, iBigImage)    }}
6.4 过滤不合规的图片文件
  • 如果添加了图片白名单或者文件不是图片格式,过滤

  • 如果图片尺寸合规,并且图片是大图,大图白名单没有图片,添加到大图列表

  • 否则添加到图片目录

/** * 过滤图片 */private fun filterImage(file: File, imageFileList: ArrayList<File>, iBigImage: IBigImage) {    // 如果添加了图片白名单或者文件不是图片格式,过滤    if (config.whiteList.contains(file.name) || !ImageUtil.isImage(file)) {        return    }    // 如果图片尺寸合规,并且图片是大图,大图白名单没有图片    if ((config.isCheckSize && ImageUtil.isBigSizeImage(file, config.maxSize))        && !config.bigImageWhiteList.contains(file.name)    ) {        // 添加到大图列表        iBigImage.onBigImage(file)    }    // 将图片添加到图片图片目录    imageFileList.add(file)}
6.5 检查大图,并且将图片引用找出来
 private fun checkBigImage() {    if (bigImageList.size != 0) {        val stringBuffer = StringBuffer("Big Image Detector! ")            .append("ImageSize can't over ${config.maxSize / 1024}kb.\n")            .append("To fix this exception, you can increase maxSize or config them in bigImageWhiteList\n")            .append("Big Image List: \n")        for (fileName in bigImageList) {            stringBuffer.append(fileName)            stringBuffer.append("\n")        }        throw GradleException(stringBuffer.toString())    }}
6.6 处理图片压缩任务
private fun dispatchOptimizeTask(imageFileList: java.util.ArrayList<File>) {    if (imageFileList.size == 0 || bigImageList.isNotEmpty()) {        return    }    val coreNum = Runtime.getRuntime().availableProcessors()    if (imageFileList.size < coreNum) {        for (file in imageFileList) {            optimizeImage(file)        }    } else {        val results = ArrayList<Future<Unit>>()        val pool = Executors.newFixedThreadPool(coreNum)        val part = imageFileList.size / coreNum        for (i in 0 until coreNum) {            val from = i * part            val to = if (i == coreNum - 1) imageFileList.size - 1 else (i + 1) * part - 1            results.add(pool.submit(Callable<Unit> {                for (index in from..to) {                    optimizeImage(imageFileList[index])                }            }))        }        for (f in results) {            try {                f.get()            } catch (e: Exception) {                println("EHiPlugin Convert2WebpTask#dispatchOptimizeTask() execute wrong.")            }        }    }}/** * 压缩图片 */private fun optimizeImage(file: File) {    val path: String = file.path    if (File(path).exists()) {        oldSize += File(path).length()    }    ImageUtil.convert2Webp(file)    calcNewSize(path)}
6.7 计算压缩前后图片大小,以及压缩耗时时间
private fun optimizeImage(file: File) {   val path: String = file.path   if (File(path).exists()) {       oldSize += File(path).length()   }   ImageUtil.convert2Webp(file)   calcNewSize(path)}dispatchOptimizeTask(imageFileList) println("Before optimize Size: ${oldSize / 1024}kb") println("After optimize Size: ${newSize / 1024}kb") println("Optimize Size: ${(oldSize - newSize) / 1024}kb") println("CostTotalTime: ${System.currentTimeMillis() - startTime}ms") println("------------------------------------")

总结

本文主要是在打包前对App做了一次图片全量替换,图片转换方式借助的是Google开源工具cwebp,当然我们可以通过白名单方式规范图片尺寸大小和插件开关,如果你掌握本文内容,不仅会对你们公司应用瘦身有所帮助,同时也能弥补你对 Gradle Plugin 知识的渴望~

参考链接

https://github.com/MicroKibaco/Lavender

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击????卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值