1. 程式人生 > >Android 效能優化(一) —— 啟動優化提升60%

Android 效能優化(一) —— 啟動優化提升60%

應用啟動速度

一個應用App的啟動速度能夠影響使用者的首次體驗,啟動速度較慢(感官上)的應用可能導致使用者再次開啟App的意圖下降,或者解除安裝放棄該應用程式。

本文將從兩個方向優化應用的啟動速度 :

  • 視覺體驗優化
  • 程式碼邏輯優化

# 視覺優化

應用程式啟動有三種狀態,每種狀態都會影響應用程式對使用者可見所需的時間:冷啟動,熱啟動和溫啟動。

在冷啟動時,應用程式從頭開始。在其他狀態下,系統需要將正在執行的應用程式從後臺執行到前臺。我們建議您始終根據冷啟動的假設進行優化。這樣做也可以改善熱啟動和溫啟動的效能。

在冷啟動開始時,系統有三個任務。這些任務是:

  1. 載入並啟動應用程式。
  2. 啟動後立即顯示應用程式空白的啟動視窗
  3. 建立應用程式程序。

一旦系統建立應用程式程序,應用程式程序就會負責下一階段。這些階段是:

  1. 建立app物件.
  2. 啟動主執行緒(main thread).
  3. 建立應用入口的Activity物件.
  4. 填充載入佈局Views
  5. 在螢幕上執行View的繪製過程.measure -> layout -> draw

應用程式程序完成第一次繪製後,系統程序會交換當前顯示的背景視窗,將其替換為主活動。此時,使用者可以開始使用該應用程式。

這裡寫圖片描述


因為App應用程序的建立過程是由手機的軟硬體決定的,所以我們只能在這個建立過程中視覺優化。

啟動主題優化

冷啟動階段 :

  1. 載入並啟動應用程式。
  2. 啟動後立即顯示應用程式空白的啟動視窗
  3. 建立應用程式程序。

所謂的主題優化,就是應用程式在冷啟動的時候(1~2階段),設定啟動視窗的主題。

因為現在 App 應用啟動都會先進入一個閃屏頁(LaunchActivity) 來展示應用資訊。


1.預設情況

如果我們對App沒有做處理(設定了預設主題),並且在 Application 初始化了其它第三方的服務(假設需要載入2000ms),那麼冷啟動過程就會如下圖 :

這裡寫圖片描述

系統預設會在啟動應用程式的時候 啟動空白視窗 ,直到 App 應用程式的入口 Activity 建立成功,檢視繪製完畢。( 大概是onWindowFocusChanged方法回撥的時候 )


2.透明主題優化

為了解決啟動視窗白屏問題,許多開發者使用透明主題來解決這個問題,但是治標不治本。

雖然解決了上面這個問題,但是仍然有些不足。

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

這裡寫圖片描述
(無白屏,不過從點選到App仍然存在視覺延遲~)


3.設定閃屏圖片主題

為了更順滑無縫銜接我們的閃屏頁,可以在啟動 Activity 的 Theme中設定閃屏頁圖片,這樣啟動視窗的圖片就會是閃屏頁圖片,而不是白屏。

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/lunch</item>  //閃屏頁圖片
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">false</item><!--顯示虛擬按鍵,並騰出空間-->
    </style>

這裡寫圖片描述

這樣設定的話,就會在冷啟動的時候,展示閃屏頁的圖片,等App程序初始化載入入口 Activity (也是閃屏頁) 就可以無縫銜接。

其實這種方式並沒有真正的加速應用程序的啟動速度,而只是通過使用者視覺效果帶來的優化體驗。


程式碼優化

當然上面使用設定主題的方式優化使用者體驗效果治標不治本,關鍵還在於對程式碼的優化。

首先我們可以統計一下應用冷啟動的時間。

冷啟動耗時統計

adb 命令統計

參考如何計算 App 的啟動時間
adb命令 : adb shell am start -S -W 包名/啟動類的全限定名 , -S 表示重啟當前應用
更多adb命令

