如需轉(zhuǎn)載請(qǐng)?jiān)u論或簡(jiǎn)信,并注明出處,未經(jīng)允許不得轉(zhuǎn)載

系列文章
- App啟動(dòng)優(yōu)化(一)冷啟動(dòng)和熱啟動(dòng)
- App啟動(dòng)優(yōu)化(二)啟動(dòng)時(shí)間測(cè)量
- App啟動(dòng)優(yōu)化(三)啟動(dòng)優(yōu)化方案
目錄

前言
前面的章節(jié)我們已經(jīng)介紹了啟動(dòng)分類(lèi)的啟動(dòng)流程,同時(shí)也介紹了啟動(dòng)時(shí)間的測(cè)量方式。接下來(lái)將介紹兩種啟動(dòng)優(yōu)化方式 — 視覺(jué)優(yōu)化和異步優(yōu)化
視覺(jué)優(yōu)化
app啟動(dòng)后,WindowManager會(huì)先加載app theme中的windowBackground,所以通常就會(huì)出現(xiàn)白屏的情況(取決于你的主題是Dark還是Light),我們對(duì)windowBackground進(jìn)行設(shè)置,讓用戶(hù)從視覺(jué)上感覺(jué)app的啟動(dòng)變快
解決方案
- 新增一個(gè)主題樣式,設(shè)置
windowBackground屬性,在其中放一張背景圖片,或是廣告圖片之類(lèi)的
<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設(shè)置主題
<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()方法執(zhí)行前將主題替換回原來(lái)的樣式
@Override
protected void onCreate(Bundle savedInstanceState) {
//替換為原來(lái)的主題,在onCreate之前調(diào)用
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
這種方式雖然不能加快應(yīng)用的啟動(dòng)時(shí)間,但是可以防止應(yīng)用啟動(dòng)白屏,帶來(lái)更好的用戶(hù)體驗(yàn)
異步優(yōu)化
如何進(jìn)行異步優(yōu)化
核心思想:子線(xiàn)程分擔(dān)主線(xiàn)程任務(wù),通過(guò)并行減少時(shí)間

異步優(yōu)化的思想其實(shí)很明確,也很容易理解,但是真正在異步優(yōu)化的實(shí)踐過(guò)程中,其實(shí)是有很多“坑”的
這里寫(xiě)了一個(gè)簡(jiǎn)單的demo,我們?cè)?code>Application中進(jìn)行了一些初始化操作,實(shí)際場(chǎng)景往往比這要復(fù)雜很多
Application.java
@Override
public void onCreate() {
super.onCreate();
//高德地圖
initAMap();
//weex
initWeex();
//Stecho
initStetho();
//圖片加載
initFresco();
//自己寫(xiě)的代碼
initDeviceId();
//推送
initJPush();
}
顯然,上面這種初始化方式是串行的,如果初始化的內(nèi)容過(guò)多,顯然啟動(dòng)速度就會(huì)變慢。結(jié)合我們說(shuō)的異步初始化的思想,我們使用了線(xiàn)程池來(lái)來(lái)優(yōu)化我們的初始化,使得初始化代碼能夠并行
這里的CORE_POOL_SIZE我們參照了AsyncTask的源碼,根據(jù)cpu數(shù)量來(lái)確定線(xiàn)程池的核心池的數(shù)量
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();
}
});
....
}
如果對(duì)線(xiàn)程池比較熟的人會(huì)知道,現(xiàn)在Application的onCreate()方法一定執(zhí)行的特別快。但是有的人可能會(huì)問(wèn),這里我們能不能把所有初始化方法都放到一個(gè)Runnable中去呢?
這樣做理論上其實(shí)是可以的,因?yàn)樗钱惒降?,但是上面說(shuō)了,我們這里其實(shí)是根據(jù)cpu數(shù)量來(lái)確定線(xiàn)程池的核心池的數(shù)量,假設(shè)創(chuàng)建出來(lái)3個(gè)線(xiàn)程,但是我們只用了一個(gè),那這樣顯然是一種資源的浪費(fèi),所以我們采取了對(duì)每個(gè)初始化方法都進(jìn)行了submit的方式

我們可以看到,通過(guò)異步的方式進(jìn)行初始化,效果是很明顯的
異步優(yōu)化的問(wèn)題
見(jiàn)識(shí)到了異步優(yōu)化的成果,但是先不要急著高興,簡(jiǎn)單思考一下的話(huà)我們就會(huì)發(fā)現(xiàn),這種方式其實(shí)是并不完全合理的,很多場(chǎng)景下我們實(shí)際上并不能直接使用這種方案,這里舉幾個(gè)例子
- 不符合異步要求。實(shí)際項(xiàng)目初始化往往比較復(fù)雜,有時(shí)候會(huì)遇到某些初始化方法一定要在主線(xiàn)程執(zhí)行,那么用上面這種方式顯然也是不可行的
-
需要在某階段完成。例如我們?cè)陂W屏頁(yè)面就要用到
weex的相關(guān)方法,這時(shí)候如果weex在子線(xiàn)程還沒(méi)有初始化成功,顯然就會(huì)有問(wèn)題 -
任務(wù)之間有依賴(lài)關(guān)系。例如我們這里的
initJPush()方法,這個(gè)方法需要用到DeviceId,所以就需要在initDeviceId()之后執(zhí)行
針對(duì)問(wèn)題1,我們需要修改我們的代碼,把不符合異步要求的方法放到主線(xiàn)程執(zhí)行
針對(duì)問(wèn)題2,這里介紹一個(gè)小技巧,我們用到了CountDownLatch,構(gòu)造方法中傳入1,這里的意思是countDownLatch必須被滿(mǎn)足一次,也就是說(shuō)直到 countDownLatch.countDown()被執(zhí)行,否則countDownLatch.await()會(huì)一直等待
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();
}
}
針對(duì)問(wèn)題3,我們很容易想到下面這種方式,把initDeviceId()放到initJPush()之前
service.submit(new Runnable() {
@Override
public void run() {
initDeviceId();
initJPush();
}
});
這樣做的話(huà),看起來(lái)好像解決了問(wèn)題。但是實(shí)際場(chǎng)景往往很復(fù)雜,有很多類(lèi)似的這種依賴(lài)關(guān)系,我們很可能遇到一個(gè)情況,就是我們并不能直接把某個(gè)初始化方法移到同一個(gè)Runnable下
我們上面這種寫(xiě)法還有一個(gè)很大的問(wèn)題,就是代碼不優(yōu)雅,如果實(shí)際項(xiàng)目中有一百個(gè)初始化項(xiàng)目呢?難道就要寫(xiě)一百次service.submit()方法,我相信很多同學(xué)肯定是無(wú)法接受的,因?yàn)椴粌?yōu)雅的代碼同時(shí)也帶了很高的維護(hù)成本
總結(jié)
本文介紹了兩個(gè)比較常規(guī)的啟動(dòng)優(yōu)化方案,一個(gè)是視覺(jué)優(yōu)化,一個(gè)是異步優(yōu)化。但是經(jīng)過(guò)本文的分析,我們看到了,常規(guī)的異步優(yōu)化方法,在真實(shí)項(xiàng)目中實(shí)施起來(lái),其實(shí)是存在很多問(wèn)題的。那我們?nèi)绾蝸?lái)解決這些問(wèn)題呢?請(qǐng)看啟動(dòng)優(yōu)化(四),直接項(xiàng)目奉上,有興趣可以自己看看,具體分析可能要等到哪天有空我想分析的時(shí)候了。。