您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > Android

Android图片资源检测插件实现

时间:2023-08-25 11:45:19  来源:今日头条  作者:IT技术控

为什么要检测图片资源?

  1. 避免不小心把未压缩,不合适的图片资源打入apk中,造成apk过大
  2. 图片打入apk前,可以自动化转换,压缩

实现思路

  1. 思路一:使用gradle在aapt编译期,扫描汇总资源的文件夹,过滤出不符合要求的图片资源,并抛出异常中断编译
  2. 思路二:是思路一的进阶。还是在使用gradle在aapt编译期,查找有没有合适的gradle task,提供给我们遍历所有资源的机会

gradle插件实现

gradle插件实现的基础

简单对gradle插件实现进行复习

插件搭建

  • 新建一个模块
  • 配置好该模块的上传配置(mvn.gradle)
  • 在build中,对gradleApi进行依赖
  • scss复制代码
  • Apply plugin: 'kotlin' //插件如果使用kotlin实现,需要依赖kotlindependencies { implementation gradleApi() implementation localGroovy() implementation 'com.Android.tools.build:gradle:3.4.2'}
  • 在mAIn下面新建resources.META-INF.gradle-plugins文件夹
  • 在该文件夹中创建一个和module同名的.properties文件,在里面配置上你的插件入口类
  • 例:
  • arduino复制代码
  • implementation-class=com.xxx.checkbigimage.image.ImagePlugin

插件的基本实现

上面讲到要配置一个入口类,这个入口类就是实现了Plugin接口的类,它有一个override fun apply(project: Project)方法,就是我们插件开始执行的地方,相当于main函数,参数project就是整个工程的配置文件

可以使用以下方法,从我们使用插件的地方获取到对插件的配置

Python/ target=_blank class=infotextkey>Python复制代码project.extensions.create("config", Config::class.JAVA)mConfig = project.property("config") as Config

Config是一个java bean数据类

"config"是我们在build中的配置名称

这样一个简单gradle插件就实现了

图片资源检测插件实现

上面说了为什么要实现这样一个插件和该如何实现一个gradle插件,那么下面就具体介绍该插件的实现过程

想要的功能

  • 检测和拦截功能
    • 检测是否有大小超标的图片
    • 检测是否有宽高超标的图片
    • 拦截非webp资源,并进行提示
  • 自动化压缩
    • 自动压缩png,jpg等资源
  • 白名单设置
  • 一些统计功能

实现过程

上面已经说了gradle插件的实现,那么我们就从apply方法开始说起。

瞄准task挂钩

既然是要hock android打包的编译过程,那就要寻找android打包时,合适的task

想hock task,首先应该拿到任务task集合

在android插件编译生成apk的过程中,有好多task都可以生成apk,它们的名字基于Build Types 和 Product Flavor 生成。那么我们怎么拿到具体生成apk的task组呢?

为了解决这个问题。android插件有几个属性,就是我们平常配置的变体(所谓的环境),androd中有三类变体

  • applicationVariants(只适用于 app plugin)
  • libraryVariants(只适用于 library plugin)
  • testVariants(app、library plugin 均适用)

这三个对象都是实现了BaseVariant(BaseVariantImpl为实现这个接口的抽象类)接口的类的对象的集合

属性名

属性类型

说明

name

String

Variant 的名字,唯一

description

String

Variant 的描述说明

dirName

String

Variant 的子文件夹名,唯一。可能有不止一个子文件夹,例如 “debug/flavor1”

baseName

String

Variant 输出的基础名字,必须唯一

outputFile

File

Variant 的输出,该属性可读可写

processManifest

ProcessManifest

处理 Manifest 的 task

aidlCompile

AidlCompile

编译 AIDL 文件的 task

renderscriptCompile

RenderscriptCompile

编译 Renderscript 文件的 task

mergeResources

MergeResources

合并资源文件的 task

mergeAssets

MergeAssets

合并 assets 的 task

processResources

ProcessAndroidResources

处理并编译资源文件的 task

generateBuildConfig

GenerateBuildConfig

生成 BuildConfig 类的 task

javaCompile

JavaCompile

编译 Java 源代码的 task

processJavaResources

Copy

处理 Java 资源的 task

assemble

DefaultTask

Variant 的标志性 assemble task

因为我们的插件应该可以应用在主工程或者模块包上的,所以当我们插件运行后,我们要检测当前使用我们插件的模块是主工程,还是模块包

