Android 减小安装包大小(一)Lint

前言

Android Studio提供了一套静态代码分析工具。它可以检查出:xml文件中 unused resources没有使用到的资源等等。
这篇文章的主题是APK Reduce Size。
所以第一步,用Lint来 Check unused resources。
第二步,我们利用第一步的结果,java 实现 batch clean unused resource。

这里,我写了一个Demo,可以直接拿过来用
https://github.com/AlvinScrp/PerformanceOptimizationCase/tree/master/reducesizecase

Check unused resources

Android Lint运行

Android studio –>Analyze–>Inspect Code,根据指示确定就好。
这里写图片描述

Lint 结果

Inspection –> Android>Lint>Performance–> Unused resources,这里列出了Demo中的Unused Resources,可以双击跳转到指定位置删除,
这里写图片描述
也可以复制到文件,批量处理。如下图。
这里写图片描述

批量处理 unused resource

处理之前,我们有必要知道下资源文件的结构
有的资源有xml File形式drawable,layout,mipmap,menu,anim
有的资源有行key-value形式string,dimen,color
这里写图片描述
我们通过上一步得到一个文件,下面处理的
clean的java代码实现地址:
https://github.com/AlvinScrp/PerformanceOptimizationCase/tree/master/unusedResourcesCleanTool
处理流程大致就是文件操作:
1.文件先转成一个Map,
2.两层遍历res 目录和Map,根据xml File/key-value 删除resource
PS:在使用之前需要配置你的sourceProjectResDir和lintResultFilePath
你可以把代码拿过去打成jar文件

