(2020.04.29)Android启动速度优化探讨与总结

(转载公司内部论坛本人文章2020.04.29)
导语: 大家作为普通用户,使用App时或多或少有这样的感觉,如果一款App启动时间比较慢,就会给人感觉这个App很卡,很占用资源,会影响用户再次打开App的欲望,严重的话,甚至会卸载App。所以优化App的冷启动速度是十分有必要的。

一、App冷启动过程

App的冷启动过程可以理解为就是Activity的启动过程,只不过启动时,系统检测到启动的Activity的进程不存在会先创建该进程。详细过程如下图:
在这里插入图片描述

冷启动时间定义:对于普通用户,能感受到的App启动时间应该是从点击桌面图标到首页View绘制完成,期间包括了系统创建进程时间。但这个时间主要由手机性能决定,一般比较难优化。因此我们一般说的冷启动时间优化指的是Application.onCreate到首页View绘制完成这段时间。

二、App冷启动耗时检测

1、查看Logcat

在Android Studio Logcat中输入过滤关键字“Displayed”,可以查看到App相关的启动耗时日志。
在这里插入图片描述

特点:支持查看手机任意App的启动时间。此方法常用于查看各个Activity的启动时间。

2、adb shell

使用adb shell获取应用的启动时间
adb shell am start -W [packageName]/[Activity全路径]
在这里插入图片描述

执行后会得到三个时间:ThisTime、TotalTime和WaitTime,详情如下:
ThisTime
表示一连串启动 Activity 的最后一个 Activity 的启动耗时。
TotalTime
表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用 Activity pause 的耗时
WaitTime
表示总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间
在这里插入图片描述

说明:adb shell统计的时间结束点是onWindowFocusChanged回调的时间,并不是我们一开始说的首页View绘制完成时间

3、代码计时打点

个人比较推荐的一种方式,实现简单,并且可以运用于现网环境。

Application.onCreate开始打点,到IdleHandler.queueIdle回调即是App的冷启动时间。

三、找出冷启动过程中耗时方法

要想优化冷启动时间,首先找出冷启动过程中的耗时方法。
找耗时方法的工具比较多,Hugo、TraceView、Systrace都可以。这里推荐直接使用Android Studio自带的Profiler。使用起来简单(教程),并且图形比较直观,能很清晰的看到函数调用关系和相关耗时。图中可以看到,小米note1冷启动初始化x5内核库,一段时间后会回调回主线程做了915ms的耗时工作。
在这里插入图片描述

四、冷启动耗时方法归类

通过Profiler分析,冷启动过程中耗时方法常见的有以下几类:

1、加载布局文件

相对于业务代码,加载布局文件是十分耗时。特别是在布局文件overdraw,或xml文件中直接引用大图片或帧动画的情况下。
我们项目中之前启动时初始化了4个Fragment,虽然对Fragment实现了网络层的懒加载,但是可以看到初始化4个布局文件也是相当耗时了。
在这里插入图片描述

2、初始化SDK

一些第三方库的初始化工作是否耗时。如之前提到的,某些机型初始化X5内核时是十分耗时的。

3、业务代码不合理

冷启动过程中,写了很多与启动无关的业务代码逻辑,这个要具体问题具体分析。比如我们项目中之前冷启动过程中请求了16次网络接口,每次请求都会回调回主线程做了很多耗时操作,这大大拖慢了App的冷启动速度。

五、冷启动常规优化方案

既然找出耗时方法,优化方案就变得so easy了。常规优化三板斧:懒加载,异步加载,延迟加载

1、懒加载

  • ViewStub

ViewStub与visibility的区别在于,它需要调用inflate方法时才会真正初始化。

  • Fragment完全懒加载

Fragment懒加载一般是指Fragment的网络层懒加载。ViewPager的setOffscreenPageLimit方法有个缺省值1,至少也会加载一个不可见的Fragment,而加载布局文件也是十分耗时的。所以我们可以使用ViewStub包裹该Fragment的布局文件,在isVisibleToUser为true时才去inflate,这样便能实现Fragment的完全懒加载。

  • AsyncLayoutInflater

