使用
classpath 'com.smallsoho.mobcase:McImage:1.5.1'
apply plugin: 'McImage'
McImageConfig {
isCheckSize true
optimizeType "ConvertWebp" //Optimize Type,"ConvertWebp" or "Compress",default "Compress", "CompressWebp" is a better compression ratio but it don't support api < 18
maxSize 1*1024*1024 //big image size threshold,default 1MB
enableWhenDebug true //switch in debug build,default true
isCheckPixels true // Whether to detect image pixels of width and height,default true
maxWidth 1200 //default 1000
maxHeight 1000 //default 1000
whiteList = [
]
mctoolsDir "$rootDir"
isSupportAlphaWebp true
multiThread true
bigImageWhiteList = [
"icon_login_bg.png"
]
}
解釋下下面參數(shù)的意思:
isCheckSize:是否檢查圖片的大小
optimizeType:方式,包括壓縮和轉(zhuǎn)化成Webp格式
maxSize:大圖的判定條件,如果超過這個就會報錯,要想不報錯,則將改圖片放到bigImageWhiteList中,這樣就會跳過大圖片檢測。
enableWhenDebug:是否在dengbug進(jìn)行build。
isCheckPixels:檢查圖片的寬和高,如果超過這個就會報錯,其限制為maxWidth和maxHeight參數(shù)決定
whiteList:加入這個list的不會進(jìn)行轉(zhuǎn)變
multiThread:是否支持多線程
bigImageWhiteList:加入到這個的圖片不需要進(jìn)行大圖檢測
源碼分析
override fun apply(project: Project) {
mcImageProject = project
//check is library or application
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
}
//set config
project.extensions.create("McImageConfig", Config::class.java)
mcImageConfig = project.property("McImageConfig") as Config // 1
project.afterEvaluate {
variants.all { variant ->
variant as BaseVariantImpl
checkMcTools(project)//2
val mergeResourcesTask = variant.mergeResourcesProvider.get()//3
val mcPicTask = project.task("McImage${variant.name.capitalize()}")//4
mcPicTask.doLast {
val dir = variant.allRawAndroidResources.files
val cacheList = ArrayList<String>()
val imageFileList = ArrayList<File>()
for (channelDir: File in dir) {
traverseResDir(channelDir, imageFileList, cacheList, object : IBigImage {
override fun onBigImage(file: File) {
bigImgList.add(file.absolutePath)
}
})
}
checkBigImage()//5
val start = System.currentTimeMillis()
mtDispatchOptimizeTask(imageFileList)//6
LogUtil.log(sizeInfo())
LogUtil.log("---- McImage Plugin End ----, Total Time(ms) : ${System.currentTimeMillis() - start}")
}
//chmod task
val chmodTaskName = "chmod${variant.name.capitalize()}"
val chmodTask = project.task(chmodTaskName)//7
chmodTask.doLast {
//chmod if linux
if (Tools.isLinux()) {
Tools.chmod()
}
}
//inject task
(project.tasks.findByName(chmodTask.name) as Task).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))//8
}
}
}
1處可以理解就是創(chuàng)建一個java bean類對象,其賦值則是上面的McImageConfig里面的東西。并且在build.gradle文件中的McImageConfig這個名字是根據(jù)project.extensions.create("McImageConfig", Config::class.java)確定的,而且必須要一致,不然會報錯。具體字段如下:
public class Config {
public static final String OPTIMIZE_WEBP_CONVERT = "ConvertWebp"; //webp化
public static final String OPTIMIZE_COMPRESS_PICTURE = "Compress"; //壓縮圖片
public float maxSize = 1024 * 1024;
public boolean isCheckSize = true; //是否檢查大體積圖片
public String optimizeType = OPTIMIZE_WEBP_CONVERT; //優(yōu)化方式,webp化、壓縮圖片
public boolean enableWhenDebug = true;
public boolean isCheckPixels = true; //是否檢查大像素圖片
public int maxWidth = 1000;
public int maxHeight = 1000;
public String[] whiteList = new String[]{}; //優(yōu)化圖片白名單
public String mctoolsDir = "";
public boolean isSupportAlphaWebp = false; //是否支持webp化透明通道的圖片,如果開啟,請確保minSDK >= 18,或做了其他兼容措施
public boolean multiThread = true;
public String[] bigImageWhiteList = new String[]{}; //大圖檢測白名單
}
在2處檢查工具,即轉(zhuǎn)化成webP格式的在window,mac,linux平臺下的工具。
下載下來需要放在根目錄下。
下載路徑 https://github.com/smallSohoSolo/McImage/releases
3處則是拿到mergeDebugResourcesTask,一般執(zhí)行mergeDebugResources會將所有的資源進(jìn)行合并,這樣我們就可拿到所有的資源文件。
而我們的插件則是在這一步之后,遍歷拿到所有的圖片資源進(jìn)行操作。
4處則是創(chuàng)建兩個個task,分別有debug和release。創(chuàng)建之后可以在AS的右側(cè)的gradle的other文件夾看到這兩個task。
然后執(zhí)行dolast里面的東西。拿到所有的資源文件,然后進(jìn)行遍歷。
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)
}
}
在這里采用遞歸的方式,并且對圖片進(jìn)行一個過濾。
在5處繼續(xù)對大圖進(jìn)行檢查。
private fun checkBigImage() {
if (bigImgList.size != 0) {
val stringBuffer = StringBuffer("You have big Imgages with big size or large pixels," +
"please confirm whether they are necessary or whether they can to be compressed. " +
"If so, you can config them into bigImageWhiteList to fix this Exception!!!\n")
for (i: Int in 0 until bigImgList.size) {
stringBuffer.append(bigImgList[i])
stringBuffer.append("\n")
}
throw GradleException(stringBuffer.toString())
}
}
如果在build.gradle里面配置了,則不滿足條件的則報異常。
6處則是判定是否進(jìn)行多線程進(jìn)行圖片處理。
7處則是有重新創(chuàng)建一個task。
在8處則是對創(chuàng)建的task的一個先后執(zhí)行的順序。
在項(xiàng)目中進(jìn)行webp圖片轉(zhuǎn)換是在WebpUtils類中。
private fun formatWebp(imgFile: File) {
if (ImageUtil.isImage(imgFile)) {
val webpFile = File("${imgFile.path.substring(0, imgFile.path.lastIndexOf("."))}.webp")
Tools.cmd("cwebp", "${imgFile.path} -o ${webpFile.path} -m 6 -quiet")
if (webpFile.length() < imgFile.length()) {
LogUtil.log(TAG, imgFile.path, imgFile.length().toString(), webpFile.length().toString())
if (imgFile.exists()) {
imgFile.delete()
}
} else {
//如果webp的大的話就拋棄
if (webpFile.exists()) {
webpFile.delete()
}
LogUtil.log("[${TAG}][${imgFile.name}] do not convert webp because the size become larger!")
}
}
}
將每個文件以.webp結(jié)尾,然后調(diào)用工具生成。
如果不支持透明通道的png,則進(jìn)行壓縮,壓縮的代碼在CompressUtil 中。
fun compressImg(imgFile: File) {
if (!ImageUtil.isImage(imgFile)) {
return
}
val oldSize = imgFile.length()
val newSize: Long
if (ImageUtil.isJPG(imgFile)) {
val tempFilePath: String = "${imgFile.path.substring(0, imgFile.path.lastIndexOf("."))}_temp" +
imgFile.path.substring(imgFile.path.lastIndexOf("."))
Tools.cmd("guetzli", "${imgFile.path} $tempFilePath")
val tempFile = File(tempFilePath)
newSize = tempFile.length()
LogUtil.log("newSize = $newSize")
if (newSize < oldSize) {
val imgFileName: String = imgFile.path
if (imgFile.exists()) {
imgFile.delete()
}
tempFile.renameTo(File(imgFileName))
} else {
if (tempFile.exists()) {
tempFile.delete()
}
}
} else {
Tools.cmd("pngquant", "--skip-if-larger --speed 1 --nofs --strip --force --output ${imgFile.path} -- ${imgFile.path}")
newSize = File(imgFile.path).length()
}
LogUtil.log(TAG, imgFile.path, oldSize.toString(), newSize.toString())
}
}
這里的壓縮有兩種方式,對于JPG格式的則采用guetzli壓縮,PNG格式的則采用pngquant壓縮。guetzli壓縮是無損壓縮,壓縮時間比較長,壓縮率只能到百分之30.而pngquant算法是有損壓縮,不過損失度在可接受范圍內(nèi)。
遇到的問題
,部分轉(zhuǎn)換的會把原來png格式的圖片刪掉,但是還有部分的沒有刪掉,會同時存在兩張不同格式的圖片。到mergeDebugResource這一步報錯,很多張圖片Duplicate resources。