今天看到一篇講關於延遲加載機制的文章,在時間調度上堪稱完美,因此轉載過來記錄。html
原文連接:http://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.htmlandroid
下篇連接:http://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87.htmlapp
深度講解了,下篇中深度講解了第三種延遲加載機制的邏輯。異步
強烈推薦給各位。ide
在 Android 開發中,應用啓動速度是一個很是重要的點,應用啓動優化也是一個很是重要的過程.對於應用啓動優化,其實核心思想就是在啓動過程當中少作事情,具體實踐的時候無非就是下面幾種:函數
不用一一去解釋,作過啓動優化的估計都使用過,本篇文章將詳細講解一下一種延時加載的實現以及其原理.
其實這種加載的實現是很是簡單的,可是其中的原理可能比較複雜,還涉及到Looper/Handler/MessageQueue/VSYNC等.以及其中碰到的一些問題,還會有一些我本身額外的思考.oop
一提到DelayLoad,你們可能第一時間想到的就是在 onCreate 裏面調用 Handler.postDelayed方法, 將須要 Delay 加載的東西放到這裏面去初始化, 這個也是一個比較方便的方法. Delay一段時間再去執行,這時候應用已經加載完成,界面已經顯示出來了, 不過這個方法有一個致命的問題: 延遲多久?
你們都知道,在 Android 的高端機型上,應用的啓動是很是快的 , 這時候只須要 Delay 很短的時間就能夠了, 可是在低端機型上,應用的啓動就沒有那麼快了,並且如今應用爲了兼容舊的機型,每每須要 Delay 較長的時間,這樣帶來體驗上的差別是很明顯的.post
這裏先說優化方案:優化
首先 , 建立 Handler 和 Runnable 對象, 其中 Runnable 對象的 run方法裏面去更新 UI 線程.spa
1 2 3 4 5 6 7 8 |
private Handler myHandler = new Handler(); private Runnable mLoadingRunnable = new Runnable() { @Override public void run() { updateText(); //更新UI線程 } }; |
在主 Activity 的 onCreate 中加入下面的代碼
1 2 3 4 5 6 7 |
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 65 66 |
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執行的時機?
下面是第一種寫法的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是在兩個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 的實現,從代碼層面來看實際上是很是簡單的.其帶來的效果也是很讚的. 可是實現以後咱們還須要思考一下,爲什麼這麼作就能夠實現這種功能呢?很顯然要回答這個問題,咱們須要知道更底層的一些東西.這個還涉及到 Handler/Message/MessageQueue/Looper/VSYNC/ViewRootImpl等知識. 往大里說應該還涉及到AMS/WMS等.因爲涉及到的東西比較多,我就不在這一篇裏面闡述了, 下一篇文章將會從從原理上講解一下爲什麼優化的 Delay Load 會起做用.