在 Android 開發中,應用啟動速度是一個非常重要的點,應用啟動優化也是一個非常重要的過程.對于應用啟動優化,其實核心思想就是在啟動過程中少做事情,具體實踐的時候無非就是下面幾種:
異步加載
延時加載
懶加載
不用一一去解釋,做過啟動優化的估計都使用過,本篇文章將詳細講解一下一種延時加載的實現以及其原理.
其實這種加載的實現是非常簡單的,但是其中的原理可能比較復雜,還涉及到Looper/Handler/MessageQueue/VSYNC等.以及其中碰到的一些問題,還會有一些我自己額外的思考.
一提到DelayLoad,大家可能時間想到的就是在 onCreate 里面調用 Handler.postDelayed方法, 將需要 Delay 加載的東西放到這里面去初始化, 這個也是一個比較方便的方法. Delay一段時間再去執行,這時候應用已經加載完成,界面已經顯示出來了, 不過這個方法有一個致命的問題: 延遲多久?
大家都知道,在 Android 的高端機型上,應用的啟動是非??斓?, 這時候只需要 Delay 很短的時間就可以了, 但是在低端機型上,應用的啟動就沒有那么快了,而且現在應用為了兼容舊的機型,往往需要 Delay 較長的時間,這樣帶來體驗上的差異是很明顯的.
這里先說優化方案:
首先 , 創建 Handler 和 Runnable 對象, 其中 Runnable 對象的 run方法里面去更新 UI 線程.
|
1
2
3
4
5
6
7
|
private Handler myHandler = new Handler();
private Runnable mLoadingRunnable = new Runnable() {
@Override
public void run() {
updateText(); //更新UI線程
}
};
|
在主 Activity 的 onCreate 中加入下面的代碼
|
1
2
3
4
5
6
|
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
|
其實實現的話非常簡單,我們來對比一下三種方案的效果.
為了驗證我們優化的 DelayLoad的效果,我們寫了一個簡單的app , 這個 App 中包含三張不同大小的圖片,每張圖片下面都會有一個 TextView , 來標記圖片的顯示高度和寬度. MainActivity的代碼如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
public class MainActivity extends AppCompatActivity {
private static final int DEALY_TIME = 300 ;
private ImageView imageView1;
private ImageView imageView2;
private ImageView imageView3;
private TextView textView1;
private TextView textView2;
private TextView textView3;
private Handler myHandler = new Handler();
private Runnable mLoadingRunnable = new Runnable() {
@Override
public void run() {
updateText();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView1 = (ImageView) findViewById(R.id.image1);
imageView2 = (ImageView) findViewById(R.id.image2);
imageView3 = (ImageView) findViewById(R.id.image3);
textView1 = (TextView) findViewById(R.id.text1);
textView2 = (TextView) findViewById(R.id.text2);
textView3 = (TextView) findViewById(R.id.text3);
// 種寫法:直接Post
myHandler.post(mLoadingRunnable);
// 第二種寫法:直接PostDelay 300ms.
// myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);
// 第三種寫法:優化的DelayLoad
// getWindow().getDecorView().post(new Runnable() {
// @Override
// public void run() {
// myHandler.post(mLoadingRunnable);
// }
// });
// Dump當前的MessageQueue信息.
getMainLooper().dump(new Printer() {
@Override
public void println(String x) {
Log.i("Gracker",x);
}
},"onCreate");
}
private void updateText() {
TraceCompat.beginSection("updateText");
textView1.setText("image1 : w=" + imageView1.getWidth() +
" h =" + imageView1.getHeight());
textView2.setText("image2 : w=" + imageView2.getWidth() +
" h =" + imageView2.getHeight());
textView3.setText("image3 : w=" + imageView3.getWidth() +
" h =" + imageView3.getHeight());
TraceCompat.endSection();
}
|
我們需要關注兩個點:
updateText 這個函數是什么時候被執行的?
App 啟動后,三個圖片的長寬是否可以被正確地顯示出來?
是否有 Delay Load 的效果?
updateText執行的時機?
下面是種寫法的Trace圖:

可以看到 updateText 是在 Activity 的 onCreate/onStart/onResume三個回調執行完成后才去執行的.
圖片的寬高是否正確顯示?
從圖片看一看到,寬高并沒有顯示. 這是為什么呢? 這個問題就要從Activity 的 onCreate/onStart/onResume三個回調說起了. 其實Activity 的 onCreate/onStart/onResume三個回調中,并沒有執行Measure和Layout操作, 這個是在后面的performTraversals中才執行的. 所以在這之前寬高都是0.
是否有 Delay Load 的效果?
并沒有. 因為我們知道, 應用啟動的時候,要等兩次 performTraversals 都執行完成之后才會顯示幀, 而 updateText 這個方法在個 performTraversals 執行之前就執行了. 所以 updateText 方法的執行時間是算在應用啟動的時間里面的.
第二種寫法我們Delay了300ms .我們來看一下表現.
updateText執行的時機?
可以看到,這種寫法的話,updateText是在兩個performTraversals 執行完成之后(這時候 APP 的幀才顯示出來)才去執行的, 執行完成之后又調用了一次 performTraversals 將 TextView 的內容進行更新.
圖片的寬高是否正確顯示?
從上圖可以看到,圖片的寬高是正確顯示了出來. 原因上面已經說了,measure/layout執行完成后,寬高的數據就可以獲取了.
是否有 Delay Load 的效果?
不一定,取決于 Delay的時長.
從前面的 Trace 圖上我們可以看到 , updateText 方法由于 Delay 了300ms, 所以在應用幀顯示出來170ms之后, 圖片的文字信息才進行了更新. 這個是有 Delay Load 的效果的.
但是這里只是一個簡單的TextView的更新, 如果是較大模塊的加載 , 用戶視覺上會有很明顯的 “ 空白->內容填充” 這個過程, 或者會附加”閃一下”特效…這顯然是我們不想看到的.
有人會說:可以把Delay的時間減小一點嘛,這樣就不會閃了. 話是這么說,但是由于 Android 機器的多元性(其實就是有很多高端機器,也有很多低端機器) , 在這個機子上300ms的延遲算是快,在另外一個機子上300ms算是很慢.
我們將Delay時間調整為50ms, 其Trace圖如下:
可以看到,updateText 方法在個 performTraversals 之后就執行了,所以也沒有 Delay Load 的效果(雖然寬高是正確顯示了,因為在個 performTraversals 方法中就執行了layout和measure).
經過前兩個方法 , 我們就會想, 如果能不使用Delay方法, updateText 方法能在 第二個performTraversals 方法執行完成后(即APP幀在屏幕上顯示),馬上就去執行,那么即起到了 Delay Load的作用,又可以正確顯示圖片的寬高.
第三種寫法就是這個效果:
updateText執行的時機?
可以看到這種寫法. updateText 在第二個 performTraversals 方法執行完成后馬上就執行了, 然后下一個 VSYNC 信號來了之后, TextView就更新了.
圖片的寬高是否正確顯示?
當然是正確顯示的.如圖:
是否有 Delay Load 的效果?
從 Trace 圖上看, 是有 Delay Load的效果的, 而且可以在應用幀顯示后馬上進行數據 Load , 不用考慮 Delay時間的長短.
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。