大厂Android启动优化- 4 出其不意的优化手段

7,533 阅读5分钟

常规的手段优化后,我们能解决基本的问题,但是我们得继续追求极致,本章将分享一些意想不到的手段。

1 首页合并

通常我们启动的时候分为闪屏和首页两个页面,我们将闪屏和首页合并成一个,通过fragment来操作真实页面,广告设计成一个dialog fragment浮在首页就行。基本的收益在100ms左右。 首页合并后发现几个问题:首页管理、唤端启动问题。

  • 首页无法使用singletask,singletask的问题我们通过手动维护一个activity栈去管理,保证一个首页。
  • 唤端启动,第三方app唤端没有加newtask标签,导致页面启动在第三方app内,这个暂时没有遇到很好的解决方式,还是在推动比较大的第三方平台去解决。 image.png

2 渲染消息优先级队列

我们数据请求后,通过handler将消息发送到主线程,handler消息队列内可能比较繁忙,我们思考的是将数据回调和图片回调的消息发送到消息队列前面。
MessageQueue其实提供了一个方法handler.sendMessageAtFrontOfQueue的方式。

3 dex2oat

Android ART虚拟机后,可以将dex文件预先的翻译处理成机器码直接运行,在Android 5 - 7版本,dex处理是在app安装的时候处理的,但是由于dex2oat性能影响较大,安装的时候将耗时过长,Android 7后改为安装的时候不做翻译,运行时还是解释执行,运行时的时候记录运行的函数等信息,在手机闲置的情况下去把这些热方法做dex2oat,下次运行直接运行机器码。
但是系统判断闲置的条件比较苛刻,导致大部分情况下app没有被dex2oat,另外互联网app快速发展,发版速度较快,所以dex2oat的利用率低,通过app自己手动调用系统dex2oat达到快速将app的dex转化,提高代码执行效率,该方案的收益大概在10%。相关的详细知识可以网上搜索一下。

系统记录热方法的数据文件存在 /data/misc/profiles/cur/0/packageName/primary.prof下,我们可以通过FileObserver监听这个这个文件修改判断是否需要dex2oat。
dex2oat可以通过shell命令执行cmd package compile -m speed-profile -f packageName

4 Redex

Linux 文件系统从磁盘读文件的时候,会以 block 为单位去磁盘读取,一般 block 大小是 4KB。也就是说一次磁盘读写大小至少是 4KB,然后会把 4KB 数据放到页缓存 Page Cache 中。如果下次读取文件数据已经在页缓存中,那就不会发生真实的磁盘 I/O,而是直接从页缓存中读取,大大提升了读的速度。所以上面的例子,我们虽然读了 1000 次,但事实上只会发生一次磁盘 I/O,其他的数据都会在页缓存中得到。

Dex 文件用的到的类和安装包 APK 里面各种资源文件一般都比较小,但是读取非常频繁。我们可以利用系统这个机制将它们按照读取顺序重新排列,减少真实的磁盘 I/O 次数。

image.png

类重排

启动过程类加载顺序可以通过插装静态代码块获取。

class A {
    static {
        //记录
    }
}

然后通过 ReDex 的Interdex调整类在 Dex 中的排列顺序,最后可以利用 010 Editor 查看修改后的效果。

从多方拿到的数据来看,收益在0-6%,整体不是很明显,而且需要把redex工程化、考虑和proguard的兼容等问题。

5 黑科技

微信Hardcoder

构建了App与系统(ROM)之间可靠的通信框架,让系统知道App的需求,可以让app获取更多的系统资源。

原理

  • 1、其实质是让App跨过Framework直接跟厂商ROM通信
  • 2、分为Client端和Server端,Server端由厂商系统侧自行实现
  • 3、它们直接采用 LocalSocket 方式,Hardcoder是 Native 实现的,使用了Linux的Socket接口实现了一套自己的LocalSocket。

整体收益不是特别明显。

GC抑制

网上讲的很多文章都是4.4版本的GC抑制,因为在ART虚拟机之前,Android的GC回收算法是停止一切,所有抑制GC收益很大。

ART虚拟机后,采用并行回收的算法,GC回收对性能的影响大大降低。但是通过systrace分析,在启动过程中,GC线程也存在抢占系统资源的情况。

Google 也注意到了后台 GC 对于应用启动速度的影响,并尝试了在 Android 中对这一场景进行优化。在 Android 10 的代码中。有如下提交:cs.android.com/android/_/a…

这个改动的逻辑是:应用启动时 Zygote Fork 出新的进程之后,在2秒内暂时提高 Background GC 任务触发的阈值。这样 Background GC 将会更难被触发。

更多关于ART GC算法的内容可以参考ART GC流程

那么 Google 这个提交的优化效果如何?Comments 中包含了测试效果,可以看到各个应用的启动速度都有提升。

可以看出抑制 GC 有较明显的效果,但是 Google 这个改动只存在于 Android 10 及以上的机型。那么可以找找方法在 Android 10 之前的机器也享受到这个优化的效果。

6 Hook框架

开源框架 epic
利用hook框架发现问题,比如监控大io读取,获取系统服务等。

7 主线程优先级问题

Android的离奇陷阱 — 设置线程优先级导致的微信卡顿惨案
在Anroid11以下,在线程没有start完成设置线程优先级可能导致修改的是主线程优先级,导致主线程优先级降低,影响运行效率。

8 后继

至此,相关的优化内容已经完成,接下来分享怎么保持我们的优化成果和快速发现问题。