1. 冷启动与热启动
通常我们在使用某个应用程序时,都是点击桌面应用图标来启动该程序。你肯定或多或少的碰到过这种情况:应用启动的一刹那,手机会先白屏或者黑屏一段时间,然后再进入应用程序的主页,但是你退出应用后再次打开APP,确又发现白屏时间极短或者压根感觉不出来。
上面这种现象,涉及到2个概念:冷启动与热启动,我们先来理解下这2个术语是什么意思。
- 冷启动:当启动某个应用程序时,如果Android手机系统发现后台没有该应用程序的进程,则会先创建一个该应用程序进程,这种方式叫冷启动。
- 热启动:当启动某个应用程序时,系统后台已经有一个该应用程序的进程了,则不会再创建一个新的进程,这种方式叫热启动。通常,我们按返回键退出应用,按home键回到桌面,该应用进程还一直存活,再次启动应用都叫热启动。
需要注意的是,现在很多手机都有一键清理内存的功能,例如小米、华为等手机,还有类似腾讯手机管家之类的软件,也可实现类似功能。采用这些方式清理内存后,应用进程被杀掉了,再次启动应用,都属于冷启动了。
2. 白屏原因
通常情况下,白屏现象都是在冷启动情况下出现的,如果白屏时间过长,就会给人一种APP很卡顿的感觉。
应用程序启动时,系统会从zygote进程fork一个新的进程给程序使用,接着会创建和初始化Application类,再接着就是创建和初始化启动页Activity类了,只有启动页Activity能够在窗口显示出来之后,我们才能在设备上看到应用的程序的第一帧了。其大概流程可总结为:
从zygote进程fork出新进程 --> 创建Application类 --> Application.attachBaseContext() --> Application.onCreate() --> 创建入口Activity类 -> Activity.onCreate() --> Activity.onStart() --> Activity.onResume()
由此可见,冷启动可简单总结为3个步骤:
1.从zygote进程fork出一个新进程,这个步骤所需时间可忽略,因为我们无法控制;
2.创建Application类并初始化,主要是执行Application.onCreate()方法;
3.创建并初始化入口Activity类,并在窗口上绘制UI;
热启动时,相比冷启动少了1、2这2个步骤,步骤1我们不考虑,所以我们可初步判定Application.onCreate()执行比较耗时,才出现了白屏现象。可以自己写个demo验证,在Application.onCreate()方法里使线程停顿几秒钟,冷启动打开应用时就会发现白屏时间延长了几秒钟。
我们现在开发Android应用程序时,通常都会集成很多第三方SDK,这些SDK大多都需要在Application.onCreate()里进行初始化,同时我们自己的应用也有很多需要在这里进行初始化,这样导致了Application.onCreate()方法执行时间会很长。
但是Application.onCreate()执行时间过长跟应用启动白屏有什么关系呢?我们看到的界面,其实都是在手机window上绘制出来的,我们开发时的Activity并不是一个能看到的界面,它最终能显示出来,都需要通过measure、layout等过程之后,在window上绘制,我们才能看到界面。在应用启动之后,window就已经存在了,Application初始化时,会从配置的Theme里,读取一个名为“android:windowBackground”的属性,顾名思义就是窗口背景,该背景就会在window上绘制出来,同样我们也能看到了。如果我们采用的Theme里,没有覆盖该属性值,则会采用该Theme的默认值,大部分系统提供的Theme里,windowBackground都是白色,所以我们会看到白屏了。如果windowBackground属性值是黑色,我们就会看到黑屏了,如果是透明色,我们会发现应用卡顿之后才能看到程序界面。
3. 消除启动时的白屏/黑屏
3.1 优化Application.onCreate()方法
这里给出几点建议:
- 尽量不要在Application里做耗时的操作,例如读写文件、数据库等;
- 各种第三方SDK,看能否在需要使用的时候进行初始化;
- 某些耗时的初始化方法能否放到线程里异步执行;
建议归建议,但实际开发时会发现很难,好多操作都非得放在Application里执行,移出去很困难,特别是很多老工程,你都不能预料到会出现什么幺蛾子。
3.2 设置android:windowBackground属性
在styles.xml里定义一个style,设置windowBackground属性:
<style name=LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar.Fullscreen">
<item name="android:windowBackground">@drawable/launch_bg</item>
<item name="android:windowFullscreen">true</item>
</style>
配置入口Activity的theme属性为上面定义的style:
<activity
android:name=".LaunchActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
windowBackground属性可以配置任意drawable,比方说与你启动页一样的图片,这样在冷启动时就能立马看到这张图片展示出来,然后跳转到启动页时,能做到视觉上的无缝过渡,这样就能让应用程序看起来是秒启动。
需要注意的是,这里的style我们继承自@android:style/Theme.Light.NoTitleBar.Fullscreen,如果你的LaunchActivity继承自AppCompatActivity,那么启动该Activity时会直接报错,因为AppCompatActivity必须采用继承自Theme.AppCompat的主题。
那有人说了,如果是这样,那我们就采用Theme.AppCompat.Light.NoActionBar好了,一般情况下这样也没问题。但是当你在某些有虚拟按键的手机上,会发现冷启动时你配置的图片占据了整个屏幕(底部虚拟按键的地方会挡住你的图片),然后进入启动页时,图片显示的区域又不包含底部虚拟按键的地方了,这样过渡就不平滑,用户体验不佳。但是采用我们之前定义的那个style,就不会出现这个问题了。这个问题困扰了我好久,后来才发现采用Theme.Light.NoTitleBar.Fullscreen才能解决这种兼容性问题,希望对大家有所帮助。