从用户点击桌面图标到首页完全加载,需要经过三个页面,第三步中,如果用户是安装或升级后第一次打开应用,则显示引导页,否则显示广告页。
点击应用图标后系统会有3秒的响应时间,这段时间系统好像无响应,实则在为应用准备运行环境,打开一个无边框的窗口,这个窗口默认为白色,现在App调整成了透明,然后会初始化各个三方库,向网络请求应用图标序号。
闪屏页是启动过程中最为复杂的一步,它包含:检查权限并弹窗提醒,提前请求首页数据,启动引导页或广告页
检查权限
进入闪屏页,首先会按顺序检查四个权限,分别是读写手机文件的权限、获取本机号码本机唯一标识码的权限、手机录音的权限、获取手机定位的权限。如果其中一个对话框拒绝授权,会再弹出一个对话框劝用户授权,如果还不授权则退出APP。详细逻辑如图。
请求首页数据
如果请求成功会缓存请求结果,如果请求失败会取上次缓存的请求结果
启动引导页或者广告页
在启动引导页或广告页之前,会先停顿2秒,然后根据是否是第一次进入应用决定是进引导页还是广告页
如上图所示,引导页需要向右划4下,并点击立即体验进入应用
安装后第二次打开应用会显示广告页,广告页会根据上一次拉取的图片进行显示
这里是整个应用的入口类,在这个对象创建时,会初始化SmartRefreshLayout下拉刷新组件,然后会在onCreate生命周期回调方法中执行如左图逻辑,大部分是初始化业务SDK
上图是App类中的全局字段和全局方法
其中SP是手机存储中的SharedPreference XML文件
当入口页面第一帧绘制完毕,操作系统就会把绘制好的画面切换进屏幕的最上层,然后用户就可以开始与APP交互。
从进程角度分析,应用的启动涉及四个进程,分别是Launcher进程(实际就是系统桌面APP),system_server进程(承载了大部分的系统服务功能),Zygote进程(用于创建新进程),App进程。
当用户点击桌面图标,实际是点击Launcher应用上的带图标的按钮,触发启动。
从类和方法调用角度分析如下图所示:
图中长方形代表类,线上的文字代表函数调用,序号代表调用顺序,其中可以直接优化的就是Application类的OnCreate方法(如图中序号5位置)和Activity的onCreate方法如图中序号9的位置。
如启动原理部分介绍,系统在启动APP进程前会先打开一个空白窗口,之前APP已将此窗口设置为透明,但为了给用户更快的响应速度和更好的启动体验,可以将此窗口设置为与启动页同一张图片,这样由这个窗口到启动页虽然是两个页面,但是显示一张图,用户是感知不到切换的。具体做法见这里的启动主题优化部分。
参考案例:百度地图Android APP。
可以将APP启动过程中的retrofit,友盟,市民卡web容器(含x5内核),通付盾,无痕埋点的初始化过程放在子线程中进行。还可以优化精简逻辑。
(1)Activity合并,是否可以把换图标的ChangeIconActivity的逻辑、广告页AdvertNewActivity的逻辑、引导页GuideActivity的逻辑合并进闪屏页的WelcomeActivity的逻辑,从而减少Activity切换的时间开销。
(2)网络请求优化,是否可以把部分启动过程中的网络请求放在首页
(3)闪屏页在跳转引导页或广告页之前会故意停留2s多,可以看是否可以去掉,如不可以去掉,也可以动态计算停留时长,就是把停留时长减去之前启动逻辑(包括网络请求返回数据)的时长
如上图所示,示例APP:抖音:
这样用户可以在前台处理授权对话框,首页在后边可以同时处理大量的网络访问和页面逻辑,两边同时进行。示例APP:抖音
adb shell am start -S -R 10 -W com.hfi.hangzhoubanshi/ .home.activity.ChangeIconActivity
其中-S表示每次启动前先强行停止,-R表示重复测试次数。每一次的输出如下所示信息:
Stopping: com.example.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.app/.MainActivity }
Status: ok
Activity: com.example.app/.MainActivity
ThisTime: 1059
TotalTime: 1059
WaitTime: 1073
Complete
其中TotalTime代表当前Activity启动时间,将多次TotalTime加起来求平均即可得到启动这个Activity的时间。
缺点:应用的启动过程往往不只一个Activity,有可能是先进入一个启动页,然后再从启动页打开真正的首页。某些情况下还有可能中间经过更多的Activity,这个时候需要将多个Activity的时间加起来。
将多个Activity启动时间加起来并不完全等于用户感知的启动时间。例如在启动页可能是先等待某些初始化完成或者某些动画播放完毕后再进入首页。使用命令行统计的方式只是计算了Activity的启动以及初始化时间,并不能体现这种等待任务的时间。
没有在AndroidManifest.xml对应的Activity声明中指定
根据系统日志来统计启动耗时,在Android Studio中查找已用时间,必须在logcat视图中禁用过滤器(No Filters)。因为这个是系统的日志输出,而不是应用程序的。你也可以查看其它应用程序的启动耗时。
过滤displayed输出的启动日志.
但是这种方法只能统计Activity的从触发到完全显示的时间,但是没有包含网络访问、故意停留和用户操作的时间,所以也不能准确的反映启动时间。
Testin云测的启动时间并不准确,大部分在1s以内,显然不可能。网易云笔记推荐的NimbleAPP已变成付费服务,且价格需要商谈,所以也不推荐。友盟上未找到关于APP启动时间的字段。
在APP的attachBaseContext()的最后加入
val sp:SharedPreferences = getSharedPreferences("sp_hzgovernment", Context.MODE_PRIVATE)
sp.edit().putLong("application_attach_time", System.currentTimeMillis()).apply()
然后在MainActivity的onCreate()前边添加如下代码
var appAttachTime = SPUtils.getLong("application_attach_time", 0);
var diffTime = System.currentTimeMillis() - appAttachTime;//从application到入口Acitity的时间
Log.i("Total Time", diffTime.toString() + "ms")
就可以统计出从App启动,到首页开始加载的时长
|
第一次启动 |
第二次启动 |
小米MIX2S |
14.6s |
8.3s |
华为荣耀7X |
20.7s |
9.3s |
OPPO A37f |
14.2s |
9.8s |
第一次启动指首次安装后的打开应用,包括授权和引导页的操作时间
第二次启动指打开过一次后的再次启动,不包括授权,广告页不点跳过
误差:操作系统工作时长并未计算在内,因此实际启动比上面的时间长1到2秒
以小米MIX2S为准
第一次启动
点击桌面图标 |
空窗口 |
闪屏页启动 |
授予权限 |
闪屏页停留 |
引导页启动 |
引导页操作 |
跳转主页 |
开始 |
启动时长------》 |
结束 |
|||||
|
约2s |
1.1s |
4s |
2.2s |
0.3s |
2s |
|
第二次启动
点击桌面图标 |
空窗口 |
闪屏页启动 |
闪屏页停留 |
广告页启动 |
广告页停留 |
跳转主页 |
开始 |
启动时长-------》 |
结束 |
||||
|
约2s |
1.1s |
2.2s |
0.2s |
4s |
|
点击图标后有更快响应
用户体验到的启动速度有明显提升(首次启动时间提高30%以上,第二次启动时间提高20%以上)
1.《Android 性能优化(一) —— 启动优化提升60%》
2.《android app的启动流程》
3.《App startup time》
4.《如何统计Android App启动时间》