图片压缩插件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"

    ] 
}

解释下下面参数的意思:

isCheckSize:是否检查图片的大小

optimizeType:方式,包括压缩和转化成Webp格式

maxSize:大图的判定条件,如果超过这个就会报错,要想不报错,则将改图片放到bigImageWhiteList中,这样就会跳过大图片检测。

enableWhenDebug:是否在dengbug进行build。

isCheckPixels:检查图片的宽和高,如果超过这个就会报错,其限制为maxWidth和maxHeight参数决定

whiteList:加入这个list的不会进行转变

multiThread:是否支持多线程

bigImageWhiteList:加入到这个的图片不需要进行大图检测

源码分析
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处可以理解就是创建一个java bean类对象,其赋值则是上面的McImageConfig里面的东西。并且在build.gradle文件中的McImageConfig这个名字是根据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; //优化方式,webp化、压缩图片
    public boolean enableWhenDebug = true;
    public boolean isCheckPixels = true; //是否检查大像素图片
    public int maxWidth = 1000;
    public int maxHeight = 1000;
    public String[] whiteList = new String[]{}; //优化图片白名单
    public String mctoolsDir = "";
    public boolean isSupportAlphaWebp = false; //是否支持webp化透明通道的图片,如果开启,请确保minSDK >= 18,或做了其他兼容措施
    public boolean multiThread = true;
    public String[] bigImageWhiteList = new String[]{}; //大图检测白名单
}

在2处检查工具,即转化成webP格式的在window,mac,linux平台下的工具。

下载下来需要放在根目录下。

下载路径 https://github.com/smallSohoSolo/McImage/releases

3处则是拿到mergeDebugResourcesTask,一般执行mergeDebugResources会将所有的资源进行合并,这样我们就可拿到所有的资源文件。

而我们的插件则是在这一步之后,遍历拿到所有的图片资源进行操作。

4处则是创建两个个task,分别有debug和release。创建之后可以在AS的右侧的gradle的other文件夹看到这两个task。

然后执行dolast里面的东西。拿到所有的资源文件,然后进行遍历。

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)
        }
    }

在这里采用递归的方式,并且对图片进行一个过滤。

在5处继续对大图进行检查。

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处则是判定是否进行多线程进行图片处理。

7处则是有重新创建一个task。

在8处则是对创建的task的一个先后执行的顺序。

在项目中进行webp图片转换是在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结尾,然后调用工具生成。

如果不支持透明通道的png,则进行压缩,压缩的代码在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算法是有损压缩,不过损失度在可接受范围内。

遇到的问题

,部分转换的会把原来png格式的图片删掉,但是还有部分的没有删掉,会同时存在两张不同格式的图片。到mergeDebugResource这一步报错,很多张图片Duplicate resources。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容