Android apk瘦身实践

作者: 事多店 | 来源:发表于2019-01-17 23:41 被阅读7次

    写在前面

    最近刚做了一波apk瘦身优化,瘦身后apk大小降低了19%左右。打铁要趁热,赶紧记录一下先。

    APK Analyzer的简单使用

    工欲善其事,必先利其器。就像分析卡顿问题要善用Systrace和TraceView,分析内存问题要善用Profiler和MAT一样。Apk瘦身优化,有工具来帮我们也会省心很多,而这个工具就是APK Analyzer了。
    APK Analyzer是集成在Android Studio中的工具,直接将apk拖到Studio或者选择Build -> Analyze APK...都能够打开。

    apk_analyzer.png

    APK Size代表原始的安装包大小,Download Size代表经过Google Play压缩后。我们的apk不通过Google Play渠道的,所以关注的是APK Size
    图中的下半部分是Apk的结构,从上到下按大小排序,所以哪些地方最有优化空间也是一目了然。

    • res:存放资源的文件;
    • assets:包含应用程序的原生资源文件;
    • lib:主要存放so文件;
    • classes.dex:dex文件,所有的Java代码最终都是转化成dex文件。
    • resources.arsc:经过编译的资源,将res下的资源映射成ID。res/values/目录下的资源也会被编译进来。
    • AndroidManifest.xml:全局配置文件
    • META-INF:存放签名校验的相关文件,用于保证APK的完整性和安全性;

    点击各个类别,下方还会有一个窗口。比如说点击classes.dex:

    apk_analyzer_1.png

    可以清楚的看到包下的类,定义的方法和大小等。另外,如果发现被混淆了,还可以点击Load Proguard mappings加载mapping文件。

    删除无用的代码和资源

    优化的第一步,我没有急着去看各种apk目录下各种资源占用的大小。而是删选择掉没有用的代码,我们一般都会在gradle文件中这样配置:

    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
        }
    }
    

    这样在打包release包的时候,会帮我们把没有用的代码给删掉。但我们还是得手动删一下,这样做的原因是,这些代码虽然没有用到了,但是它们可能会引用到一些图片资源,这些资源在打包apk过程中是不会帮我们删掉的。具体做法是:右键->Analyze->Run Inspection by Name->输入Unused declaration,这时候会弹出一个框,根据自己的需求选择即可。它最厉害的地方就是能通过entry points的调用链来判断一个类的可达性。举个例子,假设有两个类:

    public class EmptyClassA {
        public EmptyClassA() {
            EmptyClassB b = new EmptyClassB(this);
        }
    }
    
    public class EmptyClassB {
        private EmptyClassA mA;
        
        public EmptyClassB(EmptyClassA a) {
            mA = a;
        }
    }
    

    EmptyClassA在初始化时创建了EmptyClassB,如果我们单纯看EmptyClassB,就会发现这是有人使用的类,不能删掉。但其实EmptyClassA没有人会用到,也就不会用到EmptyClassB,所以EmptyClassB是可以删的。当代码越来越多的时候,这种调用关系可能会很复杂,好在Android Studio就能帮我们分析出来。

    unused_code.png

    解决方法是根据提示,解除掉所有的依赖就能将类给删掉了。当然,如果某些类的依赖关系太过于复杂的话还是放弃吧,毕竟成本太高,成效太低。瘦身的过程中还要考虑一个性价比的问题。

    不过我发现这个工具有个缺点就是分析不了Kotlin文件,希望以后Google能够加上吧。

    优化的第二步是删掉无用的资源文件。同样也是利用Android Studio,不过这一步比较简单。具体方法是,在代码里右键->Refactor->Remove Unused Resources...。如果你的项目已经经过了很多个迭代了,那这一步应该可以去掉很多没有用到的资源了。

    压缩图片资源

    当经过了前两步之后,再编一个apk。嗯,你就会发现,其实减的并不多。。这时候再看apk下的资源占比,最大头的一般还是res/下的图片,但是这些图片都是项目中使用到的,不能删,所以只能选择压缩了。我用的是tinypng,这一步的效果是非常明显的,有一些几百K的图片直接被压缩成了几十K。项目里的大图片越多,效果就越明显。压缩后如果对一些大图片的大小还不是很满意,可以考虑一下WebP格式的图片,具体方法是选中图片->右键->Convert to WebP。需要自己确认下转换后图片变小了没有,实际上有些图片的大小反而是会变大的。

    这几步下来,尤其是删代码的时候耗费了很多时间。回头一看,发现才减了2.8M,10%还不到,这交不了差啊。。

    动态加载so

    回头再看看apk的资源占比,res/assets/目录下能压缩的都压缩了。代码该删的也都删了。剩下的一座大山就是lib/下的so库了。最后权衡后决定,将几个后台任务的so放到服务器上,然后下载后进行动态加载。因为这几个so本身就是执行后台任务时才需要用到的,所以对用户来说是感知不到的。

    网上的很多文章都说先将so下载到sd卡,然后在复制到App的app_lib/目录下面,然后再动态加载。最开始我也跟着这样做了,后来试了直接将so下载到app_lib/xxx目录,然后动态加载也是可行的。这样就省去了一步复制操作。

    public boolean loadSo() {
        if (hasSoLoaded()) {
            return true;
        }
        
        if (isSoExisted()) {
            return loadLibrary();
        } else if (downloadSo()) {
            return loadLibrary();
        }
        return false;
    }
    

    大概流程就是这样的:先检查so有没有加载过,没有的话检查so是否已经存在了,是的话直接加载,没有的话下载后进行加载。

    不得不说,最后这一步的效果是最明显的,大概减掉了4M。可以说是很满意了。

    相关文章

      网友评论

        本文标题:Android apk瘦身实践

        本文链接:https://www.haomeiwen.com/subject/yuxgdqtx.html