圖片壓縮插件McImage

使用
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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容