AsyncLayoutInflater 「異步加載布局」實現(xiàn)

AsyncLayoutInflater 是一個異步初始化布局的 Helper 類剔应。

它的本質(zhì)就是把對布局文件的 inflate 放入到了子線程里面,等到初始化成功后牍疏,在通過接口拋回到主線程。

Activity 中的簡單代碼實現(xiàn)為:

 // 異步加載 xml, 在 Activity.onCreate(xxx) 里面
AsyncLayoutInflater(this).inflate(R.layout.activity_try_everything, null, object : AsyncLayoutInflater.OnInflateFinishedListener {
      override fun onInflateFinished(view: View, p1: Int, p2: ViewGroup?) {
           setContentView(view)
      }
})

一般來說,我們都是通過 setContentView(R.layout.xxx) 的形式加載 xml 的.

其實有三個不同方法參數(shù)的 setContentView()函數(shù)臭挽, 如下

    public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
    }

    public void setContentView(View view) {
        this.getDelegate().setContentView(view);
    }

    public void setContentView(View view, LayoutParams params) {
        this.getDelegate().setContentView(view, params);
    }

我們可以通過 inflate 出來 layoutResID 成一個對應的 view, 然后調(diào)用 setContentView(View view), 通過這樣傳遞咬腕。

那么 AsyncLayoutInflater 是如何工作的呢欢峰?
以下內(nèi)容分為以下部分:

  1. AsyncLayoutInflater 的簡單介紹和創(chuàng)建
  2. 添加 request 初始化布局的請求: AsyncLayoutInflater.inflate(xxx)
  3. Handler 里面處理 Message 發(fā)送的消息, 把結(jié)果返回給 UI 線程
  4. 小結(jié)

1. AsyncLayoutInflater 的簡單介紹和創(chuàng)建

1.1 AsyncLayoutInflater 的簡單介紹

AsyncLayoutInflater 的實現(xiàn)涨共, 它主要有三個成員變量和一個內(nèi)部類:

  1. LayoutInflater 用來 inflate 布局
  2. Handler 用來 post 到主線程
  3. InflateThread 是一個 Thread 纽帖,一個子線程,在里面完成 inflate 過程
  4. InflateRequest 靜態(tài)內(nèi)部類举反,用來承載必要的信息
  5. OnInflateFinishedListener 對外部暴漏的接口懊直,用于通知外部,已經(jīng)初始化 View 完成

1.2 AsyncLayoutInflater 的創(chuàng)建

AsyncLayoutInflater 的創(chuàng)建火鼻, 從代碼中可以看到:

public AsyncLayoutInflater(@NonNull Context context) {
    // 在創(chuàng)建一個 AsyncLayoutInflater 對象時室囊,會同時新建 inflater, handler, 和 thread
    mInflater = new BasicInflater(context);
    mHandler = new Handler(mHandlerCallback);
    mInflateThread = InflateThread.getInstance();
}

其中雕崩,代碼分析如下:

  1. mInflater 是后續(xù)用來 inflate布局

  2. mInflateThread 是一個單例對象,在獲取時該 thread 已經(jīng)開始工作 start
    代碼如下:

    private static final InflateThread sInstance;
    // 靜態(tài)代碼塊融撞,在加載該類時就會被調(diào)用
       static {
       sInstance = new InflateThread();
       // 此時子線程已經(jīng)開始跑
       sInstance.start();
       }
    
  3. mHandler 是用來接收 thread 發(fā)出的消息盼铁,回到 UI 線程

    Message 的發(fā)送消息位置:

    Message.obtain(request.inflater.mHandler, 0, request).sendToTarget()
    

    并且把主要的信息「即 request」放入在 Message 中的 object 中,

做好上面的準備后尝偎,此時 mInflateThread 里面是沒有請求要執(zhí)行的饶火,一旦它的隊列里有了 request, 則會執(zhí)行對應的邏輯

2. 添加 request 初始化布局的請求: AsyncLayoutInflater.inflate(xxx)

當調(diào)用該方法時致扯,會有一系列步驟:

  1. 創(chuàng)建 InflateRequest 請求肤寝;
  2. 通過 mInflateThread.enqueue(request) 把該請求放入到 mInflateThread 子線程里面

源碼如下:

@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);
}

從代碼中可以看到,每一次調(diào)用 inflate(xxx) 方法都會新創(chuàng)建一個 InflateRequest, 并且把該 request 加入 mInflateThread 的隊列中抖僵。

2.1 創(chuàng)建 InflateRequest 請求

而在 InflateThread 中有一個隊列 mQueue 用來存放 InflateRequest 請求

private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);

InflateThread 線程在 start() 之后鲤看,會去調(diào)用 runInner()嘗試獲取 inflateRequest 然后執(zhí)行對應邏輯。

@Override
public void run() {
    while (true) {
        runInner();
    }
}

注:這里并不會是一個死循環(huán)耍群, 因為 mQueue.take() 方法罢绽。
ArrayBlockingQueue 是會產(chǎn)生阻塞的隊列敞曹,在 take() 方法中瑟曲,如果 count == 0, 則會一直陷入 notEmpty.await()

ArrayBlockingQueuetake() 方法源碼:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 這里會一直等待驶臊,直到有消息為止
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

2.2 通過 mInflateThread.enqueue(request) 添加請求后,mQueue 不為空

