移动开发DevSupportandroid收集

Android性能优化(一)之启动加速35%

2017-01-10  本文已影响17268人  未来的理想

一、前言

随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优化实践到项目中,欢迎持续关注!

那么第一篇文章我就从应用的启动优化开始,根据实际案例,打造闪电般的App启动速度。

二、初识启动加速

来看一下Google官方文档《Launch-Time Performance》对应用启动优化的概述;

应用的启动分为冷启动、热启动、温启动,而启动最慢、挑战最大的就是冷启动:系统和App本身都有更多的工作要从头开始!
应用在冷启动之前,要执行三个任务:

  1. 加载启动App;
  2. App启动之后立即展示出一个空白的Window;
  3. 创建App的进程;

而这三个任务执行完毕之后会马上执行以下任务:

  1. 创建App对象;
  2. 启动Main Thread;
  3. 创建启动的Activity对象;
  4. 加载View;
  5. 布置屏幕;
  6. 进行第一次绘制;

而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。

应用冷启动流程图
作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程

同样,Google也给出了启动加速的方向

  1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
  2. 避免在启动时做密集沉重的初始化(Heavy app initialization);
  3. 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

备注:方向1属于治标不治本,只是表面上快;方向2、3可以真实的加快启动速度。
接下来我们就在项目中实际应用。

三、启动加速之主题切换

按照官方文档的说明:使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。
Layout XML file:

资源文件配置 Manifest file: Manifest文件中 Activity中

这样在启动的时候,会先展示一个界面,这个界面就是Manifest中设置的Style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉。不过如上文总结这种方式其实并没有真正的加速启动过程,而是通过交互体验来优化了展示的效果。
备注:截图同样来自官方文档《Launch-Time Performance》

四、启动加速之Avoid Heavy App Initialization

通过代码分析我们可以得到App启动的业务工作流程图:

App冷启动业务工作流程图

这一章节我们重点关注初始化的部分:在Application以及首屏Activity中我们主要做了:

项目中除听云之外其余所有三方组件都抢占先机,在Application主线程初始化。这样的初始化方式肯定是过重的:

项目修改:

  1. 将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在WorkThread中初始化;
  2. 延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader
    因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化。
三方组件调用优化示例代码

注意:闪屏页的2秒停留可以利用,把耗时操作延迟到这个时间间隔里。

五、启动加速之Diagnosing The Problem

本节我们实际定位耗时的操作,在开发阶段我们一般使用BlockCanary或者ANRWatchDog找耗时操作,简单明了,但是无法得到每一个方法的执行时间以及更详细的对比信息。我们可以通过Method Tracing或者DDMS来获得更全面详细的信息。
启动应用,点击 Start Method Tracing,应用启动后再次点击,会自动打开刚才操作所记录下的.trace文件,建议使用DDMS来查看,功能更加方便全面。

优化之前应用启动trace文件分析图

左侧为发生的具体线程,右侧为发生的时间轴,下面是发生的具体方法信息。注意两列:Real Time/Call(实际发生时间),Calls+RecurCalls/Total(发生次数);
上图我们可以得到以下信息:

即便是耗时操作,但是只要正确发生在WorkThread就没问题。因此我们需要确认这些方法执行的线程以及发生的时机。这些操作如果发生在主线程,可能不构成ANR的发生条件,但是卡顿是再算难免的!结合上章节图App冷启动业务工作流程图中业务操作以及分析图,再次查看代码我们可以看到:部分耗时操作例如IO读取等确实发生在主线程。事实上在traceview里点击执行函数的名称不仅可以跟踪到父类及子类的方法耗时,也可以在方法执行时间轴中看到具体在哪个线程以及耗时的界面闪动。

分析到部分耗时操作发生在主线程,那我们把耗时操作都改到子线程是不是就万事大吉了?非也!!

通过对traceview的详细跟踪以及代码的详细比对,我发现卡顿发生在

以及其它细节问题:

项目修改:
1. 数据库及IO操作都移到工作线程,并且设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样工作线程最多能获取到10%的时间片,优先保证主线程执行。

2. 流程梳理,延后执行;
实际上,这一步对项目启动加速最有效果。通过流程梳理发现部分流程调用时机偏早、失误等,例如:

3.其它优化;

业务代码优化示例

通过以上三步及三方组件的优化:Application以及首屏Activity回调期间主线程就没有耗时、争抢资源等情况了。此外还涉及布局优化、内存优化等部分技术,因对于应用冷启动一般不是瓶颈点,这里不展开详谈,可根据实际项目实际处理。

六、对比效果:

通过ADB命令统计应用的启动时间:adb shell am start -W 首屏Activity。
同等条件下使用MX3及Nexus6P,启动5次,比较优化前与优化后的启动时间;

优化前:
MX3

ThisTime TotalTime WaitTime
1237 2205 2214
1280 2181 2189
1622 2508 2513
1485 2434 2443
1442 2418 2429

Nexus6P

ThisTime TotalTime WaitTime
1229 1832 1868
1268 1849 1880
1184 1780 1812
1262 1845 1876
1164 1766 1807

优化后:
MX3

ThisTime TotalTime WaitTime
865 1516 1523
911 1565 1573
812 1406 1418
962 1564 1574
925 1566 1577

Nexus6P

ThisTime TotalTime WaitTime
603 1192 1243
614 1076 1115
650 1120 1163
642 1107 1139
624 1084 1124

对比:
MX3提升35%

对比 ThisTime平均数 TotalTime平均数 WaitTime平均数
优化前 1413 2349 2357
优化后 895 1523 1533

Nexus6P提升39%

对比 ThisTime平均数 TotalTime平均数 WaitTime平均数
优化前 1221 1814 1848
优化后 626 1115 1156

七、问题:

1、还可以继续优化的方向?

2、异步、延迟初始化及操作的依据?
注意一点:并不是每一个组件的初始化以及操作都可以异步或延迟;是否可以取决组件的调用关系以及自己项目具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。

3、通用应用启动加速套路?

4、其它

参考文章:《官方文档——Launch-Time Performance》

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都需要,业务增长快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、非频繁跳槽(如两年两跳),欢迎加我的微信详聊:KOBE8242011

欢迎关注

欢迎关注微信公众号:定期分享Java、Android干货!

欢迎关注
上一篇下一篇

猜你喜欢

热点阅读