C:\Android\Demo>adb shell am start -S -W com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity
Stopping: com.example.moneyqian.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity }
Status: ok
Activity: com.example.moneyqian.demo/.MainActivity
ThisTime: 2247
TotalTime: 2247
WaitTime: 2278
Complete

  • ThisTime : 最後一個 Activity 的啟動耗時(例如從 LaunchActivity - >MainActivity「adb命令輸入的Activity」 , 只統計 MainActivity 的啟動耗時)
  • TotalTime : 啟動一連串的 Activity 總耗時.(有幾個Activity 就統計幾個)
  • WaitTime : 應用程序的建立過程 + TotalTime .

這裡寫圖片描述

  • 在第①個時間段內,AMS 建立 ActivityRecord 記錄塊和選擇合理的 Task、將當前Resume 的 Activity 進行 pause.
  • 在第②個時間段內,啟動程序、呼叫無介面 Activity 的 onCreate() 等、 pause/finish 無介面的 Activity.
  • 在第③個時間段內,呼叫有介面 Activity 的 onCreate、onResume.
//ActivityRecord

    private void reportLaunchTimeLocked(final long curTime) {
		``````
        final long thisTime = curTime - displayStartTime;
        final long totalTime = stack.mLaunchStartTime != 0 ? (curTime - stack.mLaunchStartTime) : thisTime;
    }

最後總結一下 : 如果需要統計從點選桌面圖示到 Activity 啟動完畢,可以用WaitTime作為標準,但是系統的啟動時間優化不了,所以優化冷啟動我們只要在意 ThisTime 即可。


### 系統日誌統計

另外也可以根據系統日誌來統計啟動耗時,在Android Studio中查詢已用時間,必須在logcat檢視中禁用過濾器(No Filters)。因為這個是系統的日誌輸出,而不是應用程式的。你也可以檢視其它應用程式的啟動耗時。

過濾displayed輸出的啟動日誌.
這裡寫圖片描述


## 程式碼優化 根據上面啟動時間的輸出統計,我們就可以先記錄優化前的冷啟動耗時,然後再對比優化之後的啟動時間。

Application 優化

Application 作為 應用程式的整個初始化配置入口,時常擔負著它不應該有的負擔~

有很多第三方元件(包括App應用本身)都在 Application 中搶佔先機,完成初始化操作。

但是在 Application 中完成繁重的初始化操作和複雜的邏輯就會影響到應用的啟動效能

通常,有機會優化這些工作以實現效能改進,這些常見問題包括:

  1. 複雜繁瑣的佈局初始化
  2. 阻塞主執行緒 UI 繪製的操作,如 I/O 讀寫或者是網路訪問.
  3. Bitmap 大圖片或者 VectorDrawable載入
  4. 其它佔用主執行緒的操作

我們可以根據這些元件的輕重緩急之分,對初始化做一下分類 :

  1. 必要的元件一定要在主執行緒中立即初始化(入口 Activity 可能立即會用到)
  2. 元件一定要在主執行緒中初始化,但是可以延遲初始化。
  3. 元件可以在子執行緒中初始化。

放在子執行緒的元件初始化建議延遲初始化 ,這樣就可以瞭解是否會對專案造成影響!

所以對於上面的分析,我們可以在專案中 Application 的載入元件進行如下優化 :

  • 將Bugly,x5核心初始化,SP的讀寫,友盟等元件放到子執行緒中初始化。(子執行緒初始化不能影響到元件的使用)
        new Thread(new Runnable() {
            @Override
            public void run() {
                //設定執行緒的優先順序,不與主執行緒搶資源
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
				//子執行緒初始化第三方元件
				Thread.sleep(5000);//建議延遲初始化,可以發現是否影響其它功能,或者是崩潰!
            }
        }).start();

  • 將需要在主執行緒中初始化但是可以不用立即完成的動作延遲載入(原本是想在入口 Activity 中進行此項操作,不過元件的初始化放在 Application 中統一管理為妙.)
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
				//延遲初始化元件
            }
        }, 3000);

