Android app 啓動優化

本篇文章已授權微信公衆號AndroidChinaNet(Android開發中文站)獨家發佈

在做食生活的項目時,曾遇到也啓動頁加載很慢,白屏。不知道是什麼原因,後來換了一種思想,這個思想也用在了我的另一個項目裏,感覺還挺實用的,現在將其總結餘一下分享給大家(圖片中有一個優化方法哦!)。

(1)Instant Run造成的白屏或者黑屏現象

Instant Run是用來做什麼的?就是你點擊是用來提升開發效率的,在Android Studio 2.0及以上有了很大改善,使用instant run,在第一次運行之後,就可以快速的在真機中看見修改後的結果,不僅僅是UI可以直接顯示,還包括代碼邏輯。不用再苦苦等build了!也就是說,只有在開發階段纔會有Instant Run這個東西,在正式的產品中是完全不存在Instant Run的!

所以如果你是直接點擊按鈕啊或者是debug版本的時候出現白屏或者黑屏,最好生成一個release版的程序就不會出現這種現象了。

(2)App的啓動過程

如果想要優化我們的app,那麼我們就要深入他,理解他。下面,簡單解釋一下Activity的啓動過程:

  • 1.Application 構造方法

  • 2.attachBaseContext()

  • 3.onCreate()

  • 4.入口Activity的對象構造

  • 5.setTheme() 設置主題等信息

  • 6.入口Activity的onCreate()

  • 7.入口Activity的onStart()

  • 8.入口Activity的onResume()

  • 9.入口Activity的onAttachToWindow()

  • 10.入口Activity的onWindowFocusChanged()

根據上面的信息,我們就能得到一些額如何減少應用啓動時的耗時的信息了,是不是很一目瞭然?

針可上面的過程我們來思考,採取以下策略:

1、在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操作的初始化,一些數據預取放在異步線程中,可以採取回調的方法來去實現。

2、對於sp的初始化,因爲sp的特性在初始化時候會對數據全部讀出來存在內存中,所以這個初始化放在主線程中不合適,反而會延遲應用的啓動速度,對於這個還是需要放在異步線程中處理。

3、對於MainActivity,由於在獲取到第一幀前,需要對contentView進行測量佈局繪製操作,儘量減少佈局的層次,考慮StubView的延遲加載策略,當然在onCreate、onStart、onResume方法中避免做耗時操作。

優化應用啓動時的體驗

對於應用的啓動時間,只能是儘量的避免一些耗時的、非必要的操作在主線程中,這樣相對可以縮減一部分啓動的耗時。

另外一方面在等待第顯示的時間裏,如加入Activity的background,這個背景會在顯示第一幀前提前顯示在界面上,可以通過視覺的欺騙,以讓人看着啓動時間變快了。

方案:通過設置Style

(1)設置背景圖Theme

設置一張背景圖。 當程序啓動時,首先顯示這張背景圖,背景圖的選擇最好和主頁面色調相近,避免出現黑屏或者白屏。

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:screenOrientation">portrait</item>
    <item name="android:windowBackground">>@mipmap/splash</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowNoTitle">true</item>
    </style>

(2)設置透明Theme

通過把樣式設置爲透明,程序啓動後不會黑屏而是整個透明瞭,等到界面初始化完才一次性顯示出來

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:screenOrientation">portrait</item>
    </style>

這樣是不是感覺稍微快了一點啊?

(3)App的啓動頁的優化過程

1)、 ViewFlipper的巧妙利用。

ViewFlipper是繼承至FrameLayout的,所以它是一個Layout裏面可以放置多個View(FrameLayout和糖炒栗子一樣,是個好東西啊)。

ViewFlipper一般用來實現滑動翻頁,經常和Viewpager一起做比較。因爲項目中要用到輪播,所以仔細的看了看他們的源碼,後來發現切換操作是通過ViewAnimator提供的方法setDisplayedChild(…)實現的。

