为什么要检测图片资源?
-
避免不小心把未紧缩,不适宜的图片资源打入apk中,造成apk过大
-
图片打入apk前,能够主动化转化,紧缩
完结思路
-
思路一:运用gradle在aapt编译期,扫描汇总资源的文件夹,过滤出不符合要求的图片资源,并抛出异常中断编译
-
思路二:是思路一的进阶。仍是在运用gradle在aapt编译期,查找有没有适宜的gradle task,供给给咱们遍历一切资源的机会
gradle插件完结
gradle插件完结的根底
简单对gradle插件完结进行温习
插件建立
-
新建一个模块
-
装备好该模块的上传装备(mvn.gradle)
-
在build中,对gradleApi进行依靠
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文件,在里边装备上你的插件入口类
例:
implementation-class=com.xxx.checkbigimage.image.ImagePlugin
插件的根本完结
上面讲到要装备一个入口类,这个入口类便是完结了Plugin接口的类,它有一个override fun apply(project: Project)办法,便是咱们插件开端履行的当地,相当于main函数,参数project便是整个工程的装备文件
能够运用以下办法,从咱们运用插件的当地获取到对插件的装备
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
由于咱们的插件应该能够应用在主工程或许模块包上的,所以当咱们插件运转后,咱们要检测当前运用咱们插件的模块是主工程,仍是模块包
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回调
project.afterEvaluate{...}
afterEvaluate该办法便是整个gradle装备文件装备成功后的回调,证明此刻装备已查看完毕,一切task已经就绪,已经能够开端按指定次序运转task了,那么我就需求在这个回调里就事!
Grade 履行次序
履行setting,检测一切module,为每个模块装备project
加载build.properties,生成task履行链表和装备
履行某个指定task,然后会先履行该task所依靠的task
装备完结后,开端遍历variants中一切的变体
project.afterEvaluate { variants.all { variant -> ... }}
咱们的方针task:mergeResourcesProvider
mergeResourcesProvider这个使命便是android插件兼并一切module中资源的task,看姓名就知道了。
咱们能够从变体中获取这个task方针
val mergeResourcesTask = variant.mergeResourcesProvider.get()
那么,咱们自己的使命呢?
gradle api供给给咱们能够在代码中生成task的办法
val mcPicTask = project.task("CheckBigImage${variant.name.capitalize()}")
运用project.task(“taskname”)来生成一个咱们自己需求履行的task
然后咱们编写这个task的逻辑,也是本插件的逻辑
mcPicTask.doLast {...}
variant里边有各种方针,allRawAndroidResources刚好便是咱们需求的。它只要3.3以上才会有。
val dir = variant.allRawAndroidResources.files
这个dir方针,便是android一切文件资源的files集合
ok。让咱们遍历这个文件list吧!
for (channelDir: File in dir) {check(channelDir)}fun check(file: File) { if(file.isDirectory) { check(file)} else { process(file)}}
假如遇到文件夹,这儿是一个递归调用。
假如遇到文件,就能够按照自己的规则处理了。
挂钩mergeResourcesProvider
咱们task写好后,需求和mergeResourcesProvider挂钩
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
使mergeResourcesTask依靠咱们的mcPicTask,当mergeResourcesTask履行前,就会先履行咱们的mcPicTask了!!
留意:此处直接运用mergeResourcesTask体系task依靠咱们的task,咱们的task履行次序会和mergeResourcesTask原有的依靠稠浊在一起,不可控。后面讲一种可控的办法
阻拦图片的逻辑
这个逻辑应该完结在上面伪代码process(file:File)办法中
-
首要咱们只需求处理图片,所以对参数file进行首轮过滤,只留下后缀名为图片的文件
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)}
-
需求查看图片的宽高的话,能够运用java的原生api
val sourceImg = ImageIO.read(FileInputStream(imgFile))if (sourceImg.height > maxHeight || sourceImg.width > maxWidth) { ...
-
需求过滤图片巨细的话
if (imgFile.length() >= maxSize) { LogUtil.log(SIZE_TAG, imgFile.path, true.toString()) return true}
紧缩图片逻辑
这儿咱们只处理一般图片转化为webp的紧缩。jpg,png的自紧缩原理相同,就不复述了
想紧缩转化webp图片,需求用到转化东西
google供给的有一套指令行转化东西:cwebp ,各个渠道都有,咱们去下载一套,放在咱们的主工程文件夹下就能够了
这儿需求留意的是:为了方便,假如把cwebp指令行程序放在环境变量下,那么履行指令时,拼接指令时,直接拼接cwebp就好。
假如运用工程目录下的cwebp,履行前,需求在cwebp指令前面拼接它地点的工程目录。
运用
project.rootDir.path
能够获取工程的根目录
怎么履行指令行程序呢?
能够运用java的api
Runtime.getRuntime().exec(cmd)
现在能够愉快的转化图片了
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)能够获取自己的依靠树
在这儿便是
(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)也依靠进来
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
这时便是D->B->C,A->C
最终,回到咱们刚刚阻拦图片的逻辑的最终代码
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
就变成了A->D->B->C,也便是mergeResourcesTask->mcPicTask->chmodTask->原依靠task,依靠和履行次序是相反的。
正常的代码便是
(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
android.enableAapt2=false
在mergeDebugResources后,processDebugResources前扫描文件夹
前面说过,mergeDebugResources是兼并一切module的资源文件到固定目录
那么processDebugResources是什么呢?便是处理这些已经兼并完结的文件,生成R.id,资源索引之类的文件
那么咱们的使命就必须插入到processDebugResources前面,而不是mergeDebugResources了
办法二
仔细翻了翻MergeResources里边的办法,有一个getResSet和computeResourceSetList看起来有点意思。那么computeResourceSetList中又调用了getResSet。最终发现computeResourceSetList公然能够获取一切文件列表。
/*** 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成功后的文件夹
-
不会影响源代码
优化
-
已经扫描过的,且承认无法经过webp优化的图片,把这些名称写入一个本地文件,优化扫描速度
未来想做的事情
计算
-
阻拦了多少图片
-
转化了多少图片
3. 计算各个模块的图片资源情况。在适宜的时间进行预警