Android - 启动白屏分析与优化
记录一次项目开发中的一个小问题,最近发现公司的项目启动白屏的时间非常长久,在高端机也要2-3秒,那么在低端机岂不是更久,别人打开你的APP,发现一篇白或者一片黑,很不友好,不懂的人还以为开发者很水。
先上一张我项目的启动时间
原始时间图.png
其实我的东西也不多,要3秒钟。所以App启动的时候会出现白屏(黑屏),非常不友好。网上有一种解决方案比较多人知道.就是将主题(白色/黑色)设置成自己SplashActivity的效果一样的话。用户误以为白屏就是正在启动的启动页,但是这种做法实际上并不能减少App启动的时间,只是将白屏的时候换上了一个跟启动页一样的背景。下面我讲两种做法。
下面跟大家科普一下启动模式。听到启动模式,大家会想到4大启动模式 ?相信Activity的启动模式大家复习基础知识面试的时候已经背烂了,我今天讲的不是Activity的启动模式,而是应用的启动模式。
应用的启动方式
1、冷启动:系统的进程没有,直到此开始,创建了应用程序的进程。 在应用程序自设备启动以来第一次启动或系统杀死应用程序等情况下会发生冷启动。 这种类型的启动在最小化启动时间方面是最大的挑战,因为系统和应用程序比其他启动状态具有更多的工作。
2、热启动:与冷启动相比,热启动应用程序要简单得多,开销更低。在热启动,所有的系统都是把你的活动到前台。如果所有应用程序的活动仍驻留在内存中,那么应用程序可以避免重复对象初始化,UI的布局和渲染。
热启动显示与冷启动场景相同的屏幕行为:系统进程显示空白屏幕,直到应用程序完成呈现活动。
3、温启动:用户退出您的应用,但随后重新启动。该过程可能已继续运行,但应用程序必须通过调用onCreate()从头开始重新创建活动。系统从内存中驱逐您的应用程序,然后用户重新启动它。进程和Activity需要重新启动,但任务可以从保存的实例状态包传递到onCreate()中。
为什么出现白屏
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
从你点击桌面的图标开始安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类。当这些操作走完之后,你才能看到SplashActivity的第一眼。那么这期间你Application里onCreate做了N多的SDK的初始化,这段时间您的屏幕都是你手机默认主题的颜色(白色/黑色)。这就是为什么我们做的APP会出现白屏。
优化方案
解决方案一 修改启动Activity的主题
步骤一 styles.xml 添加Style
<style name="SplashTheme" parent="AppTheme">
<!-- 将splash图片设置在这,这样这张图片取代白屏 -->
<item name="android:windowBackground">@drawable/bg_splash</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
步骤二 AndroidManifest.xml 添加启动页主题
<activity
android:name=".SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
解决方案二
既然是Application里做了很多耗时的初始化导致启动耗时,那么我们可以试着优化一下这里的初始化。介绍一样东西 :IntentService 。Service大家应该会比较熟悉,那我简单的科普一下这个类。
IntentService
IntentService is a base class for Service that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
This “work queue processor” pattern is commonly used to offload tasks from an application’s main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.
All requests are handled on a single worker thread – they may take as long as necessary (and will not block the application’s main loop), but only one request will be processed at a time.
IntentService是Service的子类,根据需要处理异步请求(以intent表示)。客户端通过调用startService(Intent) 发送请求,该Service根据需要启动,使用工作线程处理依次每个Intent,并在停止工作时停止自身。
这种“工作队列处理器”模式通常用于从应用程序的主线程中卸载任务。 IntentService类的存在是为了简化这种模式。 要使用它,扩展IntentService并实现onHandleIntent(Intent)。 IntentService将收到Intents,启动一个工作线程,并根据需要停止该服务。
所有请求都在单个工作线程处理 - 它们可能需要很长的时间(并且不会阻止应用程序的主循环),但是一次只会处理一个请求。
IntentService与Service的区别
Android中的Service是用于后台服务的,当应用程序被挂到后台的时候,问了保证应用某些组件仍然可以工作而引入了Service这个概念,那么这里面要强调的是Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,也就是说,在更多时候不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
那么我们当我们编写的耗时逻辑,不得不被service来管理的时候,就需要引入IntentService,IntentService是继承Service的,那么它包含了Service的全部特性,当然也包含service的生命周期,那么与service不同的是,IntentService在执行onCreate操作的时候,内部开了一个线程,去你执行你的耗时操作。
上面纯粹帮你们百度了一波,其实我也不是很懂,直接上代码吧。
InitIntentService.java
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
/**
* Created by 卖火柴的小女孩 - Jc on 2018/8/8.
*/
public class InitIntentService extends IntentService {
private static final String ACTION_INIT = "com.hjc.helloworld.action.INIT";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*/
public InitIntentService() {
super("InitIntentService");
}
public static void start(Context context) {
Intent intent = new Intent(context, InitIntentService.class);
intent.setAction(ACTION_INIT);
context.startService(intent);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent != null && ACTION_INIT.equals(intent.getAction()))
{
//执行百度地图初始化
//Push初始化
//友盟初始化
//....N多第三方初始化
}
}
}
App.java
import android.app.Application;
/**
* Created by 卖火柴的小女孩 - Jc on 2018/8/8.
*/
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
InitIntentService.start(this);
}
}
AndroidManifest.xml
<service android:name=".InitIntentService"/>
其实你可以理解开辟了另一个空间去执行各种第三方启动的耗时操作。看完本文的小伙伴,结合解决方案一和解决方案二,理论上能很大的解决APP启动白屏的问题了。