kotlin复制代码val hasAppPlugin = project.plugins.hasPlugin("com.android.application")val variants = if (hasAppPlugin) {  (project.property("android") as AppExtension).applicationVariants} else {  (project.property("android") as LibraryExtension).libraryVariants}

找到想要hock的任务

我们想hock住android插件运行的task任务,就需要一个重要的gradle回调

erlang复制代码project.afterEvaluate{...}

afterEvaluate该方法就是整个gradle配置文件配置成功后的回调,证明此时配置已检查完毕,所有task已经就绪,已经可以开始按指定顺序运行task了,那么我就需要在这个回调里办事!

Grade 执行顺序

执行setting,检测所有module,为每个模块配置project

加载build.properties,生成task执行链表和配置

执行某个指定task,然后会先执行该task所依赖的task

配置完成后,开始遍历variants中所有的变体

arduino复制代码project.afterEvaluate {  variants.all { variant ->    ...  }}

我们的目标task:mergeResourcesProvider

mergeResourcesProvider这个任务就是android插件合并所有module中资源的task,看名字就知道了。

我们可以从变体中获取这个task对象

ini复制代码val mergeResourcesTask = variant.mergeResourcesProvider.get()

那么,我们自己的任务呢?

gradle api提供给我们可以在代码中生成task的方法

ini复制代码val mcPicTask = project.task("CheckBigImage${variant.name.capitalize()}")

使用project.task("taskname")来生成一个我们自己需要执行的task

然后我们编写这个task的逻辑,也是本插件的逻辑

复制代码mcPicTask.doLast {...}

variant里面有各种对象,allRawAndroidResources恰好就是我们需要的。它只有3.3以上才会有。

ini复制代码val dir = variant.allRawAndroidResources.files

这个dir对象,就是android所有文件资源的files集合

ok。让我们遍历这个文件list吧!

scss复制代码for (channelDir: File in dir) {check(channelDir)}fun check(file: File) { if(file.isDirectory) {   check(file)} else {   process(file)}}

如果遇到文件夹,这里是一个递归调用。

如果遇到文件,就可以按照自己的规则处理了。

挂钩mergeResourcesProvider

我们task写好后,需要和mergeResourcesProvider挂钩

less复制代码mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

使mergeResourcesTask依赖我们的mcPicTask,当mergeResourcesTask执行前,就会先执行我们的mcPicTask了!!

注意:此处直接使用mergeResourcesTask系统task依赖我们的task,我们的task执行顺序会和mergeResourcesTask原有的依赖混杂在一起,不可控。后面讲一种可控的方法

拦截图片的逻辑

这个逻辑应该实现在上面伪代码process(file:File)方法中

  1. 首先我们只需要处理图片,所以对参数file进行首轮过滤,只留下后缀名为图片的文件
  2. kotlin复制代码
  3. fun isImage(file: File): Boolean { return (file.name.endsWith(Const.JPG) || file.name.endsWith(Const.PNG) || file.name.endsWith(Const.JPEG) || file.name.endsWith(Const.GIF) || file.name.endsWith(Const.WEB_P) ) && !file.name.endsWith(Const.DOT_9PNG)}
  4. 需要检查图片的宽高的话,可以使用java的原生api
  5. arduino复制代码
  6. val sourceImg = ImageIO.read(FileInputStream(imgFile))if (sourceImg.height > maxHeight || sourceImg.width > maxWidth) { ...
  7. 需要过滤图片大小的话
  8. lua复制代码
  9. if (imgFile.length() >= maxSize) { LogUtil.log(SIZE_TAG, imgFile.path, true.toString()) return true}

压缩图片逻辑

这里我们只处理普通图片转换为webp的压缩。jpg,png的自压缩原理相同,就不复述了

想压缩转换webp图片,需要用到转换工具

google提供的有一套命令行转换工具:cwebp ,各个平台都有,我们去下载一套,放在我们的主工程文件夹下就可以了

这里需要注意的是:为了方便,如果把cwebp命令行程序放在环境变量下,那么执行命令时,拼接命令时,直接拼接cwebp就好。

如果使用工程目录下的cwebp,执行前,需要在cwebp命令前面拼接它所在的工程目录。

使用

lua复制代码project.rootDir.path

可以获取工程的根目录

如何执行命令行程序呢?

可以使用java的api

scss复制代码Runtime.getRuntime().exec(cmd)

现在可以愉快的转换图片了

bash复制代码Tools.cmd("cwebp", "${imgFile.path} -o ${webpFile.path} -m 6 -quiet")

转换后,记得把原图删掉

优化点:

有的图片转换后比以前还大,这里需要注意

第一次扫描过后的无法优化的图片,可以存在一个text文本当中,第二次执行时,就不要去转换了

系统兼容

linux系统上,创建和删除文件都需要权限,如果没有权限就会失败。这时需要先判断当前的操作系统是不是linux,如果是,可以执行chmod 755 -R ${FileUtil.getRootDirPath()}添加权限

这里可以优化一下,在我们的mcPicTask前面再加一个task,用来添加权限,这个task只对文件夹进行递归添加就可以了,比一个一个文件要来的快。

因为我们不清楚系统的task(mergeResourcesTask)都依赖了哪些,那么如何在依赖上再加依赖,如何插入task呢?

gradle api提供给了我们一个方法,
xxx.taskDependencies.getDependencies(xxx)可以获取自己的依赖树

在这里就是

scss复制代码(project.tasks.findByName(chmodTask.name) asTask).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))

