Android AsyncLayoutInflater 源碼解析

先回顧下以前三篇文章,這個系列的文章從前日後順序看最佳:android

咱們已經學習了 Layout 相關的方方面面,本文就來學習下一個相對新穎的知識點:AsyncLayoutInflater;說它相對新穎是由於它是Android 24.1.0版本以後纔有的。web

一、AsyncLayoutInflater 簡介

Helper class for inflating layouts asynchronously. To use, construct an instance of AsyncLayoutInflater on the UI thread and call inflate(int, ViewGroup, OnInflateFinishedListener). The AsyncLayoutInflater.OnInflateFinishedListener will be invoked on the UI thread when the inflate request has completed.This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed.緩存

這是從 AsyncLayoutInflater 說明文檔截出來的一段話,大意是:AsyncLayoutInflater 是來幫助作異步加載 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法運行結束以後 OnInflateFinishedListener 會在主線程回調返回 View;這樣作旨在 UI 的懶加載或者對用戶操做的高響應。bash

簡單的說咱們知道默認狀況下 setContentView 函數是在 UI 線程執行的,其中有一系列的耗時動做:Xml的解析、View的反射建立等過程一樣是在UI線程執行的,AsyncLayoutInflater 就是來幫咱們把這些過程以異步的方式執行,保持UI線程的高響應。微信

AsyncLayoutInflater 比較簡單,只有一個構造函數及普通調用函數:inflate(int resid, ViewGroup parent, AsyncLayoutInflater.OnInflateFinishedListener callback),使用也很是方便。app

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(AsyncLayoutActivity.this)
                .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                    @Override
                    public void onInflateFinished(View view, int resid, ViewGroup parent) {
                        setContentView(view);
                    }
                });
        // 別的操做
    }
複製代碼

二、AsyncLayoutInflater 構造函數

AsyncLayoutInflater 的源碼很是簡單,總共只有170行代碼,咱們就從調用的入口來看下。異步

public AsyncLayoutInflater(@NonNull Context context) {
        mInflater = new BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
        mInflateThread = InflateThread.getInstance();
    }
複製代碼

能夠看到作了三件事情:async

  • 建立 BasicInflater;
  • 建立 Handler;
  • 獲取 InflateThread 對象;
  1. BasicInflater 繼承自 LayoutInflater,只是覆寫了 onCreateView:優先加載這三個前綴的 Layout,而後才按照默認的流程去加載,由於大多數狀況下咱們 Layout 中使用的View都在這三個 package 下
private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
            "android.widget.",
            "android.webkit.",
            "android.app."
        };
        @Override
        protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
            for (String prefix : sClassPrefixList) {
                try {
                    View view = createView(name, prefix, attrs);
                    if (view != null) {
                        return view;
                    }
                } catch (ClassNotFoundException e) {
                }
            }
            return super.onCreateView(name, attrs);
        }
    }
複製代碼
  1. 建立 Handler 和它普通的做用同樣,就是爲了線程切換,AsyncLayoutInflater 是在異步裏 inflate layout,那建立出來的 View 對象須要回調給主線程,就是經過 Handler 來實現的ide

  2. InflateThread 從名字上就好理解,是來作 Inflate 工做的工做線程,經過 InflateThread.getInstance 能夠猜想 InflateThread 裏面是一個單例,默認只在一個線程中作全部的加載工做,這個類咱們會在下面重點分析。函數

三、inflate

@UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
            @NonNull OnInflateFinishedListener callback) {
        if (callback == null) {
            throw new NullPointerException("callback argument may not be null!");
        }
        InflateRequest request = mInflateThread.obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        mInflateThread.enqueue(request);
    }
複製代碼

首先會經過 InflateThread 去獲取一個 InflateRequest,其中有一堆的成員變量。爲何須要這個類呢?由於後續異步 inflate 須要一堆的參數(對應 InflateRequest 中的變量),會致使方法簽名過長,而使用 InflateRequest 就避免了不少個參數的傳遞。

private static class InflateRequest {
        AsyncLayoutInflater inflater;
        ViewGroup parent;
        int resid;
        View view;
        OnInflateFinishedListener callback;

        InflateRequest() {
        }
    }
複製代碼

接下來對 InflateRequest 變量賦值以後會將其加到 InflateThread 中的一個隊列中等待執行

public void enqueue(InflateRequest request) {
        try {
            mQueue.put(request);
        } catch (InterruptedException e) {
            throw new RuntimeException(
                    "Failed to enqueue async inflate request", e);
        }
    }
複製代碼

四、InflateThread

private static class InflateThread extends Thread {
        private static final InflateThread sInstance;
        static {
            // 靜態代碼塊,確保只會建立一次,而且建立即start。
            sInstance = new InflateThread();
            sInstance.start();
        }

        public static InflateThread getInstance() {
            return sInstance;
        }

        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);// 異步inflate的緩存隊列;
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);// Todo

        // Extracted to its own method to ensure locals have a constrained liveness
        // scope by the GC. This is needed to avoid keeping previous request references
        // alive for an indeterminate amount of time, see b/33158143 for details
        public void runInner() {
            InflateRequest request;
            try {
                request = mQueue.take();// 從隊列中取一個request。
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

            try {
                request.view = request.inflater.mInflater.inflate(
                        request.resid, request.parent, false);// Inflate layout
            } catch (RuntimeException ex) {
                // Probably a Looper failure, retry on the UI thread
                Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
                        + " thread", ex);
            }
            Message.obtain(request.inflater.mHandler, 0, request)
                    .sendToTarget();// 返回主線程執行
        }

        @Override
        public void run() {
            while (true) {
                runInner();// 循環,但其實不會一直執行
            }
        }

        public InflateRequest obtainRequest() {
            InflateRequest obj = mRequestPool.acquire();
            if (obj == null) {
                obj = new InflateRequest();
            }
            return obj;
        }

        public void releaseRequest(InflateRequest obj) {
            obj.callback = null;
            obj.inflater = null;
            obj.parent = null;
            obj.resid = 0;
            obj.view = null;
            mRequestPool.release(obj);
        }

        public void enqueue(InflateRequest request) {
            try {
                mQueue.put(request);// 添加到緩存隊列中
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "Failed to enqueue async inflate request", e);
            }
        }
    }
複製代碼
  • enqueue 函數;
    • 只是插入元素到 mQueue 隊列中,若是元素過多那麼是有排隊機制的;
  • runInner 函數;
    • 運行於循環中,從 mQueue 隊列取出元素;
    • 調用 inflate 方法;
    • 返回主線程;

此處提一個問題:runInner 運行於循環中,會一直在執行嗎?

實際上不是的,mQueue 隊列的類型是 ArrayBlockingQueue ,這是一個「生產者-消費者」的模型,若是隊列中沒有元素,那麼 mQueue.take() 就會處於等待狀態,直到 mQueue.put 喚醒纔會繼續執行。

五、總結

本文主要分析了 AsyncLayoutInflater 的源碼實現,讓咱們想下其中的關鍵詞:Handler、線程、隊列、BasicInflater。

那 AsyncLayoutInflater 使用起來有什麼注意點,咱們能夠對其進行哪些方面的改進呢?歡迎繼續關注下一篇文章。

廣告時間

今日頭條各Android客戶端團隊招人火爆進行中,各個級別和應屆實習生都須要,業務增加快、日活高、挑戰大、待遇給力,各位大佬走過路過千萬不要錯過!

本科以上學歷、對技術有熱情,歡迎加個人微信詳聊:KOBE8242011

歡迎關注
相關文章
相關標籤/搜索