1+ package com.fmt.github.plugin
2+
3+ import com.android.build.gradle.BaseExtension
4+ import com.tinify.Tinify
5+ import groovy.json.JsonOutput
6+ import groovy.json.JsonSlurper
7+ import org.apache.commons.codec.digest.DigestUtils
8+ import org.gradle.api.DefaultTask
9+ import org.gradle.api.tasks.SourceSet
10+ import org.gradle.api.tasks.TaskAction
11+ import java.text.DecimalFormat
12+ import java.util.concurrent.CopyOnWriteArrayList
13+ import java.util.concurrent.CountDownLatch
14+ import java.util.concurrent.ExecutorService
15+ import java.util.concurrent.Executors
16+ import java.util.concurrent.TimeUnit
17+
18+ /**
19+ * 自定义压缩任务
20+ */
21+ class CompressTask extends DefaultTask {
22+
23+ CompressExt mCompressOption
24+ ExecutorService mExecutorService
25+
26+ CompressTask () {
27+ mCompressOption = project. compressOption
28+ mExecutorService = Executors . newCachedThreadPool()
29+ }
30+
31+ @TaskAction
32+ void run () {
33+ println (" CompressTask start ..." )
34+ // 1.判断ApiKey是否为空
35+ if (mCompressOption. apiKey == null ) {
36+ throw new IllegalArgumentException (" TinyPng ApiKey should be set" )
37+ }
38+ // 2.校验ApiKey是否合法
39+ try {
40+ Tinify . setKey(mCompressOption. apiKey)
41+ Tinify . validate()
42+ } catch (Exception ignored) {
43+ throw new IllegalArgumentException (" TinyPng ApiKey is not available" )
44+ }
45+
46+ // 3.加载图片资源所在目录 -->res目录
47+ def androidExt = project. extensions. findByType(BaseExtension . class)
48+ def sourceSets = androidExt. sourceSets. getByName(SourceSet . MAIN_SOURCE_SET_NAME )
49+ def resDir = sourceSets. res. srcDirs. first()
50+
51+ // 4.读取已经压缩过的图片集合
52+ def compressedList = loadCompressedImage()
53+
54+ // 5.过滤res目录下的文件夹 -->只对drawable-、mipmap-做处理
55+ def drawablePattern = " drawable-[a-z]*"
56+ def mipmapPattern = " mipmap-[a-z]*"
57+ def validFiles = new ArrayList<File > ()
58+ resDir. listFiles(). each { dir ->
59+ if (dir. name. matches(drawablePattern) || dir. name. matches(mipmapPattern)) {
60+ dir. listFiles(). each { file ->
61+ if (validFile(file, compressedList)) {
62+ validFiles. add(file)
63+ }
64+ }
65+ }
66+ }
67+ // 6.开启多线程进行图片压缩
68+ CountDownLatch countDownLatch = new CountDownLatch (validFiles. size())
69+ validFiles. each { file ->
70+ mExecutorService. execute(new Runnable () {
71+ @Override
72+ void run () {
73+ def ret = compressImage(file)
74+ compressedList. add(ret)
75+ countDownLatch. countDown()
76+ }
77+ })
78+ }
79+ // 7.等待所有的图片都压缩完成,再将压缩后的图片信息写入json文件中
80+ countDownLatch. await(10 , TimeUnit . MINUTES )
81+ writeCompressResource(compressedList)
82+
83+ println (" CompressionTask finished" )
84+ }
85+
86+ // 将压缩后的图片信息写入json文件中
87+ def writeCompressResource (List<CompressImageInfo > compressImageList ) {
88+ def compressFile = new File (project. projectDir, " compress-image.json" )
89+ JsonOutput jsonOutput = new JsonOutput ()
90+ def json = jsonOutput. toJson(compressImageList)
91+ compressFile. write(json, " utf-8" )
92+ }
93+
94+ // 压缩图片
95+ def compressImage (File file ) {
96+ println (" compress ${ file.name} start" )
97+ // 判断是否需要覆盖原图
98+ def dstFile
99+ if (mCompressOption. suffix == null ) {
100+ dstFile = new File (file. parent, file. name)
101+ } else {
102+ def fileName = file. name
103+ def prefix = fileName. substring(0 , fileName. lastIndexOf(" ." ))
104+ def suffix = fileName. substring(fileName. lastIndexOf(" ." ))
105+ dstFile = new File (file. parent, " $prefix ${ mCompressOption.suffix} $suffix " )
106+ }
107+
108+ if (! dstFile. exists()) {
109+ dstFile. createNewFile()
110+ }
111+
112+ // 使用Tinify进行压缩
113+ def source = Tinify . fromFile(file. path)
114+ source. toFile(dstFile. path)
115+
116+ println (" compress ${ file.name} finished" )
117+
118+ return new CompressImageInfo (file. name, file. path, formatSize(file. size()), formatSize(dstFile. size()), DigestUtils . md5Hex(file. newInputStream()))
119+ }
120+
121+ // 格式化文件的大小
122+ static def formatSize (long fileSize ) {
123+ def df = new DecimalFormat (" #.0" )
124+ if (fileSize < 1024 ) {
125+ return " ${ fileSize} B"
126+ } else if (fileSize < 1024 * 1024 ) {
127+ return " ${ df.format(fileSize * 1.0f / 1024)} KB"
128+ } else {
129+ return " ${ df.format(fileSize * 1.0f / 1024 / 1024)} M"
130+ }
131+ }
132+
133+ // 获取已经压缩过的图片集合
134+ def loadCompressedImage () {
135+ def compressFile = new File (project. projectDir, " compress-image.json" )
136+ if (! compressFile. exists()) {
137+ compressFile. createNewFile()
138+ } else {
139+ try {
140+ def ret = new JsonSlurper (). parse(compressFile, " utf-8" )
141+ if (ret instanceof List<CompressImageInfo > ) {
142+ return ret
143+ }
144+ } catch (Exception e) {
145+ throw e
146+ }
147+ }
148+ return new CopyOnWriteArrayList<CompressImageInfo > ()
149+ }
150+
151+ // 判断图片是否合法
152+ def validFile (File file , List<CompressImageInfo > compressList ) {
153+ if (file. isDirectory()) {
154+ return false
155+ }
156+ if (file. name. endsWith(" .9" )) {
157+ println (" skip file ${ file.name} which is .9" )
158+ return false
159+ }
160+ if (file. name. endsWith(" .xml" )) {
161+ println (" skip file ${ file.name} which is .xml" )
162+ return false
163+ }
164+ if (mCompressOption. suffix != null && file. name. contains(mCompressOption. suffix)) {
165+ println (" skip file ${ file.name} which has compressed" )
166+ return false
167+ }
168+ long fileSize = file. size()
169+ if (fileSize < mCompressOption. limitSize) {
170+ println (" skip file ${ file.name} which file size less than ${ mCompressOption.limitSize} " )
171+ return false
172+ }
173+ def md5 = DigestUtils . md5Hex(file. newInputStream())
174+ def match = compressList. any { imageInfo ->
175+ return imageInfo. md5 == md5
176+ }
177+ if (match) {
178+ println (" skip file ${ file.name} which has compressed" )
179+ return false
180+ }
181+ return true
182+ }
183+ }
0 commit comments