Android性能優化之啟動速度優化

来源:http://www.cnblogs.com/popfisher/archive/2017/10/24/7725515.html
-Advertisement-
Play Games

App啟動卡慢會影響一個App的卸載率和使用率。啟動速度快會給人一種輕快的感覺,減少用戶等待時間。如果一個App從點擊桌面圖標到看到主界面花了10秒,請問你能接受麽?忍耐不好的估計直接就卸載了,或者沒等打開就直接Home鍵按出去,然後殺進程了。這樣一來App卸載率提升了,使用率下降了。所以對於有大量... ...


Android性能優化之啟動速度優化

  Android app 啟動速度優化,首先談談為什麼會走到優化這一步,如果一開始創建 app 項目的時候就把這個啟動速度考慮進去,那麼肯定就不需要重新再來優化一遍了。這是因為在移動互聯網時代,大家都追求快,什麼功能都是先做出來再說,其他的可以先不考慮,先占據先機,或者驗證是否值得做。那為什麼要這麼做呢?我個人的觀點有以下幾點

  • 如果 app 不能快速開發出來,先放出去驗證一下可行性,可能連是否值得做都不知道,如果花很長時間做了一個對用戶無價值的功能,那麼還不如不做
  • 如果 app 不能快速做出來,可能被競爭對手捕獲先機,那麼可能錯失最佳商業時機
  • 如果一開始就規定不能影響啟動速度的這個目標,那麼做功能的時候就會有束縛,快不起來
  • app 初期大家都忙著開發新功能,迭代新版本,沒有時間停下來做優化
  • 同類型 app 變多,競爭對手變多,大家才開始關註啟動性能,才開始做啟動速度優化(有主動出擊也有被動優化)

一、引起性能問題的原因

  隨著項目不斷的快速迭代,往往會造成App啟動卡慢現象,因為可能在App主進程啟動階段或者在主界面啟動階段放了很多初始化其他業務的邏輯,而這些業務落地可能一開始並不需要用到。本文從作者的親身經歷給大家闡述啟動速度優化相關的點點滴滴,為啟動速度優化提供一種思路給大家參考。

二、為什麼要做啟動速度優化

  App啟動卡慢會影響一個App的卸載率和使用率。啟動速度快會給人一種輕快的感覺,減少用戶等待時間。如果一個App從點擊桌面圖標到看到主界面花了10秒,請問你能接受麽?忍耐不好的估計直接就卸載了,或者沒等打開就直接Home鍵按出去,然後殺進程了。這樣一來App卸載率提升了,使用率下降了。所以對於有大量用戶的App來說,這些性能細節是很重要的,畢竟用戶就是錢啊。

三、分析制定優化技術路線

3.1 分析啟動性能瓶頸

  在具體的優化之前,首先我們得找到需要優化的地方,怎麼找?這就要求瞭解Android App的啟動原理,我們要知道一個App從點擊桌面圖標到我們看到App的主界面整個過程中經過了哪些步驟,哪些地方是我們可以優化的地方。下圖是App啟動過程的一個大概描述。

具體的代碼流程,分析關鍵的函數耗時

  圖中onFirstDrawFinish和onWindowFocusChanged的前後順序可能會顛倒,但是時間差不大。

3.2 制定優化方向

  從上面的分析可以看出,App啟動過程中我們優化的地方包括主進程啟動流程和主界面啟動流程,主進程啟動就是Application的創建過程,主界面啟動就是MainActivity的創建過程。只需要分別對這兩個部分進行優化即可。

  1. Application中attachBaseContext最早被調用,隨後是onCreate方法,儘量在這兩個方法中不要有耗時操作。
  2. MainActivity中重點關註onCreate,onResume,onWindowFocusChange,Activity啟動完成結束標誌這裡採用沒有使用生命周期函數,而是以主界面View的第一次繪製作為啟動完成的標誌,View被第一次繪製證明View即將展示出來被我們看到。所以我們在Activity根佈局中加入一個自定義View,以它的onDraw方法第一次回調作為Activity啟動完成的標誌。

    public class FirstDrawListenView extends View {
        private boolean isFirstDrawFinish = false;
    
        private IFirstDrawListener mIFirstDrawListener;
    
        public FirstDrawListenView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (!isFirstDrawFinish) {
                isFirstDrawFinish = true;
                if (mIFirstDrawListener != null) {
                    mIFirstDrawListener.onFirstDrawFinish();
                }
            }
        }
    
        public void setFirstDrawListener(IFirstDrawListener firstDrawListener) {
            mIFirstDrawListener = firstDrawListener;
        }
    
        public interface IFirstDrawListener {
            void onFirstDrawFinish();
        }
    }

