关于启动的google官方文档
一 背景
app启动,相当于咱们的一个门口,假如咱们app启动速度比较慢,估计咱们的用户都跑去竞品那里去玩了,咱们就会失去部分用户了。所以优化启动速度是必须的。
二 应用启动类型(官方定义的三个类型)
应用有三种启动状态,每种状态都会影响应用向用户显示所需的时间:冷启动、温启动或热启动,像咱们优化的启动一般是优化冷启动,因为冷启动相对于来说,初始化东西比较多,做的东西也比较多。
2.1 冷启动
冷启动:冷启动是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用自设备启动后或系统终止应用后首次启动。系统和应用要做的工作比在另外两种启动状态中更多。所以一般优化这里的情况
2.1.1冷启动的系统3个任务
- 加载并且启动应用
- 在启动后立即显示 SplashActivity 的window背景
- 创建应用进程
2.1.2创建应用进程包括以下流程
- 创建应用对象,应该是初始化Application,执行Appplication的OnCreate() 方法
- 启动主线程
- 创建SplashActivity,开始准备要启动Activity 也就是走Activity的OnCreate()方法
- (加载View)Inflating SplashActivity的xml,变成View,此时解析xml,循环遍历
- 把上面的View 布局到 SplashActivity
- 执行绘制流程,当绘制完成,系统就会替换当前的window,替换成要启动的SplashActivity,也就是onResume()和onAttachedToWindow()方法执行完成,也就是当View的OnDraw()方法执行完,渲染完毕之后用户就会看到 SplashActivity界面的UI。
2.1.3为什么出现白屏?
因为在xml中的UI渲染完毕之前,会有很多初始化,比如Application的onCreate 和 Activity的onCreate 以及 View的onMeasure(),onLayout(),和Ondraw() 之后 和 在Activity的onWindowFocusChanged()之前 此时显示的白屏 其实是 SplashActivity 的 主题的 windowBackground。
2.1.4 Activity的onWindowFocusChanged()说明
This is the best indicator of whether this activity is visible to the user
对于用户来说是否可见,onWindowFocusChanged 是最好的指示器。也可以认为onWindowFocusChanged就是出现在和用户面前的第一帧(第一帧的概念没有官方依据,只是查阅资料凭空想象的。不正确的话请指出)
2.2 热启动
热启动:应用的热启动比冷启动简单得多,开销也更低。此时进程,Activity 都还在内存中,所以不用初始化很多东西,其实就是咱们在app中,然后点击Home键,返回到桌面而已。
2.3 温启动
温启动:此时当前应用的进程和Application都存在,温启动不会再走Application的OnCreate()方法,但是此时Activity都是重新创建的,会再次走Activity的生命周期。所以在OnAcreate()方法到onResume()到 Activity的onWindowFocusChanged() 期间还是会有空间的概念,也就是还是会有白屏的现象
- 比如 所有的界面都是通过返回键 返回到桌面的。
- 官网还说了一种情况,当系统内存不够的时候,把我们app回收了,然后用户又重新启动app,进程和Activity都需要重启,但传递到 onCreate() 的 bundel 是有值的。此时也算温启动
三. 冷启动时间检测
冷启动流程
- 启动进程。
- 初始化对象。
- 创建并初始化 Activity。
- 填充布局。
- 绘制完第一帧。
3.1 Logcat 中 筛选 Displayed 关键字
Android 4.4(API 级别 19)开始,Logcat 中会输出从程序启动到某个 Activity 显示到画面上所花费的时间。下面分别是微信,支付宝,抖音,头条,高德,陌陌 ,因为我这是10.0手机,比较快,并且我自己的demo 故意在 Application中Thread.sleep(1000)了.
- 10.0手机可以筛选ActivityTaskManager: Displayed 关键字,10.0手机只会显示启动的页面的时间
- 10.0 以下可以通过 ActivityManager: Displayed 关键字, 会显示所有的Activity的启动时间。
3.2 通过adb命令行检测
下面的packagename 是你自己的包名。后面的SplashActivity 要填写AndroidManifest.xml中的启动Activity的name。
- adb shell am start -W [packagename]/.SplashActivity ,启动一次
- adb shell am start -S -R 10 -W [packagename]/.SplashActivity 可以启动多次,
- -S表示每次启动前先强行停止
- -R表示重复测试次数
参数表示
- ThisTime:表示启动最后一个Activity的时间
- TotalTime:表示应用启动的耗时,包括新进程的创建和Activity的启动
- WaitTime(5.0之前没有):ActivityManagerService启动App的Activity时的总时间
3.3 reportFullyDrawn() 可以看出真正的时间
什么叫做真正的时间?当我们进入一个页面,虽然都绘制成功了,但是我们的网络数据还在加载中,等网络加载成功,我们设置值之后,才是咱们真正的想要的UI,所以此时我们在设置完值之后,调用 Activity 的 reportFullyDrawn() ,此时再Logcat上我们就可以观察到这个从启动到完全显示的时间,此时的过滤器不是Display了。
3.4 更加细致的时间 系统跟踪 官方文档
- Android Studio CPU 性能剖析器(Profiler)
- “系统跟踪”应用
- Systrace 命令行工具
- Perfetto 命令行工具
四.视觉上的优化
既然咱们都知道,从启动到 SplashActivity 的UI渲染出来之前,是 SplashActivity 的主题的 windowBackground, 那咱们可以更改这个背景,来假装启动很快,但是这个治标不治根,只是个假象而已。切记要在SplashActivity的onCreate中再恢复成想要的主题
4.1 把SplashActivity的主题 设置成透明的 或者是 不显示window的背景主题
<!-- 不显示window的背景主题-->
<style name="SplashDisableTheme" parent="AppTheme">
<item name="android:windowDisablePreview">true</item>
</style>
<!-- 背景主题为透明的-->
<style name="SplashTransparencyTheme" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
</style>
但是这个有点奇怪,就是我们点击logo之后,等一会才进去app。
4.2把SplashActivity的 windowBackground 背景替换成关于我们app的一个图片,或者是 颜色,或者是图片层
<!-- 设置一个颜色-->
<style name="SplashTheme" parent="AppTheme">
<item name="android:windowBackground">@color/colorAccent</item>
</style>
<!-- 设置一个图片层-->
<style name="SplashlayerTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowFullscreen">true</item>
</style>
下面是splash_bg的图片层
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#DC2929" />
</shape>
</item>
<item android:bottom="90dp">
<bitmap
android:gravity="bottom"
<!-- 一个正经八经的图片,不会变形的-->
android:src="@mipmap/love_zhongguo" />
</item>
</layer-list>
做这个全屏的时候还需要适配挖孔屏的。否则,状态栏是一片黑。也就是在values-v28文件中的主题加入windowLayoutInDisplayCutoutMode
<style name="SplashlayerTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
4.3 在 SplashActivity 的 onCreate()中恢复咱们需要的主题
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 恢复自己需要的主题
setTheme(R.style.AppTheme);
setContentView(R.layout.activity_splash)
}
五.从代码角度和执行流程角度去优化(从根减少冷启动时间)
从冷启动的的流程来看,主要从 Application 的 onCreate() 和 SplashActivity 的 onCreate()中出发
Application和Activity逻辑处理
主要从下面几个方面 Jetpack - Startup
- 异步初始化 可以参考阿里的alpha:Android异步启动框架,它简单,高效,功能完善。
- 延迟初始化 比如可以把一些东西,放到广告页里面初始化
- 懒初始化 ,用到时再初始化
- 线程问题,线程优先级,线程是一个比较珍贵的东西,一个进程中,如果不设置线程的优先级,每个线程都会相同几率来获取CPU的时间片。就会导致主线程执行的时间变小,所以可以设置线程优先级,来增大分配主线程的时间片的几率
- 线程池 , 在Application中 或者 SplashActivity中,用一个线程 要 比 用一个线程池要强的多,可以延迟初始化线程池,
- 网络请求 尽量减少网络请求
- IO/数据库操作 IO 放到子线程中
- 避免GC,因为GC会出发Stop The World
- 如果有多个进程的话,Application会初始化多次,也就是每一个进程都会走一遍Application的onCreate(),虽然其他进程初始化的Application不会影响咱们住进程的速度,但是会影响整个手机的内存大小。所以当我们app中有多进程的时候,在onCreate中可以判断是不是咱们自己的主进程,之后再初始化东西
Activity中 下面属于绘制优化了
- 避免xml过大,因为读取xml就已经很耗时了
- 布局层级尽量少 因为读取View的时候,是个递归操作
- View的onLayout,onMeasure,onDraw() 中 避免耗时操作
- 减少必要的背景
题外话
github,里面有记录的 已经做好的轮子来进行性能优化的