这个方法比较投机取巧,在闪屏页播放广告时,用AsyncLayoutInflater异步加载首页布局文件,这样便能节省首页加载布局文件的事件。

  • 首页网络数据缓存到磁盘

网络请求是十分耗时的,特别是在弱网的情况下。所以我们可以将首页的网络数据缓存到磁盘,每次启动时先从磁盘里读页面数据,这样能让用户更快的看到首页数据,给用户App启动很快的感觉。缓存具体实现可参考

2、异步加载

简单说,就是将启动时一些耗时的SDK初始化,和耗时的业务逻辑放在异步线程里去做。

但是,必须强调的一点是异步不等于不耗时。Android Linux内核线程调度是分时轮询和抢占式混合的。线程过多导致CPU频繁切换,降低线程运行效率,线程优先级混乱甚至会造成主线程抢不到CPU,并且频繁的创建和销毁线程也会增加不必要的耗时。解决方案,就是选择一个合适的线程池,注意区分CPU密集型和IO密集型。(参考

3、延迟加载

也比较简单,就是将与启动无关但又不得不放在主线程里面做的工作延迟到首页启动完成后才执行。

延迟执行的方案有很多,常见的是new Handler().postDelayed()。但这样会有一个问题,延迟的时间不好估计,以至于刚好到时间点时,用户如果刚好在滑动屏幕,此时便会引起UI卡顿。

解决方案:利用IdleHandler特性,在CPU空闲时执行,对延迟任务进行分批初始化。
在这里插入图片描述
将需要延迟加载的逻辑抽象成一个个足够小的Task任务,然后在IdleHandler的queueIdle()每次循环判断CPU是否空闲,才去执行task。

六、非常规优化方法

1、类预加载优化

在Application中提前异步加载初始化耗时较长的类。

如何找到耗时较长的类?

替换系统的ClassLoader,打印类加载的时间,按需选取需要异步加载的类。

2、启动阶段抑制GC

启动时CG抑制,允许堆一直增长,直到手动或OOM停止GC抑制

实现原理

  • 1、首先,在源码级别找到抑制GC的修改方法,例如改变跳转分支。
  • 2、然后,在二进制代码里找到 A 分支条件跳转的"指令指纹",以及用于改变分支的二进制代码,假设为 override_A。
  • 3、最后,应用启动后扫描内存中的 libdvm.so,根据"指令指纹"定位到修改位置,并使用 override_A 覆盖。

3、CPU锁屏

目前手机CPU核数普遍都是4核或8核,但手机厂商为了控制耗电,一般会限制CPU的频率。所以如果我们能在启动时暴力拉伸CPU频率,以此提高CPU的利用率,那么,应用的启动速度会提升不少。
在Android系统中,CPU相关的信息存储在/sys/devices/system/cpu目录的文件中,通过对该目录下的特定文件进行写值,实现对CPU频率等状态信息的更改。

4、保活

一般App的热启动比冷启动快得多。但由于现在手机厂商的系统都做了限制App自启。所以之前流行的一些保活技术也失效了。目前效果比较好的,只剩下厂商白名单保活了。

七、优化效果

之前对我们项目按照常规方法优化了一边,效果比较明显。非常规方法在我们项目中还未使用,原因是实现成本比较大,并且之前常规优化后效果已经十分明显了。如下图,在一些性能较差的手机上,基本也能实现秒开。
在这里插入图片描述

一些想法:随着项目发展,App冷启动时间会越来越慢,原因在于每个开发人员或多或少都会往启动过程中添加自己的代码逻辑(业务需求),有些代码是完成可以放在启动完成之后做的。根本原因是没有形成一个规范。
END

参考文章:
https://www.2cto.com/kf/201802/721194.html
https://www.androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/
https://juejin.im/post/5e6f18a951882549422ef333

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值