當該 mQueue 里面可以獲取到 request. 則會通過 inflater.inflate(xxx) 在子線程中完成 view 的構(gòu)建耘婚,并通過 Message 發(fā)送消息給對應的 handler 處理罢浇。代碼如下:

public void runInner() {
     InflateRequest request;
     try {
         // 獲取是否有請求
         request = mQueue.take();
     } catch (InterruptedException ex) {
         // Odd, just continue
         Log.w(TAG, ex);
         return;
     }
    // 
     try {
         request.view = request.inflater.mInflater.inflate(
                        request.resid, request.parent, false);
     } 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();
}

3. Handler 里面處理 Message 發(fā)送的消息, 把結(jié)果返回給 UI 線程

通過 Message 發(fā)送消息后沐祷,真正接受消息的地方是在 HandlerhandleMessage(xxx) 方法里面嚷闭,
此時已經(jīng)切回到了 UI 線程中:

@Override
public boolean handleMessage(Message msg) {
    InflateRequest request = (InflateRequest) msg.obj;
    if (request.view == null) {
        request.view = mInflater.inflate(request.resid, request.parent, false);
    }
    request.callback.onInflateFinished(request.view, request.resid, request.parent);
    mInflateThread.releaseRequest(request);
    return true;
}

代碼邏輯:

  1. msg.obj 中獲取到 InflateRequest
  2. 判斷 request.view 是否為 null
  3. 如果為空,則重新 inflate 赖临,此時是在 UI 線程中進行的胞锰,和一般的初始化一樣;
  4. 通過接口 OnInflateFinishedListener 通知外部兢榨,并把得到的 view 傳遞出去

注:在這里做了兼容嗅榕,防止在異步中 inflate 失敗,做了判斷

對外暴漏的接口:AsyncLayoutInflater.OnInflateFinishedListener

沒太多值得說的吵聪,類似與最簡單的 View.OnClickListener 一樣凌那,通過接口把事情拋到外部的調(diào)用方。

4. 小結(jié)

上述部分吟逝,簡單的介紹了 AsyncLayoutInflater 的內(nèi)部實現(xiàn)帽蝶。

可能會在心里產(chǎn)生一點點疑問:這個簡單來說不就是 new 了一個新的子線程,然后 inflateview 后块攒,重新放入到 UI 線程中, 會有很大用處嗎励稳?

確實佃乘,AsyncLayoutInflater 簡單來說就是實現(xiàn)了上述表述。

而且它也有很多缺點驹尼,官方文檔是明確的寫著:

This inflater does not support setting a LayoutInflater.Factory nor LayoutInflater.Factory2. Similarly it does not support inflating layouts that contain fragments.

有著局限性趣避,這也是我們一般不會使用這種方式的一個很大原因。

但是呢扶欣?當一個 XML 已經(jīng)可以支持異步 inflate, 那么能不能 PreInflate 呢?

能不能提前在子線程中去 inflate 布局千扶?然后在真正需要這些 view 的時候料祠,就可以省去加載布局的時間了?

當然可以澎羞,具體怎么實現(xiàn)還需要接下來再次梳理髓绽。

本次只是一個簡單的介紹 AsyncLayoutInflater

期待后續(xù)能夠整理好妆绞。

2019.10.10 by chendroid
文章來自公眾號:「Droid 二三事

PS: 后續(xù)會接著整理梳理知識顺呕。

參考:

  1. 官方文檔說明:https://developer.android.com/reference/android/support/v4/view/AsyncLayoutInflater
  2. https://juejin.im/post/5b651ad8e51d4519635008bd
  3. 簡書:http://www.reibang.com/p/a3a3bd314c45
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市括饶,隨后出現(xiàn)的幾起案子株茶,更是在濱河造成了極大的恐慌,老刑警劉巖图焰,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件启盛,死亡現(xiàn)場離奇詭異,居然都是意外死亡技羔,警方通過查閱死者的電腦和手機僵闯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藤滥,“玉大人鳖粟,你說我怎么就攤上這事∽景恚” “怎么了向图?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長标沪。 經(jīng)常有香客問我张漂,道長,這世上最難降的妖魔是什么谨娜? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任航攒,我火速辦了婚禮,結(jié)果婚禮上趴梢,老公的妹妹穿的比我還像新娘漠畜。我一直安慰自己币他,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布憔狞。 她就那樣靜靜地躺著蝴悉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘾敢。 梳的紋絲不亂的頭發(fā)上拍冠,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音簇抵,去河邊找鬼庆杜。 笑死,一個胖子當著我的面吹牛碟摆,可吹牛的內(nèi)容都是我干的晃财。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼典蜕,長吁一口氣:“原來是場噩夢啊……” “哼断盛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起愉舔,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钢猛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后轩缤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厢洞,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年典奉,在試婚紗的時候發(fā)現(xiàn)自己被綠了躺翻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡卫玖,死狀恐怖公你,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情假瞬,我是刑警寧澤陕靠,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站脱茉,受9級特大地震影響剪芥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜琴许,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一税肪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦益兄、人聲如沸锻梳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疑枯。三九已至,卻和暖如春蛔六,著一層夾襖步出監(jiān)牢的瞬間荆永,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工国章, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留具钥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓捉腥,卻偏偏與公主長得像氓拼,于是被迫代替她去往敵國和親你画。 傳聞我的和親對象是個殘疾皇子抵碟,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容