Android应用启动加速,使用IntentService在子线程初始化第三方SDK
欢迎来到格调小窝
一. 初识启动加速
应用的启动分为冷启动、热启动、温启动,而启动最慢、挑战最大的就是冷启动;系统和App本身都有很多工作需要从头开始! 应用在冷启动之前,要执行三个任务:
- 加载启动App;
- App启东之后立即展示出一个空白的Window;
- 创建App的进程
而这三个任务执行完毕之后会马上执行以下任务:
- 创建App对象;
- 启动Main Thread;
- 创建启动Activity的对象;
- 填充加载布局Views
- 执行第一次绘制屏幕过程measure -> layout -> draw,展示界面;
而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。
作为普通应用,App进程的创建等环节我们是无法主动控制的,== 可以优化的也就是Application、Activity创建以及回调等过程。==
同样,Google给出了启动加速的方向:
1.利用提前展示出来的Window,快速展示出来一个界面,给用户快速启动的体验;
2.避免在启动时做过多的耗时操作
3.避免I/O操作、反序列化、网络操作、布局嵌套等。
备注:方向1属于治标不治本,只是表面上快;方向2、3可以真实的加快启动速度。
二. 启动加速之主题切换
应用启动页白屏(StartingWindow)优化
StartingWindow 的处理方式:
1.使用系统默认的 StartingWindow : 用户点了应用图标启动应用,马上弹出系统默认的 StartingWindow(就是做动画的那个 Window) ,等应用加载好第一帧之后,StartingWindow 消失,显示应用第一帧,无缝衔接,体验还不错,这也是通常大部分 Android 应用的场景;比如大部分 Android 系统的自带应用,即刻、汽车之家等
2.自己定制简单的 StartingWindow : 用户点了应用图标启动应用,弹出应用自己定制的StartingWindow,等应用加载好第一帧之后,定制的 StartingWindow 消失,显示应用主界面,由于 StartingWindow 是自己定制的,启动的时候 Decode Bitmap 或者 Inflate 自定义 Layout 会有一定的耗时,但是总的来说与系统默认的差别不大,用户体验优;这样的应用包括淘宝、京东、微博、今日头条、美团等
在styles.xml文件中添加以下代码
<!-- 应用启动页(StartingWindow)的theme -->
<style name="AppTheme.StartingWindowTheme" parent="AppTheme">
<!-- 可以设置成纯颜色(设置一个和Activity UI相似的背景) -->
<!--<item name="android:windowBackground">@color/startingwindow_bgcolor</item>-->
<!--也可以设置成一张图片 -->
<item name="android:windowBackground">@drawable/startingwindow_bg</item>
</style>
在colors.xml文件中添加以下代码【根据实际情况来添加,如果使用纯颜色作为启动页白屏的背景的话,则需要添加】
<!-- 应用启动页(StartingWindow)的theme的背景色 -->
<color name="startingwindow_bgcolor">#00bfff</color>
在AndroidManifest.xml中给首页activity(第一个页面,一般是欢迎界面)设置自定义的theme
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.why.project.androidstartingwindowdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--将首页的them设置成自定义的样式-->
<activity android:name=".MainActivity"
android:theme="@style/AppTheme.StartingWindowTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
在Activity中恢复原有的style样式【否则的话,当activity的布局文件设置背景色为透明的时候,就会发现窗口的背景还是那张图片】
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.AppTheme);//恢复原有的样式
setContentView(R.layout.activity_main);
}
}
3.把 StartingWindow 禁掉或者设置透明 : 用户点了应用图标启动应用,由于 StartingWindow 被禁掉或者被设置透明,所以会出现点击图标后,除了图标黑一下之外没有任何响应,过个 1-N 秒(取决于应用第一帧的加载速度),直接显示应用主界面。这样的毒瘤应用包括:微信、微信读书、UC 浏览器、支付宝、工商银行、米家等。
三. 启动加速之Application
1.将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在子线程中初始化
InitializeService.start(this);
2.延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader
因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化。
注意:欢迎页面的2秒停留可以利用,把耗时操作延迟到这个时间间隔里。
四.IntentService与Service的区别
1.Service:
- Service服务是长期运行在后台;
- 不是单独的线程,它跟线程没有任何关系,所以不能进行耗时操作;
- 如果直接把耗时操作放在Service中的onStartCommand()中,可能发生ANR,如果有耗时操作,就必须开启一个单独的线程来处理;
为了解决这样的问题,就引入了IntentService;
2.IntentService:
- 启动方式和Service一样,都是startService();
- 继承于Service,包含Service所有特性,包括生命周期,是处理异步请求的一个类,;
- 一般自定义一个InitializeService继承Service,然后复写onHandleIntent()方法,在这个方法中初始化这些第三方的,来执行耗时操作;
- 可以启动多次IntentService,每一个耗时操作以工作队列在onHandleIntent()方法中执行,执行完第一个再去执行第二个,以此类推;
- 所有的请求都在单线程中,不会阻塞主线程,同一个时间只处理同一个请求;
- 不需要像在Service中一样,手动开启线程,任务执行完成后不需要手动调用stopSelf()方法来停止服务,系统会自动关闭服务;
举个栗子:
service要在AndroidManifest里注册一下:
<service
android:name=".service.InitializeService"
android:enabled="true"
android:exported="false"/>
Application中启动服务:
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//initLeakCanary(); //Square公司内存泄漏检测工具
//在子线程中初始化
InitializeService.start(this);
}
}
initApplication()方法里换成你要初始化的SDK
public class InitializeService extends IntentService {
private static final String ACTION_INIT = "initApplication";
public InitializeService() {
super("InitializeService");
}
public static void start(Context context) {
Intent intent = new Intent(context, InitializeService.class);
intent.setAction(ACTION_INIT);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_INIT.equals(action)) {
initApplication();
}
}
}
private void initApplication() {
initBugly(); //初始化腾讯bug管理平台
}
/**
* 初始化腾讯bug管理平台
* 子线程进行初始化SDK操作
*/
private void initBugly() {
/* Bugly SDK初始化
* 参数1:上下文对象
* 参数2:APPID,平台注册时得到,注意替换成你的appId
* 参数3:是否开启调试模式,调试模式下会输出'CrashReport'tag的日志
* 注意:如果您之前使用过Bugly SDK,请将以下这句注释掉。
*/
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext());
strategy.setAppVersion(AppUtils.getAppVersionName());
strategy.setAppPackageName(AppUtils.getAppPackageName());
strategy.setAppReportDelay(20000); //Bugly会在启动20s后联网同步数据
/* 第三个参数为SDK调试模式开关,调试模式的行为特性如下:
输出详细的Bugly SDK的Log;
每一条Crash都会被立即上报;
自定义日志将会在Logcat中输出。
建议在测试阶段建议设置成true,发布时设置为false。*/
CrashReport.initCrashReport(getApplicationContext(), "126dde5e58", true ,strategy);
}
}