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

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

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

為什么要檢測圖片資源?

  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. 統計各個模塊的圖片資源情況。在合適的時間進行預警

分享到:
標簽:Android
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定