one Abstract
With the increase of our business, our package size is getting bigger and bigger. The smaller the package size is, the higher the conversion rate of our APP will be. This chapter describes the unified processing logic of pictures. The biggest factor taking up package volume
- So file, using dynamic loading so, do not pack SO in the package, see dynamic loading SO
- Picture processing, using AOP ideas to process the APP, the third party AAR and the third party JAR in the picture, the content of this section
two Some ways to reduce package size
- Compress the code to remove unused resources. Use ProGuard for compression
- Reduce enumeration use
- Streamlining language Resources
- Using vector diagrams
- Only specific densities are supported
- Resource confusion AndroidResGuard
- R File inline Bytex
- pluggable
3. Write a plugin for compressing images and converting PNG and JPG to WebP
So let’s first think about how do we implement this function
- How to get a unified picture?
- What tools or code do you use to compress?
- How to convert compressed images into WebP?
- What images cannot be compressed and converted to WebP?
Based on question 1: we can look for the answer in the task packaged apK, and we can take the input file in the Task mergeResources. To get all the images and compress them, For specific packaging tasks, see completing the Android skill tree — from the AGP build process to the APK packaging process, After another from gradle 4 com. Android. Build. Gradle. Internal. AbstractAppTaskManager. In Java createCommonTasks method can also watch out. PNG can be compressed using PngCrush, PngQuant or Zopflipng, JPG can be compressed using packJPG and Guetzli. Some logos, as well as the larger ones after transformation, can be used
Start coding
Create the Plugin project
Create a plugin project, create a buildSrc directory in the main project, create a SRC /main folder, create resources in main to mark our plugin, and create a meta-INF /gradle-plugins folder in Resources. In creating com. Nzy. Plugin. The properties of profile, configuration inside our plugin, implementation – class = com. Nzy. Plugin. CwebpPlugin,
Create the Java plugin CwebpPlugin class
public class CwebpPlugin implements Plugin<Project> {
int oldSize = 0;
int newSize = 0;
public static final String TAG = "CwebpPlugin :";
/** * User config configuration */
private WebpConfig mConfig;
/** * Prints logs */
private Logger mLogger;
private ArrayList<String> bigImgList = new ArrayList<String>();
private Project mProject;
private AppExtension mAppExtension;
private ArrayList<String> cacheList = new ArrayList<String>();
/** * all image files */
private ArrayList<File> imageFileList = new ArrayList<File>();
@Override
public void apply(Project project) {
if(! project.getPlugins().hasPlugin(AppPlugin.class)) {throw new GradleException("The plugin must be used in the Android Application plugin");
}
// Get the Android configuration
mAppExtension = project.getExtensions().getByType(AppExtension.class);
// Create an extension to WebpConfig that users can use in build.gradle
project.getExtensions().create("webpConfig", WebpConfig.class);
mProject = project;
mLogger = project.getLogger();
LoggerUtil.sLogger = mLogger;
printAllTask();
convertTask();
}
/** * start conversion */
private void convertTask(a) {
// Just as with the introduction of the apply plugin: 'com.android.application', android{} can be configured.
// The gradle implementation parses the build.gradle file. AfterEvaluate means to execute our code after parsing
mProject.afterEvaluate(new Action<Project>() {
@Override
public void execute(@NotNull Project project) { initConfig(); }}); }/** * get the user configuration */
private void initConfig(a) {
mConfig = mProject.getExtensions().findByType(WebpConfig.class);
int quality = mConfig.quality;
ArrayList<String> whiteList = mConfig.whiteList;
boolean debugOn = mConfig.debugOn;
mLogger.log(LogLevel.ERROR, TAG + The quality of config is: + quality + "The whitelist is" + Arrays.toString(whiteList.toArray()) + "Debug mode enabled or not:" + debugOn);
// Android projects have debug and release by default,
// getApplicationVariants will be a collection of debug and release variants, and all will run through the collection
mAppExtension.getApplicationVariants().all(new Action<ApplicationVariant>() {
@Override
public void execute(ApplicationVariant applicationVariant) {
// The current user is in DEBUG mode, and the DEBUG run is not configured to perform hot fixes
if (applicationVariant.getName().contains("debug") && !debugOn) {
return;
}
// Start compressing some columnsconvert(applicationVariant); }}); }private void convert(ApplicationVariant variant) {
// Get: debug/release
String variantName = variant.getName();
// Uppercase
String capitalizeName = Utils.capitalize(variantName);
// This is the root path of the project
String rootPath = mProject.getRootDir().getPath();
// This is the address of the cwebP tool
FileUtil.TOOLS_DIRPATH = rootPath + "/mctools/";
// The first way to get resources
getRes1(capitalizeName);
// The second one gets resources
//getRes2(variant, capitalizeName);
// The third way to get resources
//getRes3(variant);
}
/** * The first way to get resources **@param capitalizeName
*/
private void getRes1(String capitalizeName) {
/ / get the android mergeDebugResources/mergeReleaseResources task
final Task mergeResTask =
mProject.getTasks().findByName("merge" + capitalizeName + "Resources");
if(mergeResTask ! =null) {
imageFileList.clear();
cacheList.clear();
mergeResTask.doFirst(new Action<Task>() {
@Override
public void execute(Task task) {
// All files output by the resource
TaskInputs outputs = mergeResTask.getInputs();
Set<File> files = outputs.getFiles().getFiles();
for (File file : files) {
// Walk through the folder to get all the images
traverseDir(file);
}
// Start compressionstartConvertAndCompress(); }}); }}/** * handle image compression task */
private void startConvertAndCompress(a) {
if (imageFileList.isEmpty()) {
return;
}
for (File file : imageFileList) {
/ / compression
CompressUtil.compressImg(file, mProject);
/ / webpConvertWebpUtil.securityFormatWebp(file, mConfig, mProject, mLogger); }}private void countNewSize(String path) {
if (new File(path).exists()) {
newSize += new File(path).length();
} else {
int indexOfDot = path.lastIndexOf(".");
String webpPath = path.substring(0, indexOfDot) + ".webp";
if (new File(webpPath).exists()) {
newSize += new File(webpPath).length();
}
}
mLogger.log(LogLevel.ERROR, TAG + "Compressed size: oldSize - newSize =" + (oldSize - newSize));
}
/** * Recursively traverse the folder to find the image to transform **@param file
*/
private void traverseDir(File file) {
if (cacheList.contains(file.getAbsolutePath())) {
return;
} else {
cacheList.add(file.getAbsolutePath());
}
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
if (listFile.isDirectory()) {
traverseDir(listFile);
} else{ filterImage(listFile); }}}else{ filterImage(file); }}/** * Filter invalid image files */
private void filterImage(File file) {
// If the image whitelist is added or the file is not an image format, filter it
if(mConfig.whiteList.contains(file.getName()) || ! 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((mConfig.isCheckSize && ImageUtil.isBigSizeImage(file, mConfig.maxSize)) && ! mConfig.bigImageWhiteList.contains(file.getName())) {// Add to the list of larger images
bigImgList.add(file.getName());
mLogger.log(LogLevel.ERROR, TAG + "Big picture address:" + imageFileList.size() + "-- -- -- -- --" + file.getAbsolutePath());
throw new GradleException("There's a picture you need to eat and fix.");
}
// Add the image to the image directory
imageFileList.add(file);
mLogger.log(LogLevel.ERROR, TAG + "Picture address:" + imageFileList.size() + "-- -- -- -- --" + file.getAbsolutePath());
}
/** * Prints all executed tasks */
private void printAllTask(a) {
mProject.getGradle().getTaskGraph().whenReady(new Action<TaskExecutionGraph>() {
@Override
public void execute(TaskExecutionGraph taskGraph) {
List<Task> allTasks = taskGraph.getAllTasks();
for (int i = 0; i < allTasks.size(); i++) {
Task task = allTasks.get(i);
// Print out the names of all tasks
mLogger.log(LogLevel.ERROR, TAG + "All tasks:" + i + "-- -- -- -- --"+ task.getName()); }}}); }/** * third way to get resources ** /
private void getRes3(ApplicationVariant variant) {
Get the mergeResources task
MergeResources mergeResources = variant.getMergeResourcesProvider().get();
mergeResources.doFirst(new Action<Task>() {
@Override
public void execute(Task task) {
TaskInputs outputs = task.getInputs();
Set<File> files = outputs.getFiles().getFiles();
// Traverses the resource file directory
for(File file : files) { traverseDir(file); } startConvertAndCompress(); }}); }/** * The second way to get resources ** /
private void getRes2(ApplicationVariant variant, String capitalizeName) {
Get the mergeResources task
MergeResources mergeResources = variant.getMergeResourcesProvider().get();
// Create your own task
Task convertTask = mProject.task("convertTask" + capitalizeName);
convertTask.doLast(new Action<Task>() {
@Override
public void execute(@NotNull Task task) {
Set<File> files = variant.getAllRawAndroidResources().getFiles();
// Traverses the resource file directory
for(File file : files) { traverseDir(file); } startConvertAndCompress(); }}); mergeResources.dependsOn(mProject.getTasks().findByName(convertTask.getName())); }}Copy the code
Copy the required tools to the McTools folder in the root directory
Design to CwebP, Guetzli and PngQuant, then we will directly traverse the resource folder, execute the corresponding command
In the end, both the main app and library were converted to WebP
The project addressgithub
reference
McImage