package com.example;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class UnusedResourcesClean {

    static String Type_drawable = "drawable";
    static String Type_layout = "layout";
    static String Type_string = "string";
    static String Type_color = "color";
    static String Type_dimen = "dimen";
    static String Type_menu = "menu";
    static String Type_anim = "anim";

    private static boolean cleanDrawable = true;
    private static boolean cleanLayout = true;
    private static boolean cleanAnim = true;
    private static boolean cleanMenu = true;
    private static boolean cleanColor = true;
    private static boolean cleanString = true;
    private static boolean cleanDimen = true;


    /**
     * project res dir
     */
    static String sourceProjectResDir = "/Users/alvin/Documents/workproject/PerformanceOptimizationCase/reduceSizeCase/src/main/res";

    /**
     * lint check result file path
     */
    static String lintResultFilePath = "unusedResourcesCleanTool/unusedLint";
    /**
     * key: resourceType
     * value: resource file,or resource key name
     */
    static HashMap<String, Set<String>> resMap = new HashMap<>();


    public static void main(String[] args) {

        try {
            resMap = initUnUsedMap(lintResultFilePath);
            if (resMap == null || resMap.isEmpty()) {
                return;
            }
            printUnUsedMap();
            clean(sourceProjectResDir,resMap);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * split  The resource 'R.drawable.btn_bg_top_normal' appears to be unused
     * lint result --> map
     *
     * @param resultFilePath
     */
    private static HashMap<String, Set<String>> initUnUsedMap(String resultFilePath) throws Exception {

        HashMap<String, Set<String>> resMap = new HashMap<>();
        try {
            BufferedReader bf = new BufferedReader(new FileReader(resultFilePath));
            String line = "";
            while ((line = bf.readLine()) != null) { //
                if (line.contains("appears to be unused")) {
                    String resKey = line.split("'")[1];//R.drawable.btn_bg_top_normal
                    System.out.println(resKey);
                    String resType = resKey.split("\\.")[1];
                    String resName = resKey.split("\\.")[2];

                    System.out.println(resType + " , " + resName);
                    if (resMap.get(resType) == null) {
                        resMap.put(resType, new HashSet<String>());
                    }
                    Set<String> ids = resMap.get(resType);
                    ids.add(resName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return resMap;


    }

    private static void printUnUsedMap() {

        for (Map.Entry<String, Set<String>> entry : resMap.entrySet()) {
            System.out.println("------------------" + entry.getKey() + "------------------");
            for (String s : entry.getValue()) {
                System.out.println(s);
            }
            System.out.println(" ");
        }

    }

    /**
     *
     * @param sourceProjectResDir  项目资源目录
     * @param resMap 根据Type分类的unusedResource
     *               例如 key: string     value: {rateuslib_cancel,unnecessary_cancel}
     */
    private static void clean(String sourceProjectResDir,HashMap<String, Set<String>> resMap) {

        Map<String, Boolean> deletionFileMap = new HashMap<>();
        deletionFileMap.put(Type_drawable, cleanDrawable);
        deletionFileMap.put(Type_layout, cleanLayout);
        deletionFileMap.put(Type_anim, cleanAnim);
        deletionFileMap.put(Type_menu, cleanMenu);
        deletionFileMap.put(Type_color, cleanColor);

        Map<String, Boolean> deletionLineMap = new HashMap<>();
        deletionLineMap.put(Type_string, cleanString);
        deletionLineMap.put(Type_dimen, cleanDimen);
        deletionLineMap.put(Type_color, cleanColor);

        Map<String, String> lineKeyMap = new HashMap<>();
        lineKeyMap.put(Type_string, "<string name");
        lineKeyMap.put(Type_dimen, "<dimen name=");
        lineKeyMap.put(Type_color, "<color name=");

        File dir = new File(sourceProjectResDir);
        for (File file : dir.listFiles()) {
            if (!file.isDirectory()) {
                continue;
            }
            if (file.getName().contains("value")) {
                for (File file1 : file.listFiles()) {
                    for (Map.Entry<String, Boolean> entry : deletionLineMap.entrySet()) {
                        String type = entry.getKey();
                        boolean clean = entry.getValue();
                        if (file1.getName().contains(type) && clean) {
                            resetXml(file1, resMap.get(type), lineKeyMap.get(type));
                        }
                    }
                }
            } else {
                for (Map.Entry<String, Boolean> entry : deletionFileMap.entrySet()) {

                    if (file.getName().contains(entry.getKey()) && entry.getValue()) {
                        Set<String> resSet = resMap.get(entry.getKey());
                        deleteResFile(resSet, file);
                    }
                }
            }
        }
    }

    /**
     * 从指定资源目录中,删除指定类型的文件
     *
     * @param resourceFiles
     * @param resDir       drawable/ or layout/ ...
     */
    private static void deleteResFile(Set<String> resourceFiles, File resDir) {

        try {
            for (File file1 : resDir.listFiles()) {
                if (resourceFiles.contains(file1.getName().split("\\.")[0])) {
                    file1.delete();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param sourceFile  需要删除行的文件
     * @param unusedSourceKeys 需要删除的行的关键词集合
     * @param xmlTypeKey  "<string name" or "<dimen name=" or "<color name="
     */
    public static void resetXml(File sourceFile, Set<String> unusedSourceKeys, String xmlTypeKey) {
        BufferedWriter bw = null;
        try {
            if (unusedSourceKeys==null&&unusedSourceKeys.isEmpty()){
                return;
            }
            String outFilename = sourceFile.getAbsolutePath() + "_temp";
            File outFile = new File(outFilename);
            BufferedReader bf = new BufferedReader(new FileReader(sourceFile));
            bw = new BufferedWriter(new FileWriter(outFilename));

            String line = "";// <string name="app_name">App Backup &amp; Restore</string>

            while ((line = bf.readLine()) != null) {
                if (line.contains(xmlTypeKey) && unusedSourceKeys.contains(line.split("\"")[1])) {
                    continue;
                }
                bw.write(line);
                bw.newLine();
            }
            sourceFile.delete();
            outFile.renameTo(new File(sourceFile.getAbsolutePath()));

        } catch (Exception e) {

        } finally {
            if (bw != null) {
                try {
                    bw.flush();
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值