日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

為什么要檢測(cè)圖片資源?

  1. 避免不小心把未壓縮,不合適的圖片資源打入apk中,造成apk過(guò)大
  2. 圖片打入apk前,可以自動(dòng)化轉(zhuǎn)換,壓縮

實(shí)現(xiàn)思路

  1. 思路一:使用gradle在aapt編譯期,掃描匯總資源的文件夾,過(guò)濾出不符合要求的圖片資源,并拋出異常中斷編譯
  2. 思路二:是思路一的進(jìn)階。還是在使用gradle在aapt編譯期,查找有沒有合適的gradle task,提供給我們遍歷所有資源的機(jī)會(huì)

gradle插件實(shí)現(xiàn)

gradle插件實(shí)現(xiàn)的基礎(chǔ)

簡(jiǎn)單對(duì)gradle插件實(shí)現(xiàn)進(jìn)行復(fù)習(xí)

插件搭建

  • 新建一個(gè)模塊
  • 配置好該模塊的上傳配置(mvn.gradle)
  • 在build中,對(duì)gradleApi進(jìn)行依賴
  • scss復(fù)制代碼
  • Apply plugin: 'kotlin' //插件如果使用kotlin實(shí)現(xiàn),需要依賴kotlindependencies { implementation gradleApi() implementation localGroovy() implementation 'com.Android.tools.build:gradle:3.4.2'}
  • 在mAIn下面新建resources.META-INF.gradle-plugins文件夾
  • 在該文件夾中創(chuàng)建一個(gè)和module同名的.properties文件,在里面配置上你的插件入口類
  • 例:
  • arduino復(fù)制代碼
  • implementation-class=com.xxx.checkbigimage.image.ImagePlugin

插件的基本實(shí)現(xiàn)

上面講到要配置一個(gè)入口類,這個(gè)入口類就是實(shí)現(xiàn)了Plugin接口的類,它有一個(gè)override fun apply(project: Project)方法,就是我們插件開始執(zhí)行的地方,相當(dāng)于main函數(shù),參數(shù)project就是整個(gè)工程的配置文件

可以使用以下方法,從我們使用插件的地方獲取到對(duì)插件的配置

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

Config是一個(gè)java bean數(shù)據(jù)類

"config"是我們?cè)赽uild中的配置名稱

這樣一個(gè)簡(jiǎn)單gradle插件就實(shí)現(xiàn)了

圖片資源檢測(cè)插件實(shí)現(xiàn)

上面說(shuō)了為什么要實(shí)現(xiàn)這樣一個(gè)插件和該如何實(shí)現(xiàn)一個(gè)gradle插件,那么下面就具體介紹該插件的實(shí)現(xiàn)過(guò)程

想要的功能

  • 檢測(cè)和攔截功能
    • 檢測(cè)是否有大小超標(biāo)的圖片
    • 檢測(cè)是否有寬高超標(biāo)的圖片
    • 攔截非webp資源,并進(jìn)行提示
  • 自動(dòng)化壓縮
    • 自動(dòng)壓縮png,jpg等資源
  • 白名單設(shè)置
  • 一些統(tǒng)計(jì)功能

實(shí)現(xiàn)過(guò)程

上面已經(jīng)說(shuō)了gradle插件的實(shí)現(xiàn),那么我們就從apply方法開始說(shuō)起。

瞄準(zhǔn)task掛鉤

既然是要hock android打包的編譯過(guò)程,那就要尋找android打包時(shí),合適的task

想hock task,首先應(yīng)該拿到任務(wù)task集合

在android插件編譯生成apk的過(guò)程中,有好多task都可以生成apk,它們的名字基于Build Types 和 Product Flavor 生成。那么我們?cè)趺茨玫骄唧w生成apk的task組呢?

為了解決這個(gè)問(wèn)題。android插件有幾個(gè)屬性,就是我們平常配置的變體(所謂的環(huán)境),androd中有三類變體

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

這三個(gè)對(duì)象都是實(shí)現(xiàn)了BaseVariant(BaseVariantImpl為實(shí)現(xiàn)這個(gè)接口的抽象類)接口的類的對(duì)象的集合

屬性名

屬性類型

說(shuō)明

name

String

Variant 的名字,唯一

description

String

Variant 的描述說(shuō)明

dirName

String

Variant 的子文件夾名,唯一。可能有不止一個(gè)子文件夾,例如 “debug/flavor1”

baseName

String

Variant 輸出的基礎(chǔ)名字,必須唯一

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 的標(biāo)志性 assemble task

因?yàn)槲覀兊牟寮?yīng)該可以應(yīng)用在主工程或者模塊包上的,所以當(dāng)我們插件運(yùn)行后,我們要檢測(cè)當(dāng)前使用我們插件的模塊是主工程,還是模塊包

kotlin復(fù)制代碼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的任務(wù)

我們想hock住android插件運(yùn)行的task任務(wù),就需要一個(gè)重要的gradle回調(diào)

erlang復(fù)制代碼project.afterEvaluate{...}