四、怎麼統計數據查看優化前後的數據對比

  通過上面的分析,我們可以統計進程啟動各個階段的耗時點,以及Activity啟動各個階段的耗時點(這個步驟需要額外在主佈局中加入一個自定義的空View,監聽它的onDraw方法的第一次回調),可以通過埋點數據收集這些數據,在優化之前可以先加入埋點數據,統計上報各個時間段的埋點,所以需要先發個版本驗證一下優化之前的情況。統計數據的機制加入之後,就可以著手優化了,一邊優化一邊對比,可以很清楚看到優化前後的對比。

五、制定優化的目標

  由於App啟動速度在不同是設備上差別很大,所以目標不太好定,但是做事情總得要有個目標吧。首先我們使用大家都熟悉的一個概念“秒開”,其次是冷啟動熱啟動分開算,再次是分出不同的機型(高端機,中端機型,低端機型),最後是需要先看看沒優化之前的啟動數據。這樣就可以定義出類似下麵的目標:

  1. 高端機型1秒內打開(比如小米5,Android6.0以上)
  2. 中端機型1.5秒內打開
  3. 低端機型2.5秒內打開

  上面是終極目標,真正優化的時候,要結合App實際數據以及團隊實際情況來定自己的優化目標。

六、優化具體步驟

  一般來說,快速優化最好的方式就是把不必要提前做的操作放到非同步線程中去做,也就是我們經常做的非同步載入。除了非同步載入,一些真正有性能影響的代碼需要做具體優化。下麵依次介紹一些具體的優化實施步驟。

6.1 封裝一個列印耗時點日誌的輔助類

  優化的時候為了快速定位耗時的代碼塊,我們需要在耗時代碼塊的前後加上日誌,統計耗時具體的時間。這個能在Debug模式下幫助我們快速分析定位到耗時的代碼塊,然後我們在針對具體的耗時代碼塊去下手,看看怎麼優化。

6.2 非同步載入一:Application中加入非同步線程

  在Application中封裝兩個方法:onSyncLoad(同步載入)和 onAsyncLoad(非同步載入,在Thread中執行),把不需要同步載入的部分全部放到onAsyncLoad方法,需要同步的方法放到onSyncLoad中去做,就這種簡單的分類就可以帶來一個很好的優化效果。

public class StartUpApplication extends Application {

    @Override
    public void onCreate() {
        // 程式創建時調用,次方法應該執行應該儘量快,否則會拖慢整個app的啟動速度
        super.onCreate();
        onSyncLoadForCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        onSyncLoad();
        onAsyncLoad();
    }

    private void onSyncLoadForCreate() {
        AppStartUpTimeLog.isColdStart = true;   // 設置為冷啟動標誌
        AppLog.log("StartUpApplication onCreate");
        AppStartUpTimeLog.logTimeDiff("App onCreate start", false, true);
        BlockingUtil.simulateBlocking(500); // 模擬阻塞100毫秒
        AppStartUpTimeLog.logTimeDiff("App onCreate end");
    }

    private void onSyncLoad() {
        AppLog.log("StartUpApplication attachBaseContext");
        AppStartUpTimeLog.markStartTime("App attachBaseContext", true);
        BlockingUtil.simulateBlocking(200); // 模擬阻塞100毫秒
        AppStartUpTimeLog.logTimeDiff("App attachBaseContext end", true);
    }

    public void onAsyncLoad() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 非同步載入邏輯
            }
        }, "ApplicationAsyncLoad").start();
    }
}

6.3 非同步載入二:MainActivity中加入非同步線程

  這一步驟與Application的優化思路一樣,也是封裝onSyncLoad和onAsyncLoad方法對現有代碼進行一個分類,但是這兩個方法的調用時機要晚一點,是在主界面首屏繪製完成的時候調用。這個步驟也需要new一個Thead,屬於額外的開銷,不過這不影響我們整體性能。

6.4 延遲載入功能:首屏繪製完成之後載入

  還有些操作必須要在UI線程做,但是不需要那麼快速就做,這裡放到首屏繪製完成之後,我們之前在主佈局中加入一個空的View來監聽它的第一次onDraw回調,我們通過介面的方式把這個事件接到我們的MainActivity中去(Activity中實現介面的onFirstDrawFinish方法)。為了讓用戶儘快看到主界面,我們就可以把一些需要在UI線程執行,但是又不需要那麼快的執行的操作放到onFirstDrawFinish中去。

6.5 動態載入佈局:主佈局文件優化

  把主界面中不需要第一次就用到的佈局全部使用動態載入的方式來處理,使用ViewStub或者直接在使用時動態addView的方式。