閃屏頁業務優化

最後還剩下那些為數不多的元件在主執行緒初始化動作,例如埋點,點選流,資料庫初始化等,不過這些消耗的時間可以在其它地方相抵。

需求背景 : 應用App通常會設定一個固定的閃屏頁展示時間,例如2000ms,所以我們可以根據使用者手機的執行速度,對展示時間做出調整,但是總時間仍然為 2000ms。

閃屏頁政展示總時間 = 元件初始化時間 + 剩餘展示時間

也就是2000ms的總時間,元件初始化了800ms,那麼就再展示1200ms即可。

這裡寫圖片描述

這裡寫圖片描述
檢視大圖

冷啟動的過程中系統會初始化應用程式程序,建立Application等任務,這時候會展示一個 啟動視窗 Starting Window,上面分析了過,如果沒有優化主題的話,那麼就是白屏。


如果要了解更多啟動過程原始碼,可以看我的部落格 : Launcher 啟動 Activity 的工作過程

分析原始碼後,我們可以知道 Application 初始化後會呼叫 attachBaseContext() 方法,再呼叫 Application 的 onCreate(),再到入口 Activity的建立和執行 onCreate() 方法。所以我們就可以在 Application 中記錄啟動時間。

//Application

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
		SPUtil.putLong("application_attach_time", System.currentTimeMillis());//記錄Application初始化時間
    }

有了啟動時間,我們得知道入口的 Acitivty 顯示給使用者的時間(View繪製完畢),在部落格( View的工作流程)中瞭解到,在onWindowFocusChanged()的回撥時機中表示可以獲取使用者的觸控時間和View的流程繪製完畢,所以我們可以在這個方法裡記錄顯示時間。

//入口Activity

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
  
          long appAttachTime = SPUtil.getLong("application_attach_time");
          long diffTime = System.currentTimeMillis() - appAttachTime;//從application到入口Acitity的時間
 
		 //所以閃屏頁展示的時間為 2000ms - diffTime.
    }

所以我們就可以動態的設定應用閃屏的顯示時間,儘量讓每一部手機展示的時間一致,這樣就不會讓手機配置較低的使用者感覺漫長難熬的閃屏頁時間(例如初始化了2000ms,又要展示2000ms的閃屏頁時間.),優化使用者體驗。


廣告頁優化

閃屏頁過後就要展示金主爸爸們的廣告頁了。

因為專案中廣告頁圖片有可能是大圖,APng動態圖片,所以需要將這些圖片下載到本地檔案,下載完成後再顯示,這個過程往往會遇到以下兩個問題 :

  • 廣告頁的下載,由於這個是一個非同步過程,所以往往不知道載入到頁面的合適時機。
  • 廣告頁的儲存,因為儲存是 I/O 流操作,很有可能被使用者中斷,下次拿到破損的圖片。

因為不清楚使用者的網路環境,有些使用者下載廣告頁可能需要一段時間,這時候又不可能無限的等候。所以針對這個問題我們可以開啟 IntentService 用來下載廣告頁圖片。

  • 在入口 Acitivity 中開啟 IntentService 來下載廣告頁。 或者是其它非同步下載操作。
  • 在廣告頁圖片 檔案流完全寫入後 記錄圖片大小,或者記錄一個標識。

在下次的廣告頁載入中可以判斷是否已經下載好了廣告頁圖片以及圖片是否完整,否則刪除並且再次下載圖片。

另外因為在閃屏頁中仍然有 剩餘展示時間,所以在這個時間段裡如果使用者已經下載好了圖片並且圖片完整,就可以顯示廣告頁。否則進入主 Activity , 因為 IntentService 仍然在後臺繼續默默的下載並儲存圖片~


# 優化效果

優化前 : (小米6)

