1. 程式人生 > >Android效能優化(一)App啟動原理分析及啟動時間優化

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

一、啟動原理解析

Android是基於Linux核心的,當手機啟動,載入完Linux核心後,會由Linux系統的init祖先程序fork出Zygote程序,所有的Android應用程式程序以及系統服務程序都是這個Zygote的子程序(由它fork出來的)。其中最重要的一個就是SystemServer,在ZygoteInit類的main方法中,會呼叫startSystemServer方法開啟系統裡面重要的服務,包括ActivityManagerService(Activity管理器,簡稱AMS,可以理解為一個服務程序,負責Activity的生命週期管理)、PackageManagerService(包管理器)、WindowManagerService(視窗管理器)、PowerManagerService(電量管理器)等等,這個過程中還會建立系統上下文。

public final class SystemServer {

    //zygote的主入口
    public static void main(String[] args) {
        new SystemServer().run();
    }

    public SystemServer() {
        // Check for factory test mode.
        mFactoryTestMode = FactoryTest.getMode();
    }
    
    private void run() {
        
        ...ignore some code...
        
        //載入本地系統服務庫,並進行初始化 
        System.loadLibrary("android_servers");
        nativeInit();
        
        // 建立系統上下文
        createSystemContext();
        
        //初始化SystemServiceManager物件,下面的系統服務開啟都需要呼叫SystemServiceManager.startService(Class<T>),這個方法通過反射來啟動對應的服務
        mSystemServiceManager = new SystemServiceManager(mSystemContext);
        
        //開啟服務
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        }
       
        ...ignore some code...
    
    }

    //初始化系統上下文物件mSystemContext,並設定預設的主題,mSystemContext實際上是一個ContextImpl物件。呼叫ActivityThread.systemMain()的時候,會呼叫ActivityThread.attach(true),而在attach()裡面,則建立了Application物件,並呼叫了Application.onCreate()。
    private void createSystemContext() {
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
        mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }

    //在這裡開啟了幾個核心的服務,因為這些服務之間相互依賴,所以都放在了這個方法裡面。
    private void startBootstrapServices() {
        
        ...ignore some code...
        
        //初始化ActivityManagerService
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        
        //初始化PowerManagerService,因為其他服務需要依賴這個Service,因此需要儘快的初始化
        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);

        // 現在電源管理已經開啟,ActivityManagerService負責電源管理功能
        mActivityManagerService.initPowerManagement();

        // 初始化DisplayManagerService
        mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);
    
    //初始化PackageManagerService
    mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
       mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
    
    ...ignore some code...
    
    }

}

注意一個很重要的地方

 private void createSystemContext() {
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
                  mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }
這裡幹了三件事,一個是建立ActivityThread,這個ActivityThread就是我們常說的“主執行緒”,也就是所謂的UI執行緒,APP的主入口。ActivityThread隨後會建立一個mainLooper來開啟訊息迴圈,這也就是為什麼在"主執行緒"中我們使用Handler不需要手動建立Looper的原因。第二件事是獲得了系統的上下文,第三件事是設定了預設的主題。如果想要啟動新的應用,ActivityManagerService會通過socket程序間通訊(IPC)機制來通知Zygote程序fork出新的程序。

二、當我們點選了一個APP圖示時發生了什麼

系統第一個啟動的APP是Lancher,也就是我們手機的主介面,繼承自Activity,實現了點選事件、觸控、長按等介面,在android原始碼Lancher.java中,我們可以看到onclick方法

public void onClick(View v) {
    
          ...ignore some code...
            
         Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
        //開始開啟Activity
            boolean success = startActivitySafely(v, intent, tag);

            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            //如果點選的是圖示資料夾,就開啟資料夾
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
        ...ignore some code...
        }
    }

