The Bug that can’t be changed, the pretense that can’t be written. The public account Yang Zhengyou now focuses on the development of mobile basic platform, covering various knowledge fields such as audio and video, APM and information security. Only do the whole network the most Geek public number, welcome your attention! Wonderful content should not be missed ~
preface
PNG takes up a lot of App space. Is there a tool (assembleDebug, assembleRelease) that automatically converts all PNG images before packaging, including third-party libraries? Before I accidentally found a magic CWEBP conversion tool, is it possible to use this tool to write a Plugin to complete the image conversion, at the same time to support the check of large pictures, picture size configurable. Don’t say much, say dry ~
There are several business pain points to consider before writing a plug-in
-
- How do I get all the RES resources?
-
- When is the automated transformation tool Task executed?
-
- How to check the large picture, and configure the size of the picture, automatically open the picture conversion switch?
For question 1, we can refer to McImage, which is simply a Gradle API, by looking at the documentation linked to the documentation
Given question 2, the timing of the execution of this Task actually depends on the MergeResources Task
For question 3, we can use the Gradle API to customize the API to set the switch, maximum image size, and whitelist the image
convert2WebpConfig{
enableWhenDebug true
maxSize 1024*1024 // 1M
whiteList ["xxx.png"."xxx.png"]
/ /...
}
Copy the code
Picture format conversion development process
Step 1: Create a Gradle Plugin project
Step 2: Add the Png to Webp configuration
Step 3: Configure the Plugin for com.android.application and com.android.library
Realize custom attribute picture converter switch configuration, picture maximum volume configuration, picture add whitelist configuration
Step 5: Move the MAC and Windows image conversion tools to the tool/cwebp directory and add an executable
Step 6 Add auto. Service to make it easier to dynamically add dependencies at compile time
kapt "Com. Google. Auto. Services: auto - service: 1.0 rc4." "
implementation "Com. Google. Auto. Services: auto - service: 1.0 rc4." "
compileOnly "Com. Android. Tools. Build: gradle: 4.0.1." "
testCompileOnly "Com. Android. Tools. Build: gradle: 4.0.1." "
Copy the code
Using AutoService annotations, the reflection used to de-instantiate the object VariantProcessor dynamically registers the Convert2WebpTask task, which is later used to process the Convert2WebpTask task
@AutoService(VariantProcessor::class)
class Convert2WebpVariantProcessor : VariantProcessor {
override fun process(variant: BaseVariant) {
val variantData = (variant as ApplicationVariantImpl).variantData
val tasks = variantData.scope.globalScope.project.tasks
val convert2WebpTask = tasks.findByName("convert2Webp") ?: tasks.create(
"convert2Webp".
Convert2WebpTask::class.java
)
val mergeResourcesTask = variant.mergeResourcesProvider.get()
mergeResourcesTask.dependsOn(convert2WebpTask)
}
}
Copy the code
Step 7 Convert2WebpTask Task execution
7.1 Checking whether the WebP tool exists in the Tools directory
7.2 If the Property Configuration switch is set to false, the task is interrupted
if(! config.enableWhenDebug) {
return@all
}
Copy the code
7.3 Get all Android resource files, traverse the resource files, and add the large images that meet the conditions to the large image list
val dir = variant.allRawAndroidResources.files
/ * *
* Traverse the resource file directory
* /
for (channelDir in dir) {
traverseResDir(channelDir, imageFileList, cacheList, object : IBigImage {
override fun onBigImage(file: File) {
bigImageList.add(file.absolutePath)
}
})
}
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)
}
}
Copy the code
7.4 Filtering non-compliant image files
-
If the image whitelist is added or the file is not in image format, filter it
-
If the image size is correct and the image is a large image, add it to the large image whitelist if there is no image
-
Otherwise add to the pictures directory
/ * *
* Filter images
* /
private fun filterImage(file: File, imageFileList: ArrayList<File>, iBigImage: IBigImage) {
// If the image whitelist is added or the file is not an image format, filter it
if(config.whiteList.contains(file.name) || ! ImageUtil.isImage(file)) {
return
}
// If the image size is correct and the image is a large image, the large image whitelist has no image
if ((config.isCheckSize && ImageUtil.isBigSizeImage(file, config.maxSize))
&& !config.bigImageWhiteList.contains(file.name)
) {
// Add to the list of larger images
iBigImage.onBigImage(file)
}
// Add the image to the picture picture directory
imageFileList.add(file)
}
Copy the code
7.5 Check the big picture and find out the picture reference
private fun checkBigImage() {
if(bigImageList.size ! =0) {
val stringBuffer = StringBuffer("Big Image Detector! ")
.append("ImageSize can't over ${config.maxSize / 1024}kb.\n")
.append("To fix this exception, you can increase maxSize or config them in bigImageWhiteList\n")
.append("Big Image List: \n")
for (fileName in bigImageList) {
stringBuffer.append(fileName)
stringBuffer.append("\n")
}
throw GradleException(stringBuffer.toString())
}
}
Copy the code
7.6 Processing Image compression Tasks
private fun dispatchOptimizeTask(imageFileList: java.util.ArrayList<File>) {
if (imageFileList.size == 0 || bigImageList.isNotEmpty()) {
return
}
val coreNum = Runtime.getRuntime().availableProcessors()
if (imageFileList.size < coreNum) {
for (file in imageFileList) {
optimizeImage(file)
}
} else {
val results = ArrayList<Future<Unit>>()
val pool = Executors.newFixedThreadPool(coreNum)
val part = imageFileList.size / coreNum
for (i in 0 until coreNum) {
val from = i * part
val to = if (i == coreNum - 1) imageFileList.size - 1 else (i + 1) * part - 1
results.add(pool.submit(Callable<Unit> {
for (index infrom.. to) {
optimizeImage(imageFileList[index])
}
}))
}
for (f in results) {
try {
f.get()
} catch (e: Exception) {
println("EHiPlugin Convert2WebpTask#dispatchOptimizeTask() execute wrong.")
}
}
}
}
/ * *
* Compress images
* /
private fun optimizeImage(file: File) {
val path: String = file.path
if (File(path).exists()) {
oldSize += File(path).length()
}
ImageUtil.convert2Webp(file)
calcNewSize(path)
}
Copy the code
7.7 Calculate the size of images before and after compression and the compression time
private fun optimizeImage(file: File) {
val path: String = file.path
if (File(path).exists(a)) {
oldSize += File(path).length()
}
ImageUtil.convert2Webp(file)
calcNewSize(path)
}
dispatchOptimizeTask(imageFileList)
println("Before optimize Size: ${oldSize / 1024}kb")
println("After optimize Size: ${newSize / 1024}kb")
println("Optimize Size: ${(oldSize - newSize) / 1024}kb")
println("CostTotalTime: ${System.currentTimeMillis() - startTime}ms")
println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -")
Copy the code
conclusion
This article is mainly in front of the packaging to the App made a full volume replacement images, image transformation way with the help of Google’s open source tools cwebp, of course we can standardize the image size by white list size and plug-in switch, if you grasp the article content, not only can help your company application thin body, at the same time also can make up for you to Gradle Plugin knowledge desire ~
Refer to the link
- Lavender