Displayed LaunchActivity MainActivity
+2s526ms +1s583ms
+2s603ms +1s533ms
+2s372ms +1s556ms

優化後 : (小米6)

Displayed LaunchActivity MainActivity
+995ms +1s191ms
+911ms +1s101ms
+903ms +1s187ms

通過手上 小米6,小米 mix2s,還有小米 2s的啟動測試,發現優化後App冷啟動的啟動速度均提升了 60% !!! ,並且我們可以再看一下手機冷啟動時候的記憶體情況 :

優化前 : 伴隨著大量物件的建立回收,15s內系統GC 5次。記憶體使用波瀾盪漾。
這裡寫圖片描述
檢視大圖

優化後 : 趨於平穩上升狀態建立物件,15s內系統GC 2次。(後期業務拓展加入新功能,所以程式碼量增加。)之後總記憶體使用平緩下降。

這裡寫圖片描述
檢視大圖

  • Other :應用使用的系統不確定如何分類的記憶體。
  • Code :應用用於處理程式碼和資源(如 dex 位元組碼、已優化或已編譯的 dex 碼、.so 庫和字型)的記憶體。
  • Stack : 應用中的原生堆疊和 Java 堆疊使用的記憶體。 這通常與您的應用執行多少執行緒有關。
  • Graphics :圖形緩衝區佇列向螢幕顯示畫素(包括 GL 表面、GL 紋理等等)所使用的記憶體。 (請注意,這是與 CPU 共享的記憶體,不是 GPU 專用記憶體。)
  • Native :從 C 或 C++ 程式碼分配的物件記憶體。即使應用中不使用 C++,也可能會看到此處使用的一些原生記憶體,因為 Android 框架使用原生記憶體代表處理各種任務,如處理影象資源和其他圖形時,即使編寫的程式碼採用 Java 或 Kotlin 語言。
  • Java :從 Java 或 Kotlin 程式碼分配的物件記憶體。
  • Allocated :應用分配的 Java/Kotlin 物件數。 它沒有計入 C 或 C++ 中分配的物件。


# 啟動視窗

優化完我們的程式碼後,分析一下啟動視窗的原始碼。基於 android-25 (7.1.1)

啟動視窗是由 WindowManagerService 統一管理的 Window視窗,一般作為冷啟動頁入口 Activity 的預覽視窗,啟動視窗ActivityManagerService 來決定是否顯示的,並不是每一個 Activity 的啟動和跳轉都會顯示這個視窗。

WindowManagerService 通過視窗管理策略類 PhoneWindowManager 來建立啟動視窗

這裡寫圖片描述
檢視大圖

直奔主題,在 ActivityStarterstartActivityUnchecked()方法中,呼叫了ActivityStack(Activity 狀態管理)的startActivityLocked()方法。此時Activity 還在啟動過程中,視窗並未顯示。

這裡寫圖片描述
檢視大圖

先上一張流程圖,展示了啟動視窗的顯示過程。

首先,由 Activity 狀態管理者ActivityStack開始執行顯示啟動視窗的流程。