让chmodTask依赖mergeResourcesTask的依赖。假如mergeResourcesTask是A,chmodTask是B。A依赖一个系统的C。那么上面的代码就是让B依赖了C。这时的task图就是 B->C,A->C

接下来我们再把mcPicTask(简称为D)也依赖进来

arduino复制代码(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)

这时就是D->B->C,A->C

最后,回到我们刚刚拦截图片的逻辑的最后代码

less复制代码mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

就变成了A->D->B->C,也就是mergeResourcesTask->mcPicTask->chmodTask->原依赖task,依赖和执行顺序是相反的。

正常的代码就是

scss复制代码(project.tasks.findByName(chmodTask.name) asTask).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

Tips

直接使用
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))插入task。执行顺序打印

......

Task :app:mainApkListPersistenceDebug UP-TO-DATE

Task :app:CheckBigImageDebug

Task :app:generateDebugResValues UP-TO-DATE Task :app:generateDebugResources UP-TO-DATE Task :app:mergeDebugResources

......

而使用正规的插入法顺序

Task :app:mainApkListPersistenceDebug UP-TO-DATE Task :app:generateDebugResValues UP-TO-DATE Task :app:generateDebugResources UP-TO-DATE Task :app:chmodDebug

Task :app:CheckBigImageDebug

Task :app:mergeDebugResources

gradle版本差异

我们上面的例子,都是基于比较最新的gradle和android gradle tools版本(>3.3),android插件直接提供给了我们allRawAndroidResources,方便无比,直接在merge前遍历它就好了。

那么3.3之前的版本呢?就是我们最初的设想了,在合并完各个module资源后,扫描merge文件夹!这里又有aapt和aapt2的差异

方法一

关掉aapt2

ini复制代码android.enableAapt2=false

mergeDebugResources后,processDebugResources前扫描文件夹

前面说过,mergeDebugResources是合并所有module的资源文件到固定目录

那么processDebugResources是什么呢?就是处理这些已经合并完成的文件,生成R.id,资源索引之类的文件

那么我们的任务就必须插入到processDebugResources前面,而不是mergeDebugResources

方法二

仔细翻了翻MergeResources里面的方法,有一个getResSet和computeResourceSetList看起来有点意思。那么computeResourceSetList中又调用了getResSet。最后发现computeResourceSetList果然可以获取所有文件列表。

less复制代码/*** Computes the list of resource sets to be used during execution based all the inputs.*/@VisibleForTesting@NonNullList<ResourceSet> computeResourceSetList()

注释也很有意思,有道翻译一下:根据所有输入计算执行期间使用的资源集列表。

鉴于该方法是友元方法,就使用反射获取。

因为3.3之后,aapt2是强制开启的,并且aapt2 merge后的文件不是原文件了哦!注意aapt1合并后,还是正常的xxx.png。aapt2合并后的文件扩展名为flat

所以,方法一不支持大于3.3的gradle版本。方法二支持。可以平滑过渡到新版本。鉴于新版本的gradle直接提供了allRawAndroidResources这样的方法,所以在3.3以上,直接使用它就可以了

allRawAndroidResources和扫描合并文件夹的差异。