可以看出我們點選了主介面上的應用圖示後呼叫的就是startActivitySafely這個方法,繼續深入進去,然後再往下走,我們發現呼叫的是startActivity方法,最後會呼叫Instrumentation.execStartActivity(),Instrumentation這個類就是完成對Application和Activity初始化和生命週期的工具類,然後Instrumentation會通過ActivityManagerService的遠端介面向AMS發訊息,讓他啟動一個Activity。 也就是說呼叫startActivity(Intent)以後, 會通過Binder IPC機制, 最終呼叫到ActivityManagerService。AMS會通過socket通道傳遞引數給Zygote程序。Zygote孵化自身, 並呼叫ZygoteInit.main()方法來例項化ActivityThread物件並最終返回新程序的pid。ActivityThread隨後依次呼叫Looper.prepareLoop()和Looper.loop()來開啟訊息迴圈。在ActivityThread會建立並繫結Application,這個時候才會realStartActivity(),並且AMS會將生成的Activity加到ActivityTask的棧頂,並通知ActivityThread暫停當前Activity(暫停Lancher,進入我們自己的APP)。


三、Application在何處初始化

ActivityThread.main()中,有一句話是thread.attach(false),在這個attach方法中,有一句比較重要的地方
mgr.attachApplication(mAppThread)
這個就會通過Binder呼叫到AMS裡面對應的方法,繼續研究原始碼,在handleBindApplication中,完成了Application的建立
在makeApplication方法中,最後呼叫的是instrumentation.callApplicationOnCreate(app);
這個方法裡面的onCreate就是呼叫了我們Application的OnCreate方法。

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                initializeJavaContextClassLoader();
            }
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

    //傳進來的是null,所以這裡不會執行,onCreate在上一層執行
        if (instrumentation != null) {
            try {
                instrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
               
            }
        }
        ...ignore some code... 
              
       }

        return app;
    }
上面的三點,用一張圖來改概括,就是

四、什麼地方我們可以進行優化

首先需要明確的是,在Application的onCreate之前,這個時間都是無法進行優化的,因為這部分是系統替我們完成的,開發者無能為力。所以我們能做的,就是從Application的onCreate方法開始,到lauyout_main.xml第一次佈局繪製完成之前,來進行啟動時間的優化。

五、冷啟動與熱啟動的區別

1冷啟動:當啟動應用時,後臺沒有該應用的程序,這時系統會重新建立一個新的程序分配給該應用,這個啟動方式就是冷啟動。冷啟動因為系統會重新建立一個新的程序分配給它,所以會先建立和初始化 Application 類,再建立和初始化 MainActivity 類,最後顯示在介面上。


2  熱啟動:當啟動應用時,後臺已有該應用的程序(例:按back鍵、home鍵,應用雖然會退出,但是該應用的程序是依然會保留在後臺,可進入任務列表檢視),所以在已有程序的情況下,這種啟動會從已有的程序中來啟動應用,這個方式叫熱啟動。熱啟動因為會從已有的程序中來啟動,所以熱啟動就不會走 Application 這步了,而是直接走 MainActivity,所以熱啟動的過程不必建立和初始化 Application,因為一個應用從新程序的建立到程序的銷燬,Application 只會初始化一次。

六、時間定義

由於冷啟動過程中,系統和APP的許多工作都要重新開始,所以一般而言這種啟動方式是最慢且最具有挑戰性的。除了建立和初始化Application和MainActivity之外,冷啟動還要載入主題樣式Theme,inflate佈局,setContentView ,測量、佈局、繪製以後顯示,我們才看到了螢幕的第一幀。 1.DisplayTime API19以後,Android在系統Log中增加了Display的Log資訊,在Android Studio中執行我們的App後,我們可以在Logcat中過濾ActivityManager以及Display這兩個關鍵字,可以看到
這個DisplayTime的Log資訊在activity 視窗完成所有的啟動事件之後,第一次繪製的時候輸出。這個時間包括了從Activity啟動到Layout全部顯示的過程。這基本上就是你需要優化的主要時間。它不包含使用者點選app圖示然後系統開始準備啟動activity的時間,因為作為一個開發者你無法影響這個時間,所以沒有必要去測量它。
2.reportFullyDrawn 系統日誌中的Display Time只是佈局的顯示時間,並不包括資料的載入,因為很多App在載入時會使用懶載入模式,即資料拉取後,再重新整理預設的UI。所以,系統給我們定義了一個類似的『自定義上報時間』——reportFullyDrawn。
如下圖:
3.三個time 在cmd中輸入adb shell am start -W [包名]/[全類名],即通過adb啟動我們的Activity或Service,控制檯也會輸出我們的啟動時間 此法獲取的啟動時間非常精準,可精確到毫秒。那麼這三個Thistime,TotalTime,WaitTime分別代表什麼呢?
  • ThisTime: 最後一個啟動的Activity的啟動耗時
  • 自己的所有Activity的啟動耗時
  • WaitTime: ActivityManagerService啟動App的Activity時的總時間(包括當前Activity的onPause()和自己Activity的啟動)