//ActivityStack


 final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
            ActivityOptions options) {

		``````
        if (!isHomeStack() || numActivities() > 0) {//HOME_STACK表示Launcher桌面所在的Stack
	        // 1.首先當前啟動棧不在Launcher的桌面棧裡,並且當前系統已經有啟用過Activity
	        
            // We want to show the starting preview window if we are
            // switching to a new task, or the next activity's process is
            // not currently running.

            boolean doShow = true;
            if (newTask) {
	            // 2.要將該Activity元件放在一個新的任務棧中啟動
	            
                // Even though this activity is starting fresh, we still need
                // to reset it to make sure we apply affinities to move any
                // existing activities from other tasks in to it.
                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                    resetTaskIfNeededLocked(r, r);
                    doShow = topRunningNonDelayedActivityLocked(null) == r;
                }
            } else if (options != null && options.getAnimationType()
                    == ActivityOptions.ANIM_SCENE_TRANSITION) {
                doShow = false;
            }
            if (r.mLaunchTaskBehind) {
	            //3. 熱啟動,不需要啟動視窗
	            
                // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
                // tell WindowManager that r is visible even though it is at the back of the stack.
                mWindowManager.setAppVisibility(r.appToken, true);
                ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
            } else if (SHOW_APP_STARTING_PREVIEW && doShow) {

				``````
				//4. 顯示啟動視窗
                r.showStartingWindow(prev, showStartingIcon);
            }
        } else {
	        // 當前啟動的是桌面Launcher (開機啟動)
            // If this is the first activity, don't do any fancy animations,
            // because there is nothing for it to animate on top of.
			``````
        }

    }


  1. 首先判斷當前要啟動的 Activity 不在Launcher棧裡
  2. 要啟動的 Activity 是否處於新的 Task 裡,並且沒有轉場動畫
  3. 如果是熱/溫啟動則不需要啟動視窗,直接設定App的Visibility

接下來呼叫`ActivityRecord`的`showStartingWindow()`方法來設定啟動視窗並且改變當前視窗的狀態。

如果 App 的應用程序建立完成,並且入口 Activity 準備就緒,就可以根據 mStartingWindowState 來判斷是否需要關閉啟動視窗。

//ActivityRecord


    void showStartingWindow(ActivityRecord prev, boolean createIfNeeded) {
        final CompatibilityInfo compatInfo =
                service.compatibilityInfoForPackageLocked(info.applicationInfo);
        final boolean shown = service.mWindowManager.setAppStartingWindow(
                appToken, packageName, theme, compatInfo, nonLocalizedLabel, labelRes, icon,
                logo, windowFlags, prev != null ? prev.appToken : null, createIfNeeded);
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }

WindowManagerService 會對當前 Activity 的token和主題進行判斷。

//WindowManagerService

 @Override
    public boolean setAppStartingWindow(IBinder token, String pkg,
            int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo,
            int windowFlags, IBinder transferFrom, boolean createIfNeeded) {

        synchronized(mWindowMap) {

			//1. 啟動視窗也是需要token的
            AppWindowToken wtoken = findAppWindowToken(token);
            
			//2. 如果已經設定過啟動視窗了,不繼續處理
            if (wtoken.startingData != null) {
                return false;
            }

            // If this is a translucent window, then don't
            // show a starting window -- the current effect (a full-screen
            // opaque starting window that fades away to the real contents
            // when it is ready) does not work for this.
            if (theme != 0) {
                AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                        com.android.internal.R.styleable.Window, mCurrentUserId);
                        
               //3. 一堆程式碼對主題判斷,不符合要求則不顯示啟動視窗(如透明主題)
                if (windowIsTranslucent) {
                    return false;
                }
                if (windowIsFloating || windowDisableStarting) {
                    return false;
                }
				``````
            }

			//4. 建立StartingData,並且通過Handler傳送訊息

            wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,
                    labelRes, icon, logo, windowFlags);
            Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
            // Note: we really want to do sendMessageAtFrontOfQueue() because we
            // want to process the message ASAP, before any other queued
            // messages.

            mH.sendMessageAtFrontOfQueue(m);
        }
        return true;
    }
  1. 啟動視窗也需要和 Activity 擁有同樣令牌 token ,雖然啟動視窗可能是白屏,或者一張圖片,但是仍然需要走繪製流程已經通過WMS顯示視窗。
  2. StartingData物件用來表示啟動視窗的相關資料,描述了啟動視窗的檢視資訊。
  3. 如果當前 Activity 是透明主題或者是浮動視窗等,那麼就不需要啟動視窗來過渡啟動過程,所以在上面視覺優化中的設定透明主題就沒有顯示白色的啟動視窗。
  4. 顯示啟動視窗也是一件心急火燎的事情,WMS的內部類H (handler) 處於主執行緒處理訊息,所以需要將當前Message放置佇列頭部。

