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

    1. How do I get all the RES resources?
    1. When is the automated transformation tool Task executed?
    1. 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