【摘要】在一次偶然的体验中,我发现,我们公司的Android app在启动的时候总是先白屏,然后再显示启动页,然后隔了好长一段时间才进入主页,然后加载网页。最近正好需要优化app的速度,对此进行了一次比较完整性的优化,优化下来,启动速度从2秒8,提升至一秒2左右的样子。此文章,尽量避免写看不懂的“长篇大论”,到后面写的和论文一样,就没意思了。
作者:x-teamer团队清泓
第一段:app的启动提速
我们怎么去看到app启动时间呢?
ViewRootImpl
ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁。每一个ViewRootImpl关联一个Window,
ViewRootImpl最终会通过它的setView方法绑定Window所对应的View,并通过其performTraversals方法对View进行布局、测量和绘制。
启动耗时检测
1、第一,我们的android-stuio提供打印logcat。
在Android Studio Logcat中过滤关键字“Displayed”,可以看到对应的冷启动耗时日志。
2、adb shell
使用adb shell获取应用的启动时间
// 其中的AppstartActivity全路径可以省略前面的packageName
执行后会得到三个时间:ThisTime、TotalTime和WaitTime,详情如下:
ThisTime
最后一个Activity启动耗时。
TotalTime
所有Activity启动耗时。
WaitTime
AMS启动Activity的总耗时。
一般查看得到的TotalTime,即应用的启动时间,包括创建进程 + Application初始化 + Activity初始化到界面显示的过程。
线上的app启动时间监测,大家可以看以下网址,我个人觉得,没必要,如果你有兴趣,可以详细了解的。链接是:
https://jsonchao.github.io/jsonchao.github.io
Deep into Android
Deep into Androidjsonchao.github.io
app的启动类型分为冷启动和热启动,咱们不说得那么玄乎,简单理解下来,冷启动就是在app完全关闭的时候,从触发app打开的那一刻,重新启动。而热启动的话,那就是在他快要“谢幕”的时候,把他拉回来,而这个时候,我们并不需要从application开始初始化,而可以直接跳过这一步,直接初始化内存栈的进程。
而这次,主要优化的,是冷启动,因为平时总是看到他黑屏之后再进入启动页,这样下去,看起来都不爽。
黑屏的那一刻,其实是application启动的过程,大家看看下面的图片,一个application,里面这么多初始化的东西,细数下来,
冷启动
Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl
热启动
LifeCycle -> ViewRootImpl
接下来
1.初始化webview;(这个第二段会细讲)
2.注册微信相关服务;
3.初始化友盟服务,包括分享,推送等。
4.适配各渠道的推送服务。
5.更新版本相关服务。
6.其他未列出若干初始化。
这么搞下来,当然会慢了一拍,尽管现在的android机基本都是性能怪兽,但不可避免,肯定会闪黑屏。曾经想过把这些业务放主页,到页面可见之后,再初始化,这个还是比较麻烦,且个人觉得,还是放application更稳定,且某些业务,移动起来麻烦至极,且不知道稳定性如何,需要耗费大量的时间去测试验证。
其实很简单,没必要小题大作。既然需要时间来初始化,那正好,我启动页启动快了,还没办法“打广告”。那最好的解决办法,无非是换个角度思考,直接把启动页,充当背景,套在application的视图层即可。
依我之前的做法,是需要把启动页的视图初始化的,多了一个activity的视图。这样,大大的降低启动速度,更加的显得不和谐。
具体如何做。
有的人,把Preview Window给禁掉了。这个我觉得方法不咋的,android的后生代系统才推出的优化体验的东西,直接给禁止了,没意思。当然,效果确实达到了,如果想用,也是可以用的。不建议。
<style name="APPTheme" parent="@android:style/Theme.Holo.NoActionBar"> <item name="android:windowDisablePreview">true</item> </style>
由于我的app之前已经做到以下几点,有以下我列出的几点的,应尽量避免。
1.启动期间剔除繁琐的网络数据请求阻塞了前台的UI绘制视图展示。
2.所有需要在主线程进行非常耗时操作的进程。
3.大型图片初始化,和耗时资源的加载。
4.所有异步进程。
第一步,高耗时操作线程更新的进程剔除,在我的项目里,我举个例子给大家看一下,比如这个:
其余不做赘述。
第二步把之前觉得不好的给注释掉吧,我之前的SplashActivity视图层,不要他了。注释即可。
之前有提到黑屏问题,我们需要覆盖一个和谐的“场景”,我们自制一个“主题”,然后让我们在androidManifest文件中,找到刚刚注释掉初始化视图层的activity的声明段落,把这个activity的主题替换成以下我们重新制作的"主题",这个主题里的bg_splash图片是我们的在启动页启动视图的时候应该呈现的图片。
<style name="FullscreenTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/bg_splash</item>//这个是我们提前在资源文件夹中放的图片
<item name="android:windowFullscreen">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
在这几步简单操作之后。启动速度提高了一倍。
这么,我还是感到还没达到最佳效果。我们是一个混合开发Hybridweb应用的app。
本地Webview初始化都要不少时间, 首次初始化webview与第二次初始化不同,首次会比第二次慢很多。原因预计是webview首次初始化后,即使 webview 已经释放,但一些webview 共用的全局服务或资源对象仍没有释放,第二次初始化时不需要再生成这些对象从而变快。我们可以在Application预先初始化好WebView, 当第二次初始化WebView的时候速度就快多了, 或者直接将其拿来使用。
类似于以上的初始化代码,这个根据业务需求,进行相关优化的,每个app和公司软件都有不同的需求。
另外是加载子URL的优化:因为我们需要不停的变换URL,且第一次和第二次又根据业务的需求各不一样,比如说第一次加载是请求接口,得到主URL,之后的加载才是内部网页资源,比如说.....且后来想,如果每次都加载URL,需要重新初始化webview容器配置,那将是多么恐怖的一件事情。
唯有把第一次初始化的页面给区别出来,只有第一次加载的时候才需要初始化webview,相关配置,其余的都不需要了,我需要区分出加载的主URL各页面出来,剔除掉这些主URL页面之后,其余的都是直接套用我们之前的webview配置的页面。
首先,我们定义了一个我们应用相关主tab页里面包含的结尾特有字符串。
然后,定义一个addWeb的方法,无需再次更新webview配置。
因为webview打开的网页,里面需要加载非常多的资源,如果不加入缓存策略,每次加载更多的资源和图片,都需要从网络上请求获取,每次初始化,次次如此,这个就是一个优化的瓶颈,对于webview的优化,还有一部分没写,再写偏题了,我的下一篇文章是关于webview缓存的,主要是利用webview缓存来优化用户体验。