Android apk瘦身实践

写在前面

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

APK Analyzer的简单使用

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

Android apk瘦身实践_第1张图片
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:

Android apk瘦身实践_第2张图片
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就能帮我们分析出来。

Android apk瘦身实践_第3张图片
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瘦身实践)