Android App秒開的奧秘

什么是秒開

Android App秒開,狹義的講是指你的App的Activity從啟動到顯示所花費的時間在1秒以內,廣義的講是指這個過程所花費的時間越少越好。這個時間越短,你的App給用戶的感覺就是響應越快,使用越流暢,用戶體驗更好。秒開是Android App的一個很重要的性能指標。需要我們持續(xù)的給予關注和優(yōu)化。

如何優(yōu)化秒開

Google提供了很多性能優(yōu)化的建議和官方的工具,網上也有非常多的關于Android App性能優(yōu)化的文章和工具,可以幫助你解決大部分卡頓的問題。但是現(xiàn)實卻可能是即使你付出了很多精力去做優(yōu)化,你的App還是在啟動新Activity的時候花費過多的時間。特別是隨著需求的不斷增長,你的App會變得復雜而龐大,要做優(yōu)化首先要定位需要優(yōu)化的點,而這會變得愈發(fā)困難。同時大型App在啟動新Activity的時間花費過多情況出現(xiàn)的可能性反而會越來越大。

在眾多的優(yōu)化建議中,有一條比較基本的原則是盡量避免在主線程(或者說UI線程)中進行耗時操作。例如文件讀寫操作、網絡請求、大量計算、循環(huán)等等。直觀的理解是因為啟動新Activity需要在主線程執(zhí)行很多代碼,例如onCreate()等生命周期的回調。如果此時有耗時操作的代碼在主線程被執(zhí)行,到新Activity展示出來所需要的時間就會延長。要優(yōu)化秒開,首先要能監(jiān)測主線程的運行狀態(tài),那么問題來了,主線程到底是怎樣在運行呢?你的代碼又是什么時候,如何在主線程被執(zhí)行的呢?

深入主線程

要了解主線程的工作過程,首先要了解Android的消息機制。

消息機制

先看一下現(xiàn)實生活中的一個例子,雖然現(xiàn)在都是移動支付了,但相信大家都去銀行取過錢。當你到達銀行的時候,如果你是第一個,那恭喜你,你可以馬上到柜員那里辦理你的業(yè)務;如果你前面還有人,那就比較慘了,你需要排隊,得等到你前面的人都辦完業(yè)務才會輪到你;更可怕的是如果你前面有幾位需要辦理的業(yè)務花費的時間比較長,那你需要等更長的時間;后面來的人則會按順序排在你身后,和你一樣不耐煩的琢磨什么時候才能輪到自己。

抽象一下,消息機制其實和這個例子十分類似。每個人都看做是個消息,什么時候到的銀行是不確定的。柜員可以看做一個消息處理器,他幫你辦業(yè)務就相當于在處理你的消息;而人們按照先后順序排起來的隊伍可以看做是個消息隊列。所以這個過程可以抽象為有個消息處理器,他有個消息隊列,隨機來到的消息按照一定順序排列在這個隊列里,消息處理器不停的從隊列頭部獲取消息然后處理之,周而復始的循環(huán)重復這個過程。如下圖所示:


消息機制

那么Android是怎樣怎樣實現(xiàn)這個消息機制的呢?

Android的消息機制

消息機制首先得有消息,在Android中就是Message。怎樣能確定一個消息呢?消息要有來源或者目標,也就是target;消息要表明自己要做什么,也就是what或者callback;消息要表明自己希望在什么時候執(zhí)行,也就是when。有了這幾個要素,基本上這個消息就是個完備的消息了,可以被加入到消息隊列中了。Android中的消息隊列是MessageQueue。消息處理循環(huán)是Looper。Looper是個死循環(huán),不停的從MessageQueue中獲取消息然后處理之,具體的執(zhí)行是在Handler里面進行的。另外消息加入消息隊列也需要Handler來操作。Message,MessageQueue,Looper,Handler組合在一起,就構成了整個Android的消息機制。

Android的主線程就運行著這樣一個消息機制。

Android的主線程

主線程是在ActivityThread中創(chuàng)建的,可以看到在main函數(shù)中

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        Looper.loop();
    }

主線程實現(xiàn)了一個消息機制。所以Android的主線程就是個消息處理的循環(huán)。它所做的工作就是在不停的從消息隊列獲取消息,處理消息,周而復始。你的App所有的在UI上的操作,例如點擊事件的處理、頁面動畫、顯示更新頁面、View繪制、啟動新Activity等操作都是在給主線程發(fā)消息,主線程然后挨個處理這些消息。

主線程如何影響秒開

我們了解了主線程的工作機制后,就要看看主線程中的消息處理是如何影響Activity秒開的。
當我們要啟動一個新的Activity的時候,從調用startActivity開始到新Activity顯示出來,Android系統(tǒng)會發(fā)送一系列的消息給主線程。這一系列的消息處理所花費的總時間會影響頁面的秒開,如果執(zhí)行時間過長,用戶就會有響應非常慢的感覺。此外,除了Android系統(tǒng)會給主線程發(fā)消息,App自身也會給主線程發(fā)消息,如果在啟動新Activity的過程中,這些App自己的消息正好插入這一系列的Android系統(tǒng)消息中,那也會導致總的處理時間延長,造成不能秒開。


秒開示意