afterEvaluate該方法就是整個(gè)gradle配置文件配置成功后的回調(diào),證明此時(shí)配置已檢查完畢,所有task已經(jīng)就緒,已經(jīng)可以開始按指定順序運(yùn)行task了,那么我就需要在這個(gè)回調(diào)里辦事!

Grade 執(zhí)行順序

執(zhí)行setting,檢測(cè)所有module,為每個(gè)模塊配置project

加載build.properties,生成task執(zhí)行鏈表和配置

執(zhí)行某個(gè)指定task,然后會(huì)先執(zhí)行該task所依賴的task

配置完成后,開始遍歷variants中所有的變體

arduino復(fù)制代碼project.afterEvaluate {  variants.all { variant ->    ...  }}

我們的目標(biāo)task:mergeResourcesProvider

mergeResourcesProvider這個(gè)任務(wù)就是android插件合并所有module中資源的task,看名字就知道了。

我們可以從變體中獲取這個(gè)task對(duì)象

ini復(fù)制代碼val mergeResourcesTask = variant.mergeResourcesProvider.get()

那么,我們自己的任務(wù)呢?

gradle api提供給我們可以在代碼中生成task的方法

ini復(fù)制代碼val mcPicTask = project.task("CheckBigImage${variant.name.capitalize()}")

使用project.task("taskname")來(lái)生成一個(gè)我們自己需要執(zhí)行的task

然后我們編寫這個(gè)task的邏輯,也是本插件的邏輯

復(fù)制代碼mcPicTask.doLast {...}

variant里面有各種對(duì)象,allRawAndroidResources恰好就是我們需要的。它只有3.3以上才會(huì)有。

ini復(fù)制代碼val dir = variant.allRawAndroidResources.files

這個(gè)dir對(duì)象,就是android所有文件資源的files集合

ok。讓我們遍歷這個(gè)文件list吧!

scss復(fù)制代碼for (channelDir: File in dir) {check(channelDir)}fun check(file: File) { if(file.isDirectory) {   check(file)} else {   process(file)}}

如果遇到文件夾,這里是一個(gè)遞歸調(diào)用。

如果遇到文件,就可以按照自己的規(guī)則處理了。

掛鉤mergeResourcesProvider

我們task寫好后,需要和mergeResourcesProvider掛鉤

less復(fù)制代碼mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

使mergeResourcesTask依賴我們的mcPicTask,當(dāng)mergeResourcesTask執(zhí)行前,就會(huì)先執(zhí)行我們的mcPicTask了!!

注意:此處直接使用mergeResourcesTask系統(tǒng)task依賴我們的task,我們的task執(zhí)行順序會(huì)和mergeResourcesTask原有的依賴混雜在一起,不可控。后面講一種可控的方法

攔截圖片的邏輯

這個(gè)邏輯應(yīng)該實(shí)現(xiàn)在上面?zhèn)未aprocess(file:File)方法中

  1. 首先我們只需要處理圖片,所以對(duì)參數(shù)file進(jìn)行首輪過(guò)濾,只留下后綴名為圖片的文件
  2. kotlin復(fù)制代碼
  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復(fù)制代碼
  6. val sourceImg = ImageIO.read(FileInputStream(imgFile))if (sourceImg.height > maxHeight || sourceImg.width > maxWidth) { ...
  7. 需要過(guò)濾圖片大小的話
  8. lua復(fù)制代碼
  9. if (imgFile.length() >= maxSize) { LogUtil.log(SIZE_TAG, imgFile.path, true.toString()) return true}

壓縮圖片邏輯

這里我們只處理普通圖片轉(zhuǎn)換為webp的壓縮。jpg,png的自壓縮原理相同,就不復(fù)述了

想壓縮轉(zhuǎn)換webp圖片,需要用到轉(zhuǎn)換工具

google提供的有一套命令行轉(zhuǎn)換工具:cwebp ,各個(gè)平臺(tái)都有,我們?nèi)ハ螺d一套,放在我們的主工程文件夾下就可以了

這里需要注意的是:為了方便,如果把cwebp命令行程序放在環(huán)境變量下,那么執(zhí)行命令時(shí),拼接命令時(shí),直接拼接cwebp就好。

如果使用工程目錄下的cwebp,執(zhí)行前,需要在cwebp命令前面拼接它所在的工程目錄。

使用

lua復(fù)制代碼project.rootDir.path

可以獲取工程的根目錄

如何執(zhí)行命令行程序呢?

可以使用java的api

scss復(fù)制代碼Runtime.getRuntime().exec(cmd)

現(xiàn)在可以愉快的轉(zhuǎn)換圖片了

bash復(fù)制代碼Tools.cmd("cwebp", "${imgFile.path} -o ${webpFile.path} -m 6 -quiet")

轉(zhuǎn)換后,記得把原圖刪掉

優(yōu)化點(diǎn):

有的圖片轉(zhuǎn)換后比以前還大,這里需要注意

第一次掃描過(guò)后的無(wú)法優(yōu)化的圖片,可以存在一個(gè)text文本當(dāng)中,第二次執(zhí)行時(shí),就不要去轉(zhuǎn)換了