allRawAndroidResources提供的是未合并前的资源路径

  • 源码依赖的module,编译时,会获取该文件的真实路径
  • aar依赖的路径,会获取到aar-cache的路径
  • 所以:如果开启自动转换webp功能你会发现:你本地源代码中的png,都转成了webp

扫描合并文件夹,扫描的是编译期merge成功后的文件夹

  • 不会影响源代码
  •  

优化

  1. 已经扫描过的,且确认无法经过webp优化的图片,把这些名称写入一个本地文件,优化扫描速度

未来想做的事情

统计

  1. 拦截了多少图片
  2. 转换了多少图片
  3. 3. 统计各个模块的图片资源情况。在合适的时间进行预警


Tags:Android   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Android Emulator黑屏怎么办 Android模拟器黑屏解决方法
Android Emulator黑屏问题困扰了非常多的玩家,Android Emulator作为一款安卓模拟器,可以让你在电脑上运行和浏览安卓应用程序,但是程序本身不是很稳定,很容易会出现黑屏,启动不了...【详细内容】
2024-03-04  Search: Android  点击:(37)  评论:(0)  加入收藏
Android 谷歌三件套:解锁谷歌生态!
大家是不是遇到这个情况?当我们需要下载一些国外的游戏或者软件的时候,需要在手机里面安装Google Play商店,然后通过Google Play商店下载国外软件!为了帮助大家使用上各种好用的...【详细内容】
2024-01-02  Search: Android  点击:(110)  评论:(0)  加入收藏
Android开发中常见的Hook技术有哪些?
Hook技术介绍Hook技术是一种在软件开发中常见的技术,它允许开发者在特定的事件发生时插入自定义的代码逻辑。常见的应用场景包括在函数调用前后执行特定的操作,或者在特定的事...【详细内容】
2023-12-25  Search: Android  点击:(83)  评论:(0)  加入收藏
在Android应用开发中使用NFC功能
NFC介绍NFC是指“近场通讯”(Near Field Communication),它是一种短距离无线通信技术,允许设备在非接触或极短距离内进行通信。NFC通常用于移动支付、门禁系统、智能标签和其他...【详细内容】
2023-12-22  Search: Android  点击:(102)  评论:(0)  加入收藏
关于Android图像Bitmap类,你要知道的一切
Bitmap介绍Bitmap是一种图像文件格式,它由像素阵列组成,每个像素都有自己的颜色信息。在计算机图形学中,Bitmap图像可以被描述为一个二维的矩阵,其中每个元素代表一个像素的颜色...【详细内容】
2023-12-19  Search: Android  点击:(97)  评论:(0)  加入收藏
Android开发中如何进行单元测试?
单元测试介绍单元测试是软件开发中的一种测试方法,用于验证代码中的最小可测试单元(通常是函数或方法)是否按预期工作。单元测试通常由开发人员编写,旨在隔离和测试代码的特定部...【详细内容】
2023-12-11  Search: Android  点击:(167)  评论:(0)  加入收藏
我的手机我做主,如何为Android手机应用换图标?
作为一名Android用户,你是否曾经为自己的手机桌面感到单调而乏味?虽然Android系统的桌面定制性已经非常强大,但有时候我们还是希望能够在细节上做出一些改变,尤其是对于那些每天...【详细内容】
2023-12-10  Search: Android  点击:(61)  评论:(0)  加入收藏
了解Android系统架构中的HAL硬件抽象层
在Android系统中,HAL的存在使得不同厂商的硬件可以统一被上层的应用程序调用,从而提高了系统的兼容性和可移植性。HAL还可以帮助开发者更方便地开发应用程序,因为他们不需要为...【详细内容】
2023-12-06  Search: Android  点击:(200)  评论:(0)  加入收藏
谷歌 CEO 皮查伊建议 Android 用户不要侧载应用,称非常危险
iOS和Android的一个显著差异是,Android支持用户从第三方渠道安装应用程序(即“侧载”)。然而,谷歌似乎并不希望用户这样做。最近,在与Epic Store的法律诉讼中,谷歌首席执行官桑达...【详细内容】
2023-11-20  Search: Android  点击:(166)  评论:(0)  加入收藏
Android数据对象序列化原理与应用
序列化与反序列化「序列化」是将对象转换为可以存储或传输的格式的过程。在计算机科学中,对象通常是指内存中的数据结构,如数组、列表、字典等。通过序列化,可以将这些对象转换...【详细内容】
2023-11-14  Search: Android  点击:(272)  评论:(0)  加入收藏
▌简易百科推荐
Android Emulator黑屏怎么办 Android模拟器黑屏解决方法
Android Emulator黑屏问题困扰了非常多的玩家,Android Emulator作为一款安卓模拟器,可以让你在电脑上运行和浏览安卓应用程序,但是程序本身不是很稳定,很容易会出现黑屏,启动不了...【详细内容】
2024-03-04  18183游戏网    Tags:Android Emulator   点击:(37)  评论:(0)  加入收藏
Android开发中常见的Hook技术有哪些?
Hook技术介绍Hook技术是一种在软件开发中常见的技术,它允许开发者在特定的事件发生时插入自定义的代码逻辑。常见的应用场景包括在函数调用前后执行特定的操作,或者在特定的事...【详细内容】
2023-12-25  沐雨花飞蝶  微信公众号  Tags:Android   点击:(83)  评论:(0)  加入收藏
在Android应用开发中使用NFC功能
NFC介绍NFC是指“近场通讯”(Near Field Communication),它是一种短距离无线通信技术,允许设备在非接触或极短距离内进行通信。NFC通常用于移动支付、门禁系统、智能标签和其他...【详细内容】
2023-12-22  沐雨花飞蝶  微信公众号  Tags:Android   点击:(102)  评论:(0)  加入收藏
关于Android图像Bitmap类,你要知道的一切
Bitmap介绍Bitmap是一种图像文件格式,它由像素阵列组成,每个像素都有自己的颜色信息。在计算机图形学中,Bitmap图像可以被描述为一个二维的矩阵,其中每个元素代表一个像素的颜色...【详细内容】
2023-12-19  沐雨花飞蝶  微信公众号  Tags:Android   点击:(97)  评论:(0)  加入收藏
Android开发中如何进行单元测试?
单元测试介绍单元测试是软件开发中的一种测试方法,用于验证代码中的最小可测试单元(通常是函数或方法)是否按预期工作。单元测试通常由开发人员编写,旨在隔离和测试代码的特定部...【详细内容】
2023-12-11  沐雨花飞蝶  微信公众号  Tags:Android   点击:(167)  评论:(0)  加入收藏
一篇聊聊Jetpack Room实现数据存储持久性
Room介绍Room 是 Android Jetpack 组件库中的一部分,它是用于在 Android 应用中进行本地数据库访问和管理的库。Room 提供了一个抽象层,使开发者能够更轻松地访问 SQLite 数据...【详细内容】
2023-12-08  沐雨花飞蝶  微信公众号  Tags:Jetpack   点击:(142)  评论:(0)  加入收藏
了解Android系统架构中的HAL硬件抽象层
在Android系统中,HAL的存在使得不同厂商的硬件可以统一被上层的应用程序调用,从而提高了系统的兼容性和可移植性。HAL还可以帮助开发者更方便地开发应用程序,因为他们不需要为...【详细内容】
2023-12-06  沐雨花飞蝶  微信公众号  Tags:Android   点击:(200)  评论:(0)  加入收藏
我们一起聊聊 IntentService 与 Service 的区别?
Service介绍Service组件是Android应用开发中的四大组件之一,用于在后台执行长时间运行的操作或处理远程请求。它可以在没有用户界面的情况下执行任务,并且可以与其他应用组件...【详细内容】
2023-12-06  沐雨花飞蝶  微信公众号  Tags:IntentService   点击:(167)  评论:(0)  加入收藏
Android数据对象序列化原理与应用
序列化与反序列化「序列化」是将对象转换为可以存储或传输的格式的过程。在计算机科学中,对象通常是指内存中的数据结构,如数组、列表、字典等。通过序列化,可以将这些对象转换...【详细内容】
2023-11-14  沐雨花飞蝶  微信公众号  Tags:Android   点击:(272)  评论:(0)  加入收藏
你了解Android中的SELinux吗?
SELinux介绍SELinux(Security-Enhanced Linux)是一种安全增强的Linux操作系统,它通过强制访问控制(MAC)机制来提供更高级别的系统安全保护。相比于传统的Linux访问控制机制(DAC),SEL...【详细内容】
2023-11-09  沐雨花飞蝶  微信公众号  Tags:Android   点击:(263)  评论:(0)  加入收藏
站内最新
站内热门
站内头条