上圖代表了啟動新Activity的主線程的三種情況,每個矩形代表主線程處理一個消息所花的時間,越寬代表處理的時間越長。綠色填充的代表這是一個Android系統(tǒng)發(fā)過來的消息;藍色填充的代表這是一個App自己發(fā)過來的消息。最下方的向右箭頭代表時間,起點是startActivity被調用的時刻。

  • 第一種狀況代表正常的情形,主線程中只有和startActivity相關的系統(tǒng)消息被處理,而且處理每個消息所花費的時間都在合理范圍內。所以這個頁面可以滿足秒開。
  • 第二種情況代表一個異常的情形,雖然主線程處理的消息都是系統(tǒng)消息,但是某一個或某幾個消息的處理時間超出了合理值,導致頁面不能秒開。
  • 第三種情況代表另一種異常的情形,在系統(tǒng)消息中混入了App自己的消息,主線程不僅要處理系統(tǒng)消息,還要處理App自己的消息,結果就是總的啟動時間要額外加上App消息的處理時間,導致頁面不能秒開。
    實際情況中還有可能會出現(xiàn)既有系統(tǒng)消息處理時間過長同時也混有App自己的消息的情形。

秒開優(yōu)化

了解了影響秒開的因素之后,我們只要有辦法能監(jiān)測主線程中每個消息處理時間,我們就能定位到造成頁面卡慢的原因,然后再做優(yōu)化。
幸好Android工程師為我們在Looper中預留了打log的位置。

public static void loop() {
        final Looper me = myLooper();
        ...
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
           ...
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
           ...
            msg.recycleUnchecked();
        }
    }

public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

可見在消息被處理的開始和處理結束之后都會打印log。
你只需要在代碼中調用Looper.setMessageLogging()設置一下就好。

Looper.getMainLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String s) {   
                    Log.v("debug", s);
                }
            });

編譯運行你的程序,你會在logcat輸出看到類似這樣的log:


Message logging

每行 “>>>>> Dispatching to”開頭的log代表一個消息即將開始被處理;緊接著下一行“<<<<< Finished to”開頭的log代表這一消息處理完畢。通過這些log你可以知道所有被主線程處理的消息,并可以根據開始結束的時間差知道每個消息消耗的時間。有了這些信息你可以找到導致你的app卡慢的消息,然后進一步去debug問題。

在你啟動一個新的Activity的時候你可以觀測這樣的log輸出,看看里面有沒有處理時間比較長的消息,或者看看里面有沒有App自己的消息被處理,如果有的話,這些都是需要優(yōu)化的點。

然而直接看log的缺點是這樣的log會比較多,而且并不容易定位啟動Activity的開始和結束時間點,另外每個消息處理的時間也要自己計算,并不是十分直觀。

StallBuster

為了方便的進行秒開優(yōu)化,我做了個工具叫StallBuster來協(xié)助定位Activity秒開失敗的原因。

集成StallBuster非常簡單,只需要兩步就可以了

  1. 添加對StallBuster的依賴
dependencies {
    compile 'com.github.zhangjianli:stallbuster:1.1'
}
  1. 在你的App的Application中添加以下代碼
public class YourApplication extends Application {
    @Override
    public void onCreate() {
        StallBuster.getInstance().init(this);
        super.onCreate();
    }
}

這樣就可以了,編譯運行你的App。在你的App中打開新的Activity,StallBuster會發(fā)出一個Notification。告訴你剛啟動這個Activity花了多少毫秒


notification

點擊這個Notification就會打開StallBuster的歷史記錄頁面。


records

這個頁面按照時間順序列出了你的App啟動每個Activity的歷史記錄。每條記錄最左邊是啟動所花費的時間。綠色代表所費時間符合秒開要求;紅色代表時間太長。需要關注。右邊是這條記錄對應的Activity名稱。點擊某條記錄就會進入詳情頁。
詳情頁

在詳情頁里你可以看到啟動這個Activity的過程中主線程處理過的消息。上方的復選框可以過濾執(zhí)行時間比較短的消息,方便定位問題。

對于每條記錄,首先顯示的是這條消息開始被處理的時間戳。然后是cost字段,表示處理這條消息花了多長時間。正常情況下是字體是黑色的;如果處理時間過長,則顯示為紅色。表明這里可能是我們需要優(yōu)化的地方。

接下來是target字段,對應的是這個消息是被哪個Handler處理的。Android系統(tǒng)的Handler會顯示為黑色;App自己的Handler會顯示為紅色,表明這個消息不應該在啟動Activity的時候出現(xiàn),這里也可能是需要優(yōu)化的地方。

例如上圖中第一條記錄,.MainActivity$StallHandler處理這個消息花費了142ms。這會使啟動SubActivity的時間至少延長了142ms。而這個Handler是App自己的Handler。我們需要調試代碼使得在啟動這個Activity的時候確保不會有來自這個Handler的消息,142ms的時間就會節(jié)省下來。

最后一個字段是message或者callback。對應的是Message中的what或callback。有了這些信息我們就能很方便的定位主線程中影響秒開的消息,進而優(yōu)化我們的App。

StallBuster就給大家介紹到這里,希望StallBuster能幫到你。如果大家有任何建議或者問題請給我留言。

總結

App秒開是是一項非常重要的性能指標。秒開的優(yōu)化是個復雜的工作,有很多因素會影響App秒開。其中比較重要的一個因素是啟動Activity的時候主線程的消息處理情況。在啟動Activity過程中需要避免消息處理時間過長,也要避免在此期間有App自己的消息需要處理。優(yōu)化的關鍵點是要定位到主線程中的耗時操作,我們可以通過打印分析主線程的消息處理log來定位,但這種方式并不是很直觀方便。這時可以使用StallBuster幫助你快速定位秒開問題點,讓秒開優(yōu)化變的更加簡單。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容