PS : 為什麼需要通過 Handler 傳送訊息 ?
你可以在各大服務Service中見到 Handler 的身影,並且它們可能都有一個很吊的命名 H ,因為可能呼叫這個服務的某個執行方法處於子執行緒中,所以 Handler 的職責就是將它們切換到主執行緒中,並且也可以統一管理排程。更多 Handler 瞭解可以查閱文章 : 你真的瞭解Handler?

//WindowManagerService --> H 

        public void handleMessage(Message msg) {
            switch (msg.what) {

                case ADD_STARTING: {
                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
                    final StartingData sd = wtoken.startingData;

                    View view = null;
                    try {
                        final Configuration overrideConfig = wtoken != null && wtoken.mTask != null
                                ? wtoken.mTask.mOverrideConfig : null;
                        view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,
                            sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,
                            sd.windowFlags, overrideConfig);
                    } catch (Exception e) {
                        Slog.w(TAG_WM, "Exception when adding starting window", e);
                    }

                    ``````
      
                } break;
   }     

在當前的handleMessage方法中,會處於主執行緒處理訊息,拿到token和StartingData啟動資料後,便通過mPolicy.addStartingWindow()方法將啟動視窗新增到WIndow上。

mPolicyPhoneWindowManager,控制著啟動視窗的新增刪除和修改。

在PhoneWindowManager對啟動視窗進行配置,獲取當前Activity設定的主題和資源資訊,設定到啟動視窗中。

//PhoneWindowManager