6.6 主佈局文件深度優化

  如果做了上面這些優化還是會發現進入主界面還是有些慢,那麼需要重點關註主佈局文件了。主佈局文件的複雜度直接影響到了Activity的載入速度,這個時候需要對主佈局文件進行深度優化了。Activity在載入佈局的時候,會對整個佈局文件進行解析,測量(measure),佈局(layout)和繪製(draw),所以設計簡單合理的佈局尤為重要。佈局的優化不做詳細介紹,網上很多文章的。幾個重要的優化如下:

  1. 減少佈局層級
  2. 減少首次載入View的數量
  3. 減少過度繪製

  如果需要看看主佈局載入具體用了多少時間,需要用自定ViewGroup作為根佈局根元素,然後監控它的onInflateFinished,onMeasure,onLayout,onDraw方法,通過我們之前寫好的列印時間日誌的輔助類,列印一些關鍵日誌,可以分析出具體的耗時的步驟,還可以定位哪個View載入耗時最長。

6.7 功能代碼深度優化

  前面的優化步驟中,我們有部分耗時操作放到了首屏繪製onFirstDrawFinish之後來做了,這裡會帶來一個體驗上的問題,雖然進入主界面變快了,但是可能進入之後短暫的時間類UI線程是阻塞的,如果有其他的UI操作可能會卡主,因為onFirstDrawFinish中掛了很多耗時的操作,需要等這些做完之後UI線程才能空閑。所以我們還需要對一些功能代碼進行優化,確保其真正用時少。另外我們非同步載入線程中的操作是有一定的安全風險的,如果有些操作很耗時,可能導致我們進入主界面需要用到數據時還沒有準備好,所以非同步載入我們要註意代碼塊的順序,如果有些非常耗時的操作考慮用單獨的線程去處理。

七、總結

  優化是一條持續之路,通過優化我們可以瞭解到影響啟動性能的因素有哪些,這樣我們平時在編碼的過程中就會多註意自己的代碼性能。本文從全局的角度去看待整個啟動性能優化,看起來好像還挺容易,但是可能實際過程中優化並不會很順利,不同的設備上可能表現不一樣,有時候可能啟動一個服務都會耗時。所以要想真正的不耗時,那就是大招:刪除它吧。

八、項目地址

模擬耗時點,列印日誌觀察生命周期函數回調情況

https://github.com/PopFisher/AppStartUpSpeedOpt


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在這之前是給路由加一個meta屬性: 註意:但是事實是登錄的時候大多數時候並不進行跳轉,所以這裡需要在login跳轉的路徑中再加一段: ...
  • 有時候特別需要,個別網頁要去掉橫向滾動條和豎向滾動條,那該怎麼去掉呢,很簡單,看代碼: 讓豎條沒有: <body style=`overflow:-Scroll;overflow-y:hidden` > </body> 讓橫條沒有: <body style=`overflow:-Scroll;ove ...
  • 前言 vue這個框架現在挺流行的,作為一個專註前端100年的代碼愛好者,學習下目前流行的框架是必須的!在網上搜索vue的項目是比較少的,在官網進行了入門學習後,沒有一個項目練習鞏固下,學了就等於沒學,所以我就決定自己寫一個項目咯。在這裡我也順便分享下我學習vue的資源。我在GitHub上發現了一個v ...
  • 一、簡述 最近跟小伙伴一起討論了一下,決定一起仿一個BiliBili的app(包括android端和iOS端),我們並沒有打算把這個項目完全做完,畢竟我們的重點是掌握一些新框架的使用,併在實戰過程中發現並彌補自身的不足。 本系列將記錄我(android端)在開發過程中的一些我覺得有必要記錄的功能實現 ...
  • 前言 在面試過程中你也許會被問到消息轉發機制。這篇文章就是對消息的轉發機制進行一個梳理。主要包括什麼是消息、靜態綁定/動態綁定、消息的傳遞和消息的轉發。接下來開發進入正題。 消息的解釋 在其他語言裡面,我們可以用一個類去調用某個方法,在OC裡面,這個方法就是消息。某個類調用一個方法就是向這個類發送一 ...
  • Android O新增的一個特性,系統會在通知欄顯示當前在後臺運行的應用,其實際是顯示啟動了前臺服務的應用,並且當前應用的Activity不在前臺。具體我們看下源碼是怎麼實現的。 1 APP調用 或`startForegroundService`啟動一個service. 和`startForegro ...
  • 通過友盟sdk集成微博、微信、qq等分享功能時,微博和qq很順利,但在做微信集成時一直不成功。主要問題還是之前在微信開放平臺申請創建移動應用時,對應用簽名沒有填寫對,走了很多彎路現總結出來,加深記憶避免後繼彎路。在這裡微信開放平臺的註冊、移動應用的創建就不做說明瞭,需要註意的是提交申請後騰訊需要一周 ...
  • 每次切換線程的操作 變換封裝操作 使用的時候 水一波 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...