性能優(yōu)化(1.3)-APP啟動(dòng)速度優(yōu)化實(shí)例解析

主目錄見:Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
?這篇是看了別人的想法之后米愿,發(fā)現(xiàn)這個(gè)方法還是蠻好的利诺,所以在這里給大家一起分享下赞庶,畢竟性能還是大家所追求的翁脆。而且有時(shí)候性能優(yōu)化就是一個(gè)簡單的舉動(dòng)也許就能改變很多眷蚓,使你的App看過去更專業(yè)。上一篇我們講了[布局優(yōu)化(扁平化反番,Merge的使用沙热,ViewStub的使用)],這一篇也會(huì)用到這里的知識(shí)罢缸。當(dāng)然如果你想要這篇文章的例子可以[點(diǎn)擊下載]篙贸。

一.目標(biāo)

今天分享這一篇呢主要就是給大家提供一個(gè)方案吧,也希望大家如果有好的東西可以分享出來給大家知道枫疆。今天的目標(biāo)是:
1.優(yōu)化App的冷啟動(dòng)速度爵川;
2.同時(shí)從性能上說說白屏的解決方法優(yōu)劣。

二.優(yōu)化實(shí)例解析

我們都知道养铸,Activity的啟動(dòng)流程對(duì)于我們的來說雁芙,能優(yōu)化的流程里面只有后面View怎么呈現(xiàn)出來,View渲染完畢數(shù)據(jù)的請(qǐng)求加載钞螟。按理說這部分能做的工作最多兔甘。我們先來說說View是怎么顯示在界面上的?

Android的視圖都是通過window來呈現(xiàn)的鳞滨,不管Activity洞焙,dialog或者Toast等都有一個(gè)對(duì)應(yīng)的window,然后通過WindowManager來管理View拯啦。而且在Acitivity啟動(dòng)過程中又會(huì)通過WindowManagerImpl將window和view(DecorView)進(jìn)行關(guān)聯(lián)調(diào)用addView將decorView添加澡匪。接著從ViewRootImpl調(diào)用performTraversals()依次經(jīng)過measure(),layout()和draw()三個(gè)過程將view顯示出來褒链。

從這上面可以可以看出有一大部分的時(shí)間占用在視圖的渲染上面唁情。我們先來看看以前早期我們的啟動(dòng)頁設(shè)計(jì):

  • 我們首先會(huì)先創(chuàng)建一個(gè)SplashActivity頁面,然后在這里寫啟動(dòng)頁的展示信息而且也會(huì)有幾秒跳過廣告等甫匹,同時(shí)這里可能做一些數(shù)據(jù)的預(yù)加載甸鸟,然后數(shù)據(jù)加載完畢傳給MainActivity。

然后我們看看我們這篇的做法:

  • 我們這里把SplashAcitivity改成Fragment的形式實(shí)現(xiàn)兵迅,然后在MainActivity的時(shí)候進(jìn)行展示這個(gè)Fragment抢韭,在展示這個(gè)Fragment的同時(shí),我們?cè)诖绑w加載完畢之后直接把我們真正的布局加載進(jìn)來恍箭,這個(gè)布局可能會(huì)比較復(fù)雜刻恭,所以我們?cè)谡故綟ragment的時(shí)候我們不渲染,而是用ViewStub進(jìn)行懶加載扯夭,與此同時(shí)鳍贾,我們還可以同時(shí)進(jìn)行異步網(wǎng)絡(luò)數(shù)據(jù)的加載。

從上面的做法我們可以看出交洗,第一種做法贾漏,雖然SplashActivity加載很快,然后數(shù)據(jù)也做了預(yù)加載藕筋,但是到MainActivity的時(shí)候纵散,首頁布局依舊是比較復(fù)雜的所以還是會(huì)經(jīng)過view的measure,layout隐圾,draw整個(gè)過程伍掀,還是拖慢了整個(gè)啟動(dòng)過程,而第二個(gè)方案卻將啟動(dòng)頁和MainActivity主布局合并在一起暇藏,而且在渲染Splash頁面的時(shí)候蜜笤,MainActivity的主布局是放在ViewStub里面的。同時(shí)數(shù)據(jù)可以在Splash頁面展示的時(shí)候就進(jìn)行加載盐碱,用戶感知不到把兔。

1.SplashFragment