@Override
    public View addStartingWindow(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
            int icon, int logo, int windowFlags, Configuration overrideConfig) {
            
         //可以通過SHOW_STARTING_ANIMATIONS設定不顯示啟動視窗
        if (!SHOW_STARTING_ANIMATIONS) {
            return null;
        }
        WindowManager wm = null;
        View view = null;

        try {
	        //1. 獲取上下文Context和主題theme以及標題
            Context context = mContext;
            if (theme != context.getThemeResId() || labelRes != 0) {
                try {
                    context = context.createPackageContext(packageName, 0);
                    context.setTheme(theme);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }

			//2. 建立PhoneWindow 用來顯示
            final PhoneWindow win = new PhoneWindow(context);
            win.setIsStartingWindow(true);

			//3. 設定當前視窗type和flag,原始碼註釋中描述的很清晰...
            win.setType(
                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);

            // Force the window flags: this is a fake window, so it is not really
            // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
            // flag because we do know that the next window will take input
            // focus, so we want to get the IME window up on top of us right away.
            win.setFlags(
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

            win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT);

			``````
			
            view = win.getDecorView();

			//4. WindowManager的繪製流程
            wm.addView(view, params);

            return view.getParent() != null ? view : null;
        } catch (WindowManager.BadTokenException e) {
            // ignore
        } catch (RuntimeException e) {
            // don't crash if something else bad happens, for example a
            // failure loading resources because we are loading from an app
            // on external storage that has been unmounted.
            Log.w(TAG, appToken + " failed creating starting window", e);
        }
        return null;
    }

  1. 如果theme和labelRes的值不為0,那麼說明開發者指定了啟動視窗的主題和標題,那麼就需要從當前要啟動的Activity中獲取這些資訊,並設定到啟動視窗中。
  2. 和其它視窗一樣,啟動視窗也需要通過PhoneWindow來設定佈局資訊DecorView所以在上面視覺優化中的設定閃屏圖片主題的啟動視窗顯示的就是圖片內容。
  3. 啟動視窗和普通視窗的不同之處在於它是 fake window ,不需要觸控事件
  4. 最後通過WindowManger走View的繪製流程(measure-layout-draw)將啟動視窗顯示出來,最後會請求WindowManagerService為啟動視窗新增一個WindowState物件,真正的將啟動視窗顯示給使用者,並且可以對啟動視窗進行管理。

更多WindowManager的addView流程可以查閱 : View的工作流程


# 總結 至此應用程式的啟動優化和啟動視窗的原始碼分析已經總結完畢,在專案的開發中要知其然而之所以然 ,並且對原始碼的分析有助於我們瞭解原理和解決問題的根源。

參考

相關推薦

Android 效能優化() —— 啟動優化提升60%

應用啟動速度 一個應用App的啟動速度能夠影響使用者的首次體驗,啟動速度較慢(感官上)的應用可能導致使用者再次開啟App的意圖下降,或者解除安裝放棄該應用程式。 本文將從兩個方向優化應用的啟動速度 : 視覺體驗優化 程式碼邏輯優化 # 視覺優化

Android效能優化 -- 應用啟動優化啟動頁設計

上篇部落格我們學習了應用啟動優化的一些優化思路,經過這些優化後,如果還不能達到你的要求,我們一般會做個啟動頁。因為啟動頁一般View數量比較少,業務邏輯比較簡單,因此啟動比較快。一、設計思路常規啟動頁設計思路通常點選桌面就會執行Application中的邏輯,然後會跳入啟動頁

Android效能優化 網路優化

在上一篇部落格中,我和大家一起探討了在Android中對SQLite資料庫的操作優化細節。還沒有看的點選這裡: 今天我們就網路方面的優化和大家分享。 1.連線伺服器 不用域名,直接使用IP 這種方式可以省去中間解析DNS的過程,首次域名解析一般需要幾百毫秒,直接使用IP

效能優化_app啟動優化方案學習

  目錄 資料參考 :  玉剛說 一 app啟動方式 冷啟動(Cold start) 溫啟動(Warm start) 熱啟動(Hot start) 二 從點選APP圖示到主頁顯示出現需要經過的步驟 三 啟動速度優化 如何對啟動時間進行

Android UI優化— App啟動優化

黑白屏產生的原因和解決辦法 黑白屏產生的原因 1、還沒載入到佈局檔案,就已經顯示了window視窗背景 2、黑屏白屏就是window視窗背景 容易產生黑白屏的地方 1、Activity的onCreate()中 @Override pro

Android面試系列冷啟動優化

什麼是冷啟動? 冷啟動的定義 冷啟動就是在啟動應用前,系統中沒有該應用的任何程序資訊時候的啟動(第一次開啟應用,或者殺死了這個app程序後的啟動) 冷啟動/熱啟動的區別 熱啟動定義:使用者使用app返回鍵退出應用,然後馬上又重新啟動應用。 區別一:ap

Android效能全面分析與優化方案研究

效能優化是一個持續的過程,要多種手段,一點一點優化,一般是優化影響比較大頭的,再逐步優化小頭的,

Android 系統性能優化(30)---Android效能全面分析與優化方案研究

5.1、渲染問題先來看看造成應用UI卡頓的常見原因都有哪些?1、人為在UI執行緒中做輕微耗時操作,導致UI執行緒卡頓;2、佈局Layout過於複雜,無法在16ms內完成渲染;3、同一時間動畫執行的次數過多,導致CPU或GPU負載過重;4、View過度繪製,導致某些畫素在同一幀時間內被繪製多次,從而使CPU或G

Android效能分析——Activity啟動速度分析工具

Android頁面基本是由大量Activity承載,流暢的頁面啟動速度代表著高使用者體驗 眾所周知,除卻底層的IPC呼叫等,就應用開發者層面而言,Activity啟動速度主要由三部分構成:佈局(layout),繪製(draw),業務(biz),針對此處,筆者開

Android效能優化筆記()——啟動優化

參考文章:  https://time.geekbang.org/column/article/73651 https://mp.weixin.qq.com/s/eaArt5Udc4WZ3NoH5RlEkQ https://juejin.im/post/5874bff0128fe1006b4

Android效能優化)App啟動原理分析及啟動時間優化