系統(tǒng)兼容

在linux系統(tǒng)上,創(chuàng)建和刪除文件都需要權(quán)限,如果沒有權(quán)限就會(huì)失敗。這時(shí)需要先判斷當(dāng)前的操作系統(tǒng)是不是linux,如果是,可以執(zhí)行chmod 755 -R ${FileUtil.getRootDirPath()}添加權(quán)限

這里可以優(yōu)化一下,在我們的mcPicTask前面再加一個(gè)task,用來(lái)添加權(quán)限,這個(gè)task只對(duì)文件夾進(jìn)行遞歸添加就可以了,比一個(gè)一個(gè)文件要來(lái)的快。

因?yàn)槲覀儾磺宄到y(tǒng)的task(mergeResourcesTask)都依賴了哪些,那么如何在依賴上再加依賴,如何插入task呢?

gradle api提供給了我們一個(gè)方法,
xxx.taskDependencies.getDependencies(xxx)可以獲取自己的依賴樹

在這里就是

scss復(fù)制代碼(project.tasks.findByName(chmodTask.name) asTask).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))

讓chmodTask依賴mergeResourcesTask的依賴。假如mergeResourcesTask是A,chmodTask是B。A依賴一個(gè)系統(tǒng)的C。那么上面的代碼就是讓B依賴了C。這時(shí)的task圖就是 B->C,A->C

接下來(lái)我們?cè)侔裮cPicTask(簡(jiǎn)稱為D)也依賴進(jìn)來(lái)

arduino復(fù)制代碼(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)

這時(shí)就是D->B->C,A->C

最后,回到我們剛剛攔截圖片的邏輯的最后代碼

less復(fù)制代碼mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

就變成了A->D->B->C,也就是mergeResourcesTask->mcPicTask->chmodTask->原依賴task,依賴和執(zhí)行順序是相反的。

正常的代碼就是

scss復(fù)制代碼(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。執(zhí)行順序打印

......

Task :app:mainApkListPersistenceDebug UP-TO-DATE

Task :app:CheckBigImageDebug

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

......

而使用正規(guī)的插入法順序

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,方便無(wú)比,直接在merge前遍歷它就好了。

那么3.3之前的版本呢?就是我們最初的設(shè)想了,在合并完各個(gè)module資源后,掃描merge文件夾!這里又有aapt和aapt2的差異

方法一

關(guān)掉aapt2

ini復(fù)制代碼android.enableAapt2=false

mergeDebugResources后,processDebugResources前掃描文件夾

前面說(shuō)過(guò),mergeDebugResources是合并所有module的資源文件到固定目錄

那么processDebugResources是什么呢?就是處理這些已經(jīng)合并完成的文件,生成R.id,資源索引之類的文件

那么我們的任務(wù)就必須插入到processDebugResources前面,而不是mergeDebugResources

方法二

仔細(xì)翻了翻MergeResources里面的方法,有一個(gè)getResSet和computeResourceSetList看起來(lái)有點(diǎn)意思。那么computeResourceSetList中又調(diào)用了getResSet。最后發(fā)現(xiàn)computeResourceSetList果然可以獲取所有文件列表。

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

注釋也很有意思,有道翻譯一下:根據(jù)所有輸入計(jì)算執(zhí)行期間使用的資源集列表。

鑒于該方法是友元方法,就使用反射獲取。

因?yàn)?.3之后,aapt2是強(qiáng)制開啟的,并且aapt2 merge后的文件不是原文件了哦!注意aapt1合并后,還是正常的xxx.png。aapt2合并后的文件擴(kuò)展名為flat

所以,方法一不支持大于3.3的gradle版本。方法二支持。可以平滑過(guò)渡到新版本。鑒于新版本的gradle直接提供了allRawAndroidResources這樣的方法,所以在3.3以上,直接使用它就可以了

allRawAndroidResources和掃描合并文件夾的差異。

allRawAndroidResources提供的是未合并前的資源路徑

  • 源碼依賴的module,編譯時(shí),會(huì)獲取該文件的真實(shí)路徑
  • aar依賴的路徑,會(huì)獲取到aar-cache的路徑
  • 所以:如果開啟自動(dòng)轉(zhuǎn)換webp功能你會(huì)發(fā)現(xiàn):你本地源代碼中的png,都轉(zhuǎn)成了webp

掃描合并文件夾,掃描的是編譯期merge成功后的文件夾

  • 不會(huì)影響源代碼
  •  

優(yōu)化

  1. 已經(jīng)掃描過(guò)的,且確認(rèn)無(wú)法經(jīng)過(guò)webp優(yōu)化的圖片,把這些名稱寫入一個(gè)本地文件,優(yōu)化掃描速度

未來(lái)想做的事情

統(tǒng)計(jì)

  1. 攔截了多少圖片
  2. 轉(zhuǎn)換了多少圖片
  3. 3. 統(tǒng)計(jì)各個(gè)模塊的圖片資源情況。在合適的時(shí)間進(jìn)行預(yù)警

分享到:
標(biāo)簽:Android
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定