@levinzhang
2017-11-12T23:04:41.000000Z
字数 2503
阅读 617
随着特性的不断增加,Instagram的App面临了许多性能方面的难题和开发效率的损耗,Instagram采用了模块化和懒加载的方式来解决这些问题,本文介绍了模块化和懒加载的适用场景和技术原理。
本文最初发布于Instagram博客,经原作者授权由InfoQ中文站翻译并分享。
Instagram提供了一款精益的App并以此为傲。但是随着工程师和特性数量的增长,所面临的挑战也在不断增加。我们开始面临的问题包括:
为了解决这些问题,我们最近在Instagram范围内制定了严格的特性隔离规范。规范同时面向Android和iOS,但是本文主要关注Android。特性在模块化之后,它就可以从主执行文件中移除出去,并编译到单独文件中,在App运行时,可以根据需要进行懒加载。懒加载会带来一系列的好处,比如改善冷启动时间(尤其是在Dalvik上)和整体运行时环境、减少磁盘占用,如果实现模块热替换的话,还能实现开发人员更好的敏捷性。本文将会详细介绍Instagram的模块化和懒加载方式,还会介绍如何借助我们新的开源框架,在你自己的App内实现这一点。
模块化就是拆分代码的逻辑组件并创建它们之间清晰边界的过程。对Instagram来说,我们一直关注模块化特性代码,因为它代表了很大一部分代码库并且每个特性有着清晰的边界。通过模块化减少跨特性的依赖有助于以多种不同的方式解决App增长所带来的挑战。这种方式的一个好处就是它能保证App的启动时间不会随着App代码的增长而增加。在采取模块化之前,App启动时可能就会将某个特性引用的其他特性的代码全部加载进来,这是有风险的一种做法。通过使用模块化拆分这些引用之后,我们就可以采用类似懒加载(Lazy Loading)的方式当代码真正需要的时候再去加载。
另外,模块化还有其他的好处,包括整个代码库的清晰度、构建时间、开发人员的敏捷性以及即时应用(Instant App)等。
模块化的目标是定义一个清晰的接口,这样的话就能将特性的实现代码与外界代码隔离开来。首先,我们要审查特性所有的传入依赖。针对每项依赖,确定它应该移除还是应该保存。因为每个场景都是不同的,所以并没有什么简单的方式来做出这些评估决策,但是这里有一些考量因素:
如果考虑完这些因素后,依然发现依赖是必要的,那么这部分依赖就应该作为特性的接口。如果我们很好地进行了模块化,特性的接口应该只包含几个重要的方法,比如:
尽管模块化能够将特性与外部世界解耦并为特性创建一个干净的接口,但是懒加载能够更进一步,将特性编译到主运行(dex)文件外边,放到一个单独的dex文件之中。每个懒加载的特性会位于单独的文件中,这样会带来一些好处,比如:
在开发人员的敏捷性方面,我们对懒加载模块提供了热替换功能,这意味着开发人员在编码时就能看到变化,而不需要重新启动App。
一般来讲,当我们预期某项特性将要用到的时候,就会加载该模块。一旦模块加载之后,它就会一直存在于内存之中,直到下次冷启动。模块加载可能会造成一些小的延迟(这取决于模块的大小),所以在这里可以使用不同的技巧:
当用户距离某个模块只有一步之遥的时候,就在后台加载该模块。当然,如果用户此时不点击该特性或者进行了回退操作,那么这个模块就不会用到。如果用户点击该模块的可能性非常高的话,那么这是一个很好的解决方案。
用户导航至该模块的时候再去加载模块。如果大多数场景下(比如99%)加载延迟都很小的话(低于50毫秒),那么我们可以阻塞加载,等到加载完成再导航至该特性。否则的话,可以显示一个简单的旋转加载框或进度条,这样的话,App不会看上去像卡住一样。
有些模块本身就是异步的,这样会让懒加载变得更加容易,因为它可以作为异步加载的一部分。举例来说,视频播放器会在一个二级进程中运行。Instagram首先会展现一个视频的截图,后台则会进行加载(通常会在网络上获取)。懒加载会在二级进程中运行,对用户是完全透明的。
我们在ig-lazy-module-loader上开源了用于懒加载的框架,所以其他的开发人员可以在自己的App上使用该功能或者学习我们的实现方式。如果你正面临Dalvik上多dex文件所带来的性能问题或者有很多特性都是用户用不到的,所以希望节省磁盘空间的话,那这种方式是很有帮助的。