這三個時間不是很好理解,我們可以把整個過程分解

1.上一個Activity的onPause()——2.系統呼叫AMS耗時——3.第一個Activity(也許是閃屏頁)啟動耗時——4.第一個Activity的onPause()耗時——5.第二個Activity啟動耗時

那麼,ThisTime表示5(最後一個Activity的啟動耗時)。TotalTime表示3.4.5總共的耗時(如果啟動時只有一個Activity,那麼ThisTime與TotalTime就是一樣的)。WaitTime則表示所有的操作耗時,即1.2.3.4.5所有的耗時。
如果還是沒有理解,可以檢視framework層ActivityRecord原始碼,其中有這幾個time的計算方法, 最關鍵的幾行程式碼
 public void reportFullyDrawnLocked() {
        final long curTime = SystemClock.uptimeMillis();
        if (displayStartTime != 0) {
            reportLaunchTimeLocked(curTime);
        }
        final ActivityStack stack = task.stack;
        if (fullyDrawnStartTime != 0 && stack != null) {
            final long thisTime = curTime - fullyDrawnStartTime;
            final long totalTime = stack.mFullyDrawnStartTime != 0
                    ? (curTime - stack.mFullyDrawnStartTime) : thisTime;
}
邏輯如下圖

七、為什麼啟動時會出現短暫黑屏或白屏的現象

系統程序在建立Application的過程中會產生一個BackgroudWindow,等到App完成了第一次繪製,系統程序才會用MainActivity的介面替換掉原來的BackgroudWindow,見下圖 也就是說當用戶點選你的app那一刻到系統呼叫Activity.onCreate()之間的這個時間段內,WindowManager會先載入app主題樣式中的windowBackground做為app的預覽元素,然後再真正去載入activity的layout佈局。
很顯然,如果你的application或activity啟動的過程太慢,導致系統的BackgroundWindow沒有及時被替換,就會出現啟動時白屏或黑屏的情況(取決於你的主題是Dark還是Light)。

八、冷啟動優化

1.主題替換 我們在style中自定義一個樣式Lancher,在其中放一張背景圖片,或是廣告圖片之類的
<style name="AppTheme.Launcher">
        <item name="android:windowBackground">@drawable/bg</item>
    </style>
把這個樣式設定給啟動的Activity
<activity
            android:name=".activity.SplashActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.Launcher"
            >
然後在Activity的onCreate方法,把Activity設定回原來的主題
@Override
    protected void onCreate(Bundle savedInstanceState) {
        //替換為原來的主題,在onCreate之前呼叫
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
    }
這樣在啟動時就通過給使用者看一張圖片或是廣告來防止黑白屏的尷尬。
還一種方式,就是把windowBackground屬性設為null,這樣在啟動時,backgroundWindow的背景就會變成透明的,給人的感覺就是點了應用圖示以後,延遲了一會兒然後載入第一個activity的介面。
<style name="AppTheme.Launcher">
        <item name="android:windowBackground">@null</item>
    </style>
2.優化Application和MainActivity 上面所說的改變主題實際上是一種偽優化,因為它實質上並沒有真正減少App啟動的時間。 Application是程式的主入口,特別是很多第三方SDK都會需要在Application的onCreate裡面做很多初始化操作,不得不說,各種第三方SDK,都特別喜歡這個『兵家必爭之地』,再加上自己的一些庫的初始化,會讓整個Application不堪重負。優化的方法,無非是通過以下幾個方面:
  • 延遲初始化
  • 後臺任務
  • 介面預載入
在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操作的初始化,一些資料預取放在非同步執行緒中。
資料庫,IO操作,密集網路請求不要放在Application的構造方法中,能使用工作執行緒的儘量使用工作執行緒,不要在Application的onCreate中建立執行緒池,因為那樣會有比較大的開銷,可以考慮延後再建立。 第三方SDK如果主執行緒中沒有立即使用,可以考慮延遲幾秒再初始化,總之一句話,儘早讓使用者看到應用的介面,其他操作都可以先讓路。 對於MainActivity,由於在獲取到第一幀前,需要對contentView進行測量佈局繪製操作,儘量減少佈局的層次,考慮StubView的延遲載入策略,當然在onCreate、onStart、onResume方法中避免做耗時操作。
對於sharedPreferences的初始化,因為sharedPreferences的特性在初始化時候會對資料全部讀出來存在記憶體中,所以這個初始化放在主執行緒中不合適,反而會延遲應用的啟動速度,對於這個還是需要放在非同步執行緒中處理。 
例子
new Thread(){
            @Override
            public void run() {
                initNim();
                initImagePicker();
                initOkHttp();
            }
        }.start();

九、優化啟動時間的一個很好用的工具 TraceView

TraceView 是 Android SDK 中內建的一個工具,它可以載入 trace 檔案,用圖形的形式展示程式碼的執行時間、次數及呼叫棧,便於我們分析。
用法很簡單,在你想要測試花費時間的程式碼之前,呼叫Debug.startMethodTracing(filename),系統會生產 trace 檔案,並且產生追蹤資料,在結束處呼叫程式碼Debug.stopMethodTracing()時,會將追蹤資料寫入到 trace 檔案中。以下程式碼在sd卡中生成trace檔案
File file = new File(Environment.getExternalStorageDirectory(), "app7");
       Log.i(TAG, "onCreate: " + file.getAbsolutePath());
       Debug.startMethodTracing(file.getAbsolutePath());
        //對全域性屬性賦值
        mContext = getApplicationContext();
        mMainThread = Thread.currentThread();
        mMainThreadId = android.os.Process.myTid();
        mMainLooper = getMainLooper();
        mHandler = new Handler();
        initNim();
        initImagePicker();
        initOkHttp();
        Debug.stopMethodTracing();
如果你用的是模擬器,就可以使用下面的控制檯命令將trace檔案拉出到桌面上
cd Desktop
adb pull /storage/sdcard/app7.trace
然後將這個trace檔案拖入android studio 就可以了,可以看到這樣的介面


具體各個部分的資訊如下     從上半部分可以清晰的看出方法之間的呼叫關係和執行時間,從下半部分可以具體看出那幾個方法耗時較長
有了這樣方便的工具,啟動優化時對症下藥就很方便了 參考部落格

相關推薦

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

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

Android面試題——Activity的生命週期和啟動模式

引言 這份面試題系列文章旨在查漏補缺,通過常見的面試題發現自己在Android基礎知識上的遺漏和欠缺,驗證所學是否紮實。 這是系列的第一章,後面我會根據安卓知識模組分類併網羅分析各種常見面試題。 面試題: Activity的生命週期 答

專案練習APP熱點標籤分析

專案練習(一)APP熱點標籤分析 1、專案背景 通過找到熱度標籤,贈標籤熱度,以提高相應APP的下載量和使用量。 2、需求分析 (1)爬取資料: 6個欄位,分別為(appId,app名稱, 一級分類,二級分類,三級分類,Tags描述資訊),但並不一定完全規整,視實際情況可能左

Android網路程式設計HTTP協議原理

 1.HTTP簡介 HTTP是一個屬於應用層的面向物件的協議,由於其簡捷、快速的方式,適用於分散式超媒體資訊系統。它於1990年提出,經過幾年的使用與發展,得到不斷地完善和擴充套件。 HTTP協議的主要特點 支援C/S(客戶/伺服器)模式。簡單快速:客戶向伺服器請求服

SpringMVC學習———— springmvc框架原理分析和簡單入門程式

一、什麼是springmvc?       我們知道三層架構的思想,並且如果你知道ssh的話,就會更加透徹的理解這個思想,struts2在web層,spring在中間控制,hibernate在dao層與資料庫打交道,而前面剛寫的mybatis跟hibernate一樣,與資料庫打交道在dao層的另一個框架,而

Android之測量APP效能概覽

如果應用程式響應緩慢、顯示不穩定的動畫、凍結、崩潰或消耗大量電力,則認為其效能很差。為了避免這些效能問題,使用本頁中列出的分析工具來識別應用程式在哪裡低效使用資源,例如CPU、記憶體、圖形、網路和裝置電池。 Note: While profiling an app, you shoul

android記錄筆記記憶體洩露和各種效能優化

該篇筆記來自於平時學習時,對各種學習資源的整合,如有冒犯敬請諒解,整理的不好,還望指出錯誤,主要用於查詢與記錄 一、記憶體洩露 針對記憶體洩露我認為要知道下面三點: 第一:要弄清楚記憶體洩露與記憶體溢位的區別 第二:要弄清楚常規的記憶體分析方法,重點掌握Leakcanary的使用

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

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

Android繪製優化繪製效能分析

前言 一個優秀的應用不僅僅是要有吸引人的功能和互動,同時在效能上也有很高的要求。執行Android系統的手機,雖然配置在不斷的提升,但仍舊無法和PC相比,無法做到PC那樣擁有超大的記憶體以及高效能的CPU,因此在開發Android應用程式時也不可能無限制的使用

Android內存優化DVM和ART原理初探

java虛擬機 劃分 cimage beef 靜態 由於 jar blank 查找 要學習Android的內存優化,首先要了解Java虛擬機,此前我用了多篇文章來介紹Java虛擬機的知識,就是為了這個系列做鋪墊。在Android開發中我們接觸的是與Java虛擬機類似的Dal

Android繪制優化繪制性能分析

pro -i tco public 繼續 但是 們的 sched mda 前言 一個優秀的應用不僅僅是要有吸引人的功能和交互,同時在性能上也有很高的要求。運行Android系統的手機,雖然配置在不斷的提升,但仍舊無法和PC相比,無法做到PC那樣擁有超大的內存以及高性能的CP

Android內存優化Dalvik虛擬機和ART虛擬機對比

參考 -a 會有 font google 都是 http -s 轉換成 1.概述  Android4.4以上開始使用ART虛擬機,在此之前我們一直使用的Dalvik虛擬機,那麽為什麽Google突然換了Android運行的虛擬機呢?答案只有一個:ART虛擬機更優秀。 2.D

【MySQL資料庫】效能優化之索引優化

一、Mysql效能優化之影響效能的因素 1.商業需求的影響 不合理的需求造成的資源投入產出,這裡就用一個看上去很簡單的功能分析。需求:一個論壇帖子的總量統計,附加要求:實時更新。從功能上看來是非常容易實現的,執行一條select count(*)from表名就可以得到結果,但是如果我們採

淺談前端效能優化

前端效能優化中,減少HTTP請求可以提高頁面的響應速度。 瀏覽器在第一次訪問頁面時向伺服器請求資源,並快取起來,下次再訪問時會判斷在快取中是否已有該資源且有沒有更新過,如果已有該資源且未更新過,則直接從瀏覽器快取中讀取。原理:通過HTTP 請求頭中的 If-Modified-Since(If-No-Matc

oracle程式設計300例-效能優化

1、在SELECT語句中避免使用“*” 2、儘可能減小記錄行數 3、使用rowid高效刪除重複記錄 例項: delete from stu s where s.rowid>(select min(t.rowid) from stu t where t.stu=t.stu / 4、使用t

Android進階: Launcher啟動過程

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 今天就將 Launcher 系統啟動過程 總結一下(基於Android 8.0 系統)。 文章

Web前端效能優化

1. 靜態資源的壓縮與合併 我們在開發的時候會習慣縮排和寫註釋,方便我們在日常的維護,但將程式碼上傳至服務端後,我們完全可以把那些空格、製表符、換行符進行壓縮,以此減少請求資源的大小;同樣的,我們在服務端所引用的第三方庫進行合併,能減少 HTTP 的請求數量 將

MySQL之查詢效能優化

為什麼查詢速度會慢 通常來說,查詢的生命週期大致可以按照順序來看:從客戶端,到伺服器,然後在伺服器上進行解析,生成執行計劃,執行,並返回結果給客戶端。其中“執行”可以認為是整個生命週期中最重要的階段,這其中包括了大量為了檢索資料到儲存引擎的呼叫以及呼叫後的資料處理,包括排序、分組等。 在完成這些任務的時候

Android 開發藝術探索》讀書筆記——Activity 的生命週期和啟動模式

Activity 作為 Android 四大元件之首,它作為和使用者互動的介面,在開發中使用得可謂極其頻繁,所以弄清楚 Activity 的生命週期和啟動方式是非常重要的,要牢記。 1 Activity 的生命週期全面分析 1.1 典型情況下的生命週期分析 onCrea