包体积优化 · 彩蛋篇 · Android编译期PNG自动化转换WEBP

简介: 包体积优化 · 彩蛋篇 · Android编译期PNG自动化转换WEBP

image.png

【小木箱成长营】包体积优化系列教程:

包体积优化 · 实战论 · 怎么做包体积优化? 做好能晋升吗? 能涨多少钱?

包体积优化 · 工具论 · 初识包体积优化

包体积优化 · 方法论 · 揭开包体积优化神秘面纱

BaguTree包体积优化录播视频课(关注公众号小木箱成长营,回复“包体积优化”可免费获取课程PPT)

前言

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

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

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

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

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

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

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

图片格式转换开发流程

第一步: 新建Gradle Plugin 工程

image.png

第二步: 添加Png转Webp配置

image.png

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

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

image.png

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

image.png


image.png

第六步 添加 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任务 执行

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

image.png

image.png

7.2 如果配置属性配置开关为false,中断任务

      if (!config.enableWhenDebug) {
                return@all
            }
            

7.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)
        }
    }

7.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)
    }

7.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())
      }
  }

7.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)
    }

7.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 知识的渴望~

参考链接


相关文章
|
3月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
63 10
|
2天前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
7天前
|
运维 监控 Linux
自动化运维:如何利用Python脚本优化日常任务##
【10月更文挑战第29天】在现代IT运维中,自动化已成为提升效率、减少人为错误的关键技术。本文将介绍如何通过Python脚本来简化和自动化日常的运维任务,从而让运维人员能够专注于更高层次的工作。从备份管理到系统监控,再到日志分析,我们将一步步展示如何编写实用的Python脚本来处理这些任务。 ##
|
24天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
40 4
|
2月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
61 20
Android经典面试题之图片Bitmap怎么做优化
|
7天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
29天前
|
存储 运维 监控
高效运维管理:从基础架构优化到自动化实践
在当今数字化时代,高效运维管理已成为企业IT部门的重要任务。本文将探讨如何通过基础架构优化和自动化实践来提升运维效率,确保系统的稳定性和可靠性。我们将从服务器选型、存储优化、网络配置等方面入手,逐步引导读者了解运维管理的核心内容。同时,我们还将介绍自动化工具的使用,帮助运维人员提高工作效率,降低人为错误的发生。通过本文的学习,您将掌握高效运维管理的关键技巧,为企业的发展提供有力支持。
|
2月前
|
机器学习/深度学习 存储 算法
Optuna发布 4.0 重大更新:多目标TPESampler自动化超参数优化速度提升显著
Optuna,广受欢迎的超参数优化框架,近日发布了其第四个主要版本。自2018年问世以来,Optuna迅速成为机器学习领域的关键工具,目前拥有10,000+ GitHub星标、每月300万+下载量、16,000+代码库使用、5,000+论文引用及18,000+ Kaggle使用。Optuna 4.0引入了OptunaHub平台,支持功能共享;正式推出Artifact Store管理生成文件;稳定支持NFS的JournalStorage实现分布式优化;显著加速多目标TPESampler,并引入新Terminator算法。
119 9
Optuna发布 4.0 重大更新:多目标TPESampler自动化超参数优化速度提升显著
|
2月前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
55 2
|
2月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
下一篇
无影云桌面