一、啟動原理解析 Android是基於Linux核心的,當手機啟動,載入完Linux核心後,會由Linux系統的init祖先程序fork出Zygote程序,所有的Android應用程式程序以及系統服務程序都是這個Zygote的子程序(由它fork出來的)。其中最重要的一個就

【朝花夕拾】Android效能優化篇之()序言及JVM篇

序言 筆者從事Anroid開發有些年頭了,深知掌握Anroid效能優化方面的知識的必要性,這是一個程式設計師必須修煉的內功。在面試中,它是面試官的摯愛,在工作中,它是程式碼質量的攔路虎,其重要性可見一斑。在團隊中,效能優化的工作又往往由經驗豐富的老師傅來完成,可見要做好效能優化,絕不是一件容易的事情。    

android專案效能優化啟動時間

一般來說,判定一個android專案效能優劣,我們有以下幾個指標: 啟動時間 apk大小 UI渲染 穩定性 記憶體佔用 電量消耗 接下來,讓我們就這幾個指標展開來詳述各自究竟應該怎樣去優化。 啟動時間 一般來說,應用啟動時間分為三種 首次啟動

Android效能優化乾貨分享;你的 APP 為何啟動那麼慢?

App啟動方式 冷啟動(Cold start) 冷啟動是指APP在手機啟動後第一次執行,或者APP程序被kill掉後在再次啟動。 可見冷啟動的必要條件是該APP程序不存在,這就意味著系統需要建立程序,APP需要初始化。在這三種啟動方式中,冷啟動耗時最長,對於冷啟動的優化也是最具挑戰的。因此本

Android 效能優化啟動速度優化

前言 本篇文章對app啟動速度進行優化。先了解 Android 效能優化 基本概念應用是如何啟動的,會對此有幫助。 1. 應用的啟動模式 冷啟動 Cold start 當啟動應用時,後臺沒有該應用的程序,這時系統會重新建立一個新的程序分配給該應用,

Android效能優化)記憶體洩露優化(靜態變數、單例模式、屬性動畫)

記憶體洩露優化分為兩個方面,一方面是在開發過程中避免寫出有記憶體洩露的程式碼,另一方面是通過一些分析工具比如 MAT來找出潛在的記憶體洩露繼而解決。 一、靜態變數導致記憶體洩露。一般情況下靜態變數引用

Android效能優化之 App啟動原理分析及速度和時間優化

應用的啟動速度緩慢這是很多開發者都遇到的一個問題,比如啟動緩慢導致的黑屏,白屏問題,大部分的答案都是做一個透明的主題,或者是做一個Splash介面,但是這並沒有從根本上解決這個問題。那麼如何從根本上解決這個問題或者做到一定程度的緩解? 一、應用的啟動方式 1、冷啟動:

Android應用啟動優化:種DelayLoad的實現和原理(下篇)(轉載)

我們使用第三種方法來實現延遲載入。不過上一篇寫的比較簡單,只是講解了如何去實現,這一篇就來講一下為何要這麼做,以及這麼做後面的原理。 其中會涉及到一些 Android 中的比較重要的類,以及 Activity 生命週期中比較重要的幾個函式。 其實這個其中的原理比較簡單,不過要弄清楚其實現的過程,還是一件蠻好

Android 效能優化——啟動時間優化指南

請保持淡定,分析程式碼,記住:效能很重要。 毫無疑問,應用的啟動速度越快越好。 本文可以幫助你優化應用的啟動時間:首先描述應用啟動過程的內部機制;然後討論如何分析啟動效能;最後,列舉了一些常見的影響啟動時間的問題,並就如何解決這些問題給出一些提示。

Android啟動優化工具() Method Tracking

前言 當我覺得app在啟動或者某處執行比較慢時,我需要開始我的優化工作了。大多數情況下,是因為在主執行緒執行了耗時的操作。 Android Studio 自帶的 Method Tracking 可以很直觀的看到某個時間段內哪個方法花了多少時間,真的屢試不