App啟動(dòng)優(yōu)化(三)啟動(dòng)優(yōu)化方案

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

系列文章

目錄

前言

前面的章節(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)變快

解決方案

  1. 新增一個(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>
  1. 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>
  1. MainActivityonCreate()方法執(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)化.png

異步優(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)在ApplicationonCreate()方法一定執(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的方式

異步優(yōu)化.png

我們可以看到,通過(guò)異步的方式進(jìn)行初始化,效果是很明顯的

異步優(yōu)化的問(wèn)題

見(jiàn)識(shí)到了異步優(yōu)化的成果,但是先不要急著高興,簡(jiǎn)單思考一下的話(huà)我們就會(huì)發(fā)現(xiàn),這種方式其實(shí)是并不完全合理的,很多場(chǎng)景下我們實(shí)際上并不能直接使用這種方案,這里舉幾個(gè)例子

  1. 不符合異步要求。實(shí)際項(xiàng)目初始化往往比較復(fù)雜,有時(shí)候會(huì)遇到某些初始化方法一定要在主線(xiàn)程執(zhí)行,那么用上面這種方式顯然也是不可行的
  2. 需要在某階段完成。例如我們?cè)陂W屏頁(yè)面就要用到weex的相關(guān)方法,這時(shí)候如果weex在子線(xiàn)程還沒(méi)有初始化成功,顯然就會(huì)有問(wèn)題
  3. 任務(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í)候了。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容