系列文章
目录
前言
前面的章节我们已经介绍了启动分类的启动流程,同时也介绍了启动时间的测量方式。接下来将介绍两种启动优化方式 — 视觉优化和异步优化
视觉优化
app启动后,WindowManager
会先加载app theme
中的windowBackground
,所以通常就会出现白屏的情况(取决于你的主题是Dark
还是Light
),我们对windowBackground
进行设置,让用户从视觉上感觉app的启动变快
解决方案
- 新增一个主题样式,设置
windowBackground
属性,在其中放一张背景图片,或是广告图片之类的
<style name="AppTheme.Launcher" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/bg_launcher</item
</style>
bg_launcher.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap
android:gravity="center"
android:src="@drawable/ic_android" />
</item>
</layer-list>
- 给
MainActivity
设置主题
<activity android:name=".MainActivity"
android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 在
MainActivity
的onCreate()
方法执行前将主题替换回原来的样式
@Override
protected void onCreate(Bundle savedInstanceState) {
//替换为原来的主题,在onCreate之前调用
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
这种方式虽然不能加快应用的启动时间,但是可以防止应用启动白屏,带来更好的用户体验
异步优化
如何进行异步优化
核心思想:子线程分担主线程任务,通过并行减少时间
异步优化.png异步优化的思想其实很明确,也很容易理解,但是真正在异步优化的实践过程中,其实是有很多“坑”的
这里写了一个简单的demo,我们在Application
中进行了一些初始化操作,实际场景往往比这要复杂很多
Application.java
@Override
public void onCreate() {
super.onCreate();
//高德地图
initAMap();
//weex
initWeex();
//Stecho
initStetho();
//图片加载
initFresco();
//自己写的代码
initDeviceId();
//推送
initJPush();
}
显然,上面这种初始化方式是串行的,如果初始化的内容过多,显然启动速度就会变慢。结合我们说的异步初始化的思想,我们使用了线程池来来优化我们的初始化,使得初始化代码能够并行
这里的CORE_POOL_SIZE
我们参照了AsyncTask
的源码,根据cpu数量来确定线程池的核心池的数量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
@Override
public void onCreate() {
super.onCreate();
mApplication = this;
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
service.submit(new Runnable() {
@Override
public void run() {
//高德地图
initAMap();
}
});
service.submit(new Runnable() {
@Override
public void run() {
//weex
initWeex();
}
});
service.submit(new Runnable() {
@Override
public void run() {
//Stecho
initStetho();
}
});
....
}
如果对线程池比较熟的人会知道,现在Application
的onCreate()
方法一定执行的特别快。但是有的人可能会问,这里我们能不能把所有初始化方法都放到一个Runnable
中去呢?
这样做理论上其实是可以的,因为它是异步的,但是上面说了,我们这里其实是根据cpu数量来确定线程池的核心池的数量,假设创建出来3个线程,但是我们只用了一个,那这样显然是一种资源的浪费,所以我们采取了对每个初始化方法都进行了submit
的方式
我们可以看到,通过异步的方式进行初始化,效果是很明显的
异步优化的问题
见识到了异步优化的成果,但是先不要急着高兴,简单思考一下的话我们就会发现,这种方式其实是并不完全合理的,很多场景下我们实际上并不能直接使用这种方案,这里举几个例子
- 不符合异步要求。实际项目初始化往往比较复杂,有时候会遇到某些初始化方法一定要在主线程执行,那么用上面这种方式显然也是不可行的
-
需要在某阶段完成。例如我们在闪屏页面就要用到
weex
的相关方法,这时候如果weex
在子线程还没有初始化成功,显然就会有问题 -
任务之间有依赖关系。例如我们这里的
initJPush()
方法,这个方法需要用到DeviceId
,所以就需要在initDeviceId()
之后执行
针对问题1,我们需要修改我们的代码,把不符合异步要求的方法放到主线程执行
针对问题2,这里介绍一个小技巧,我们用到了CountDownLatch
,构造方法中传入1
,这里的意思是countDownLatch
必须被满足一次,也就是说直到 countDownLatch.countDown()
被执行,否则countDownLatch.await()
会一直等待
private CountDownLatch countDownLatch = new CountDownLatch(1);
@Override
public void onCreate() {
super.onCreate();
mApplication = this;
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
.....
service.submit(new Runnable() {
@Override
public void run() {
//weex
initWeex();
countDownLatch.countDown();
}
});
.....
try {
countDownLatch.await();
}catch (Exception e){
e.printStackTrace();
}
}
针对问题3,我们很容易想到下面这种方式,把initDeviceId()
放到initJPush()
之前
service.submit(new Runnable() {
@Override
public void run() {
initDeviceId();
initJPush();
}
});
这样做的话,看起来好像解决了问题。但是实际场景往往很复杂,有很多类似的这种依赖关系,我们很可能遇到一个情况,就是我们并不能直接把某个初始化方法移到同一个Runnable
下
我们上面这种写法还有一个很大的问题,就是代码不优雅,如果实际项目中有一百个初始化项目呢?难道就要写一百次service.submit()
方法,我相信很多同学肯定是无法接受的,因为不优雅的代码同时也带了很高的维护成本
总结
本文介绍了两个比较常规的启动优化方案,一个是视觉优化,一个是异步优化。但是经过本文的分析,我们看到了,常规的异步优化方法,在真实项目中实施起来,其实是存在很多问题的。那我们如何来解决这些问题呢?请看启动优化(四)
网友评论