首先我們來從啟動(dòng)頁Fragment說起沪伙,這個(gè)頁面我們盡量簡單。這里我們先來看看xml內(nèi)容:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >

    <FrameLayout
        android:id="@+id/frame"
        android:background="@mipmap/bg_welcome"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>

</RelativeLayout>

我們看到這里只是簡單的一個(gè)背景而已县好,當(dāng)然有的app會(huì)在這邊播放一個(gè)視頻围橡,或者在這邊加載一個(gè)廣告。但是這是具體的設(shè)計(jì)決定缕贡,我們不參與細(xì)講翁授。我們來看看我們的splashFragment:

public class SplashFragment extends Fragment {
    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_splash, container,false);
    }
}

我們看到我們這里只是簡單地將我們的布局inflate進(jìn)來。然后我們就將這個(gè)fragment在我們的MainAcitivity里面展示晾咪。

2.MainActivity

我們這里先來看看MainActivity里面setContentView的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                >
    <ViewStub
        android:id="@+id/content_viewstub"
        android:layout="@layout/activity_main_viewstub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>
</RelativeLayout>

我們看到第一個(gè)ViewStub是用于放我們MainActivity主布局activity_main_viewstub的收擦,第二個(gè)FrameLayout是為了放我們SplashFragment的。我們?cè)趧傞_始加載的時(shí)候ViewStub是不會(huì)加載到我們的view樹中的谍倦,所以不占用我們的渲染時(shí)間塞赂。我們看下onCreate里面做了什么:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        splashFragment = new SplashFragment();
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.frame, splashFragment);
        transaction.commit();

        viewStub = (ViewStub)findViewById(R.id.content_viewstub);
        //1.判斷當(dāng)窗體加載完畢的時(shí)候,就把我們真正的布局加載進(jìn)來
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                // 開啟延遲加載
                mHandler.post(new Runnable() {

                    @Override
                    public void run() {
                        //將viewstub加載進(jìn)來
                        viewStub.inflate();
                    }
                } );
            }
        });
        //2.判斷當(dāng)窗體加載完畢的時(shí)候執(zhí)行,延遲一段時(shí)間是為了模擬做動(dòng)畫的耗時(shí)。
        getWindow().getDecorView().post(new Runnable() {

            @Override
            public void run() {
                // 開啟延遲加載,也可以不用延遲可以立馬執(zhí)行(我這里延遲是為了實(shí)現(xiàn)fragment里面的動(dòng)畫效果的耗時(shí))
                mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
            }
        });
        //3.同時(shí)進(jìn)行異步加載數(shù)據(jù)
    }

我們看到程序剛進(jìn)來就將fragment顯示出來昼蛀,然后在窗體加載完畢才將我們的真正布局加載進(jìn)來减途,同時(shí)模擬了做動(dòng)畫或者廣告時(shí)間,當(dāng)然實(shí)際過程中可以在動(dòng)畫完畢或者廣告結(jié)束再去移除Fragment曹洽,或者我們有跳過廣告的時(shí)候我們可以做一個(gè)回調(diào)給MainActivity讓他馬上加載主頁布局鳍置。當(dāng)然同時(shí)我們可以加載我們的網(wǎng)絡(luò)數(shù)據(jù)。接著我們來看我們移除Fragment的代碼:

  static class DelayRunnable implements Runnable{
        private WeakReference<Context> contextRef;
        private WeakReference<SplashFragment> fragmentRef;

        public DelayRunnable(Context context, SplashFragment f) {
            contextRef = new WeakReference<>(context);
            fragmentRef = new WeakReference<>(f);
        }

        @Override
        public void run() {
            if(contextRef!=null){
                SplashFragment splashFragment = fragmentRef.get();
                if(splashFragment==null){
                    return;
                }
                FragmentActivity activity = (FragmentActivity) contextRef.get();
                FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
                transaction.remove(splashFragment);
                transaction.commit();
            }
        }
    }

這里說下為什么要用弱引用來存放著兩個(gè)對(duì)象送淆,我們知道税产,我們?cè)谑褂肏andler或者Timer的時(shí)候,如果是static的類且有時(shí)間延遲的話那么就有可能內(nèi)存溢出偷崩,因?yàn)閟tatic的生存時(shí)間周期可能長于Activity辟拷,這樣會(huì)導(dǎo)致Activity關(guān)閉了,但是引用還是被static類持有阐斜,GC不能回收衫冻。所以這里用弱應(yīng)用,也就是說如果Activity關(guān)閉谒出,那么GC可以隨時(shí)回收這個(gè)Activity隅俘。最后我們看下效果:


splash.gif

我們看到我們的效果跟以前用splashActivity在視覺上沒什么區(qū)別,因?yàn)槲覀兊膯?dòng)頁面布局都比較簡單:
我們可以用指令來進(jìn)行對(duì)比:

adb shell am start -W com.lenovohit.splashoptimize/com.lenovohit.splashoptimize.MainActivity

啟動(dòng)時(shí)間

3.白屏或者黑屏問題

其實(shí)這里的白屏和黑屏都是同個(gè)問題(只是主題的原因有可能會(huì)不同)笤喳,是因?yàn)閣indow的背景为居,因?yàn)镈ecorView默認(rèn)會(huì)有一個(gè)純色的背景,由于我們的activity可能做得工作太多杀狡,或者application初始化太多東西蒙畴,我們的實(shí)際視圖還沒渲染完成,所以我們啟動(dòng)我們Activity會(huì)先顯示window的背景呜象。所以由于設(shè)備的性能原因表現(xiàn)出來的白屏或者黑屏問題也不一樣膳凝。一般來說這有兩種方法解決:
1.在style文件中加入如下代碼:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ......
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

2.給Window加上背景避免白屏

1.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 底層白色 -->
    <item android:drawable="@color/white" />

    <!-- 頂層Logo居中 -->
    <item>
        <bitmap
            android:gravity="center"
            android:src="@drawable/ic_github" />
    </item>
</layer-list>

2.
<style name="SplashTheme" parent="AppTheme">
        <item name="android:windowBackground">@drawable/bg_splash</item>
</style>

3.
<activity
            android:name=".activity.MainActivity"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme">

我們看到這上面兩種方式都是會(huì)解決白屏問題碑隆,但是我還是想說一下我個(gè)人的看法,我們知道蹬音,雖然第一種方法讓主題變成透明了上煤,但是其實(shí)android系統(tǒng)還是回去渲染,顯然這也可能影響過度繪制的祟绊,第二種方法也是類似,因?yàn)槲覀儎?dòng)態(tài)給window加上了背景哥捕,相當(dāng)于我們自動(dòng)多了一層背景牧抽,如果我們activity里面的布局也有背景的話,那么也是會(huì)影響過度繪制的遥赚。所以我覺得最好的方式就是歡迎頁盡量簡單扬舒,能迅速渲染出我們的布局,這樣的話白屏的可能性也就變小了凫佛。

總結(jié):今天講的只是一個(gè)例子讲坎,大家可以當(dāng)做一個(gè)思維的拓展,還有最后的白屏黑屏問題如果大家有自己的想法歡迎留言呀愧薛,希望我們共同進(jìn)步哈晨炕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市毫炉,隨后出現(xiàn)的幾起案子瓮栗,更是在濱河造成了極大的恐慌,老刑警劉巖瞄勾,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件费奸,死亡現(xiàn)場離奇詭異,居然都是意外死亡进陡,警方通過查閱死者的電腦和手機(jī)愿阐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾疚,“玉大人缨历,你說我怎么就攤上這事〔诼螅” “怎么了戈二?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長喳资。 經(jīng)常有香客問我觉吭,道長,這世上最難降的妖魔是什么仆邓? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任鲜滩,我火速辦了婚禮伴鳖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘徙硅。我一直安慰自己榜聂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布须肆。 她就那樣靜靜地躺著,像睡著了一般桩皿。 火紅的嫁衣襯著肌膚如雪豌汇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天泄隔,我揣著相機(jī)與錄音拒贱,去河邊找鬼。 笑死佛嬉,一個(gè)胖子當(dāng)著我的面吹牛逻澳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暖呕,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斜做,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了湾揽?” 一聲冷哼從身側(cè)響起陨享,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钝腺,沒想到半個(gè)月后抛姑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艳狐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年定硝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫目。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔬啡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镀虐,到底是詐尸還是另有隱情箱蟆,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布刮便,位于F島的核電站空猜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辈毯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一坝疼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谆沃,春花似錦钝凶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至据沈,卻和暖如春哟沫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卓舵。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國打工南用, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膀钠,地道東北人掏湾。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像肿嘲,于是被迫代替她去往敵國和親融击。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容