前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >App极限瘦身: png 打包前自动化转换 webp

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

原创
作者头像
小木箱
修改2020-11-25 10:14:27
2.4K0
修改2020-11-25 10:14:27
举报
文章被收录于专栏:性能优化

改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础平台开发 ,涵盖音视频, APM和信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!精彩内容不容错过~

前言

大家都知道 png 是比较占用App体积的,有没有工具可以在打包前(比如 assembleDebug、assembleRelease)自动去转化所有的 png 图片,包括第三方依赖库里面的呢?之前不经意间发现有一个神器cwebp 转化工具,是不是可以借鉴这种工具自己写个Plugin完成图片转换,同时支持检查大图片,图片大小可配置。话不多说,说干就干~

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

  1. 怎么拿到所有的 res 资源呢?
  2. 自动化转换工具Task 的执行时机点?
  3. 如何检查大图片,并配置图片大小,自动化开启图片转换开关?

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

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

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

代码语言:javascript
复制
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 方便在编译期间动态添加依赖

代码语言:javascript
复制
    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任务

代码语言:javascript
复制
@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任务 执行

7.1 检查tools 路径下是否有 webp工具
7.2 如果配置属性配置开关为false,中断任务
代码语言:javascript
复制
      if (!config.enableWhenDebug) {
                return@all
            }
7.3 拿到所有的Android资源文件,遍历资源文件,将满足条件的大图添加到大图列表
代码语言:javascript
复制
    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)
        }
    }
7.4 过滤不合规的图片文件
  • 如果添加了图片白名单或者文件不是图片格式,过滤
  • 如果图片尺寸合规,并且图片是大图,大图白名单没有图片,添加到大图列表
  • 否则添加到图片目录
代码语言:javascript
复制
    /**
     * 过滤图片
     */
    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)
    }
7.5 检查大图,并且将图片引用找出来
代码语言:javascript
复制
   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())
      }
  }
7.6 处理图片压缩任务
代码语言:javascript
复制
    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)
    }
7.7 计算压缩前后图片大小,以及压缩耗时时间
代码语言:javascript
复制
 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 知识的渴望~

参考链接

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 图片格式转换开发流程
    • 第一步: 新建Gradle Plugin 工程
      • 第二步: 添加Png转Webp配置
        • 第三步: 针对com.android.application和com.android.library配置Plugin
          • 第五步: 将mac版本和windows版本图片转换工具移到 tool/cwebp 目录下,并添加可执行程序
            • 第六步 添加 auto.service 方便在编译期间动态添加依赖
              • 第七步 Convert2WebpTask任务 执行
                • 7.1 检查tools 路径下是否有 webp工具
                • 7.2 如果配置属性配置开关为false,中断任务
                • 7.3 拿到所有的Android资源文件,遍历资源文件,将满足条件的大图添加到大图列表
                • 7.4 过滤不合规的图片文件
                • 7.5 检查大图,并且将图片引用找出来
                • 7.6 处理图片压缩任务
                • 7.7 计算压缩前后图片大小,以及压缩耗时时间
            • 总结
            • 参考链接
            相关产品与服务
            图片处理
            图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档