apk瘦身计划---dex拆包定制,精准测量分模块占比

1. 需求驱动 ---精准监控统计apk包体积

 

 目前各应用APP 包体积,日渐增大,各个公司都在提出精简包体积的要求。这个过程中,除了图片,so库,第三方library,混淆效率这些维度进行分析,统计,代码层面,统计的力度比较低,本博文重点从代码文件角度,分析统计。

 

2. 背景:大部分公司都是各部门之间分工合作完成整个apk的开发。如果部门老大希望精确知道每个部门或模块,到底为最终版apk贡献了多少分量。你该怎么做呢?

首先想到的是解压最终apk包,看看到底各部分之间占了多少。资源跟so库,基本上可以根据对应索引,写脚本精确找到各个图片,.xml这些到底属于哪个模块,甚至哪位程序员所贡献。

代码如何统计呢,有人会想到反编译,或者直接dx工具编译class文件。但问题是dex文件最终会经过系统的多种处理,混淆,对齐,最关键是压缩(比如我们的apk看压缩比1:3)。这方法粒度就太粗了。

 

Android系统提供的multidex工具,是自动分配dex文件。

如果能够手动指定每个class类最终进到某个dex中,这个就能够精确的知道每个模块的代码在apk中,究竟占了多了百分比。以便建立长期机制,定期去监控各模块代码增长情况了。如何做呢?

 

3.先来看下,android官方给出的编译流程图


 

基本概念:dex分包前提,即内置的java编译器已经将java文件编译成为.class文件(当然还有第三方库及.class文件)。再通过dx工具或gradle task编译生成dex文件。

 

3. multidex 分包源码研究

 

源码下载地址:https://android.googlesource.com/platform/tools/base/+/gradle_2.2.2/build-system/

 

3.1.生成maindexlist核心源码

 MainDexListBuilder.java

 

This is a command line tool used by mainDexClasses script to build a main dex classes list.

这个类主要用来生成MaindexList

 

 public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)

            throws IOException {

        ZipFile jarOfRoots = null;

        Path path = null;

        try {

            try {

                jarOfRoots = new ZipFile(rootJar);

            } catch (IOException e) {

                throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("

                        + e.getMessage() + ")", e);

            }

            path = new Path(pathString);

 

            ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);

            mainListBuilder.addRoots(jarOfRoots);

            for (String className : mainListBuilder.getClassNames()) {

                filesToKeep.add(className + CLASS_EXTENSION);

            }

            if (keepAnnotated) {

                keepAnnotated(path);

            }

        } finally {

            try {

                jarOfRoots.close();

            } catch (IOException e) {

                // ignore

            }

            if (path != null) {

                for (ClassPathElement element : path.elements) {

                    try {

                        element.close();

 

 

 

                    } catch (IOException e) {

                        // keep going, lets do our best.

                    }

                }

            }

        }

}

 

 

使用到的action如下:

MultiDexTransform

 

 // Inputs

    @NonNull

    private final File manifestKeepListProguardFile;

    @Nullable

    private final File userMainDexKeepProguard;

    @Nullable

    private final File userMainDexKeepFile;

    @NonNull

    private final VariantScope variantScope;

    @Nullable

    private final File includeInMainDexJarFile;

 

    private final boolean keepRuntimeAnnotatedClasses;

 

    // Outputs

    @NonNull

    private final File configFileOut;

    @NonNull

    private final File mainDexListFile;

 

 

3.2dex生成的核心代码分析

 

 

主要属性:

 

 

 

 

生成规律的classes2.dexclasses3.dex......classesN.dex 的代码

 

 


4 hack的主要思路


      很多人优先想到的是hook进某task但前提是Option属性要支持,注意,build.gradle 1.X 版本与2.x版本差距比较大,网上流传的很多配置都过时了。

 

DxOptions{

dexInProcess = false

additionalParameters + = “--main-dex-list=$projectDir/maindexlist.txt”.toString()

additionalParameters + = “--secondary-dexes-list=$projectDir/secondarydexes.txt”.toString()

additionalParameters + = “--minimal-main-dex”


}


 

1)其中maindexlist.txtsecondarydxeslist.txt在住工程文件root目录下。

 

2)dexInProcess 设置为false 才能够强制将dx生成过程,指向本地Build.tools中的dx.jar

3)dx.jar的生成过程,是整个hack过程的关键。

 

3.hack核心源码

 

 

这里与上面dxOption属性对应

 


这里就是生成的hackdx.jar中生成secondclasses.dex的核心代码,理论上是可以直接指定每一个dex的名称的。但系统重新加载时,直接忽略了dex名称,而仅仅遍历dex文件目录,所以即使修改了dex文件名,也不会最终生效。

 

 

生成dex文件后,其余混淆,对齐,签名,这些与原有逻辑一致,至此dex分拆工作就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值