FrameLayout的佈局上下重疊,setDisplayedChild(…)來實現顯示某個View或ViewGroup,而其他的都會設置爲Gone,進源碼看一下:

 public void setDisplayedChild(int whichChild) {
        mWhichChild = whichChild;
        if (whichChild >= getChildCount()) {
            mWhichChild = 0;//從0開始,1,2....
        } else if (whichChild < 0) {
            mWhichChild = getChildCount() - 1;
        }
        boolean hasFocus = getFocusedChild() != null;
        // This will clear old focus if we had it
        showOnly(mWhichChild);
        if (hasFocus) {
            // Try to retake focus if we had it
            requestFocus(FOCUS_FORWARD);
        }
    }

 void showOnly(int childIndex) {
        final boolean animate = (!mFirstTime || mAnimateFirstTime);
        showOnly(childIndex, animate);
    } 
       void showOnly(int childIndex, boolean animate) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (i == childIndex) {
                if (animate && mInAnimation != null) {
                    child.startAnimation(mInAnimation);
                }
                child.setVisibility(View.VISIBLE);
                mFirstTime = false;
            } else {
                if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
                    child.startAnimation(mOutAnimation);
                } else if (child.getAnimation() == mInAnimation)
                    child.clearAnimation();
                child.setVisibility(View.GONE);//從這裏看出來
            }
        }
    }

曬出我的佈局,和邏輯處理(很簡單,但是很實用)

佈局可以使用ViewStub的形式進行懶加載,我這裏沒有用到。

代碼內處理:

2)、 巧用Fragment,Fragment大法就是好

Fragment必須是依存與Activity而存在的,因此Activity的生命週期會直接影響到Fragment的生命週期。官網這張圖很好的說明了兩者生命週期的關係:

帥氣的Fragment擁有自己的生命週期和接收、處理用戶的事件,這樣就不必在Activity寫一堆控件的事件處理的代碼了。更爲重要的是,你可以動態的添加、替換和移除某個Fragment(解決問題的思路)

優化方向:

把SplashActivity改成SplashFragment,應用程序的入口仍然是MainActivity,在MainActivity中先展示SplashFragment

當SplashFragment顯示完畢後再將它remove,同時在SplashFragment的2S的友好時間內進行網絡數據緩存,在窗口加載完畢後,我們加載activity_main的佈局,考慮到這個佈局有可能比較複雜,耽誤View的解析時間,採用ViewStub的形式進行懶加載。

ViewStub是一個輕量級的View,它一個看不見的,不佔佈局位置,佔用資源非常小的控件。可以爲ViewStub指定一個佈局,在Inflate佈局的時候,只有ViewStub會被初始化,然後當ViewStub被設置爲可見的時候,或是調用了ViewStub.inflate()的時候,ViewStub所向的佈局就會被Inflate和實例化,然後ViewStub的佈局屬性都會傳給它所指向的佈局。

代碼:
AnotherActivity.java

AnotherActivity佈局:

SplashFragment.java

SplashFragment佈局:

mainlayout.xml佈局:

優化思路及辦法

  • 1、避免啓動頁UI的過度繪製,減少UI重複繪製時間,打開設置中的GPU過度繪製開關,界面整體呈現淺色,特別複雜的界面,紅色區域也不應該超過全屏幕的四分之一;

  • 2、主線程中的所有SharedPreference能否在非UI線程中進行,SharedPreferences的apply函數需要注意,因爲Commit函數會阻塞IO,這個函數雖然執行很快,但是系統會有另外一個線程來負責寫操作,當apply頻率高的時候,該線程就會比較佔用CPU資源。類似的還有統計埋點等,在主線程埋點但異步線程提交,頻率高的情況也會出現這樣的問題。

  • 3、 對於首次啓動的黑屏問題,對於“黑屏”是否可以設計一個.9圖片替換掉,間接減少用戶等待時間。

+ 4、對於網絡錯誤界面,友好提示界面,使用ViewStub的方式,減少UI一次性繪製的壓力。

本文源碼gthub地址

發佈了40 篇原創文章 · 獲贊 234 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章