「性能優化1.3」延遲加載方案

「性能優化1.0」啓動分類及啓動時間的測量
「性能優化1.1」計算方法的執行時間
「性能優化1.2」異步優化
「性能優化1.3」延遲加載方案

一、延時加載

1.1、爲什麼要延遲加載?

我們在 MainActivity 中優先應該展示視圖給用戶,而一些其它的數據可以將其延遲再去初始化,例j如我們一般會在進入 MainActivity 時去檢測一下當前是否是新用戶來確定是否要顯示引導圖,或者讀取當前未讀的消息等,這些操作要求的及時性並不是那麼高,這樣就不會影響視圖的展示。

1.2、延遲加載的方案是什麼?

  • 常規實現

在 MainActivity#OnCreate 執行一個 postDelayed(Runnable r, long delayMillis)

  • 更優方案

使用 IdleHandler

1.3、常規方案

postDelayed(Runnable r, long delayMillis)這種方案的僞代碼如下:

//MainActivity.java
public void onCreate(...) {
    
    ...
    mainHandler.postDelay(new Runnable(){
        public void run() {
            //具體要做的延遲加載
            //例如讀取未讀消息
            showTipPopWindow();
            //用戶當前登錄狀態等
            checkUnReadMsg();            
        }
    },500);
    ...
}

從僞代碼中可以看出,這種方式確實可以做到數據的延遲加載,但是其缺點是很明顯的:

時間不好把控,不能確定 delay 多少時間。如果時間設置過短,那麼此時 UI 還沒渲染完畢,這勢必會阻塞到 UI 的渲染,如果過長,那麼又導致延遲時間變長了。

1.4、更優方案

1.4.1、IdleHandler 處理延遲加載

源碼位置:MessageQueue.IdleHandler

先列出來 IdleHandler 的源碼。

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

我們先來描述一下關於 Handler ,Looper,MessageQueue,Message 這幾個東西的作用。

每一個 Looper 都會綁定一個線程,,而且 Looper 會不斷的輪訓對應的 MessageQueue 去獲取需要處理的 Message,當前消息隊列沒有更多消息可以處理的話(也就是當前是空閒狀態),那麼系統就會告訴 IdleHandler,我們只需要在 queueIdle() 方法中處理我們做的任務。

當我們處理好我們的一個任務之後,queueIdle() 返回 true 表示我們要繼續使用這個 IdleHanlder,下次 MessageQueue 空閒時,還是會繼續回調 queueIdle() 方法,我們繼續在這裏處理我們未完成的任務即可,如果返回 false ,那麼系統就會移除這個 IdleHandler

1.4.2、核心思想是什麼?

分批進行延遲加載,每一次 Handler 空閒時就加載一個任務

配合這個示例圖和下面的代碼來理解如何使用 IdleHanlder 實現延遲初始化。

延遲初始化.png

  • 延遲加載的兩個任務
//顯示引導框UI
public void showTipPopWindow(){
    //一頓操作異步操作,然後更新 UI 視圖
}
//加載消息紅點UI
public void checkUnReadMsg(){
    //一頓操作異步操作,然後更新 UI 視圖
}
  • 將任務的執行放到 Runnable 中,保存到一個集合列表中 tasks
final List<Runnable> tasks = new ArrayList<>();
tasks.add(new Runnable() {
    @Override
    public void run() {
        showTipPopWindow();
    }
});
tasks.add(new Runnable() {
    @Override
    public void run() {
        checkUnReadMsg();
    }
});
  • 定義一個 IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        Log.d(TAG, "queueIdle");
        if (!tasks.isEmpty()) {
            //取出一個任務
            Runnable task = tasks.get(0);
            //執行這個任務
            task.run();
            //執行完畢,移除這個人
            tasks.remove(task);
        }
        //如果任務列表爲空,就不要這個 IdleHandler 了,
        return !tasks.isEmpty();
    }
};
//往主線程的 MessageQueue 中添加我們自定義的 IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
  • 在 MainActitity 中第一個 View 繪製時調用

我們上一節中已經瞭解瞭如何獲取應用的啓動時間,我們是通過打點的方式來計算這個時間差值,開始打點時間爲 Application#attachBaseContext,而結束打點的位置就是第一個 View的 onPreDrawListener 回調時。具體不清楚的讀者可以回到上一小節看一下具體過程。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView textView = findViewById(R.id.textview);
    textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
                MessageQueue.IdleHandler idleHandler = new   MessageQueue.IdleHandler() {
                    @Override
                    public boolean queueIdle() {
                        Log.d(TAG, "queueIdle");
                            if (!tasks.isEmpty()) {
                                //取出一個任務
                                    Runnable task = tasks.get(0);
                                //執行這個任務
                                task.run();
                                //執行完畢,移除這個人
                                tasks.remove(task);
                            }
                            //如果任務列表爲空,就不要這個 IdleHandler 了,
                        return !tasks.isEmpty();
                     }
            };
//往主線程的 MessageQueue 中添加我們自定義的 IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
            return true;
        }
    });
}

好了,經過上面幾步的操作,我們就已經將需要延遲加載的任務放到 IdleHanlder 中去處理,這種方案解決了上面通過 Handler.postDelay()不能確定具體需要 delay 的時間問題,並且因爲任務的及時性要求不是很高,那麼就可以等主線程比較空閒時再來執行,一舉兩得。

二、總結

本節中,我們瞭解爲什麼要進行任務的異步加載,以及實現了兩種異步加載方案,並對比了兩種方案的優劣性,最後我們得到一個更加優的方案就是利用系統提供的 IdleHandler 來處理我們的延遲任務。在本節中最重要的是需要理解好延遲加載的核心思想就是在主線程``空閒時每次只加載一個任務

記錄於 2019年3月19日

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章