Android中異步加載布局的小能手_AsyncLayoutInflater

本文主要從如下幾點學(xué)習(xí)AsyncLayoutInflater

  • AsyncLayoutInfalter是啥
  • AsyncLayoutInflater代碼實操
  • AsyncLayoutInflater的源碼分析
  • AsyncLayoutInflater的問題

AsyncLayoutInflater是啥

官方源碼定義

  • 如下

    Helper class for inflating layouts asynchronously
    //用于異步加載布局的幫助類
    

AsyncLayoutInflater代碼實操

  • 代碼如下

     new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                @Override
                public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                    Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
                    setContentView(view);
                }
            });
    

AsyncLayoutInflater源碼解析

AsyncLayoutInflater的構(gòu)造方法

  • 代碼如下

    public AsyncLayoutInflater(@NonNull Context context) {
                  //創(chuàng)建BasicInflater對象筋粗,BasicInflater繼承至 LayoutInflater
            mInflater = new BasicInflater(context);
           //創(chuàng)建一個Handler對象前标,目的是線程的切換,從布局加載的工作線程切換到主線程中。
            mHandler = new Handler(mHandlerCallback);
                  //創(chuàng)建一個InflateThread對象屋讶,該類是繼承至Thread類悍抑,
            mInflateThread = InflateThread.getInstance();
        }
    

AsyncLayoutInflater的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!");
            }
          // 構(gòu)建InflateRequest對象,將resid、parent望迎、callback等變量存儲到這個變量中。
            InflateRequest request = mInflateThread.obtainRequest();
              
            request.inflater = this;
            request.resid = resid;
            request.parent = parent;
            request.callback = callback;
              // 然后將帶有參數(shù)的InflateRequest對象凌外,通過Inflatethread類中的enqueue方法保存到ArrayBlockingQueue隊列中
            mInflateThread.enqueue(request);
        }
    

AsyncLayoutInflater中的InflateThread類

  • 代碼如下

     // InflateThread是AsyncLayoutInfalter類中的一個靜態(tài)內(nèi)部類
    private static class InflateThread extends Thread {
            private static final InflateThread sInstance;
                  // 當(dāng)類加載的時候辩尊,會初始化該類并且開啟線程
            static {
                sInstance = new InflateThread();
                sInstance.start();
            }
                  //對外提供獲取實例的方法
            public static InflateThread getInstance() {
                return sInstance;
            }
                  //生產(chǎn)者-消費者的模型,阻塞隊列
            private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
      //使用了對象池康辑,來緩存創(chuàng)建的InflateRequest對象摄欲,防止重復(fù)創(chuàng)建
            private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
            public void runInner() {
                InflateRequest request;
                try {
                  //從隊列中取出一個請求,
                    request = mQueue.take();
                } catch (InterruptedException ex) {
                    // Odd, just continue
                    Log.w(TAG, ex);
                    return;
                }
    
                try {
                  //LayoutInfalter加載view的操作
                  //獲取到View對象
                  
                    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);
                }
              //無論inflate方法的失敗或者成功疮薇,都將request發(fā)送到主線程中胸墙。
               Message.obtain(request.inflater.mHandler, 0, request)
                        .sendToTarget();
            }
    
            @Override
            public void run() {
              //死循環(huán)
                while (true) {
                    runInner();
                }
            }
                  
                  //主要創(chuàng)建一個InflateRequest對象
            public InflateRequest obtainRequest() {
              //先從對象緩存池中查詢
                InflateRequest obj = mRequestPool.acquire();
              //如果當(dāng)前InflateRequest對象沒有創(chuàng)建過,就創(chuàng)建一個
                if (obj == null) {
                    obj = new InflateRequest();
                }
              //否則的話就將當(dāng)前內(nèi)存中存在的返回按咒;
                return obj;
            }
    
                  //將對象緩存池中的對象數(shù)據(jù)清空迟隅,方便對象的復(fù)用。
            public void releaseRequest(InflateRequest obj) {
                obj.callback = null;
                obj.inflater = null;
                obj.parent = null;
                obj.resid = 0;
                obj.view = null;
                mRequestPool.release(obj);
            }
                  
                  //將inflate請求中的request存儲到隊列中
            public void enqueue(InflateRequest request) {
                try {
                    mQueue.put(request);
                } catch (InterruptedException e) {
                    throw new RuntimeException(
                            "Failed to enqueue async inflate request", e);
                }
            }
        }
    

AsyncLayoutInflater中的InflateRequest類

  • 代碼如下

    //該類主要是攜帶數(shù)據(jù)励七,賦給Message.obj變量智袭,通過Message將數(shù)據(jù)發(fā)送到主線程中
    private static class InflateRequest {
            AsyncLayoutInflater inflater;
            ViewGroup parent;
            int resid;
            View view;
            OnInflateFinishedListener callback;
    
            InflateRequest() {
            }
        }
    

AsyncLayoutInflater的BasicInflater類

  • 代碼如下

    //該類是繼承至LayoutInflater
    //重寫了onCreateView
    private static class BasicInflater extends LayoutInflater {
            private static final String[] sClassPrefixList = {
                "android.widget.",
                "android.webkit.",
                "android.app."
            };
    
            BasicInflater(Context context) {
                super(context);
            }
    
            @Override
            public LayoutInflater cloneInContext(Context newContext) {
                return new BasicInflater(newContext);
            }
    
            @Override
            protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
              //在onCreateView中優(yōu)先加載 android.widget.", "android.webkit.","android.app."中的控件,之后在按照常規(guī)的加載方式去加載
               
                
                for (String prefix : sClassPrefixList) {
                    try {
                        View view = createView(name, prefix, attrs);
                        if (view != null) {
                            return view;
                        }
                    } catch (ClassNotFoundException e) {
                        // In this case we want to let the base class take a crack
                        // at it.
                    }
                }
    
                return super.onCreateView(name, attrs);
            }
        }
    

AsyncLayoutInflater的Callback

  • 代碼如下

    private Callback mHandlerCallback = new Callback() {
            @Override
            public boolean handleMessage(Message msg) {
              //通過Handler從Message中獲取InflateRequest中的數(shù)據(jù)帶到主線程
                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);
              //置空InflateRequest中攜帶的數(shù)據(jù)
                mInflateThread.releaseRequest(request);
                return true;
            }
        };
    

AsyncLayoutInflater的問題

同樣看源碼定義

  • 定義如下

    For a layout to be inflated asynchronously it needs to have a parent
     whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
     and all the Views being constructed as part of inflation must not create
     any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
     layout that is trying to be inflated cannot be constructed
     asynchronously for whatever reason, {@link AsyncLayoutInflater} will
     automatically fall back to inflating on the UI thread.
       
     // 1. 對于異步加載的布局需要它的父View的generateLayoutParams(AttributeSet attrs)方法是線程安全的掠抬。
       
     // 2. 所有被創(chuàng)建的View不能在內(nèi)部創(chuàng)建Handler或者是調(diào)用Looper.myLooper()方法吼野。
       
     // 如果使用AsynclayoutInflater.inflate()的方法異步加載失敗,不管是什么原因剿另,都會會退到主線程中就加載布局箫锤。
    
    NOTE that the inflated View hierarchy is NOT added to the parent. It is
     equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
     with attachToRoot set to false. Callers will likely want to call
     {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
     callback at a minimum.
    // 3. 異步加載布局獲取的View沒有添加到父View中,因為源碼中request.inflater.mInflater.inflate(
    //    request.resid, request.parent, false);是這樣調(diào)用去加載布局的 attachToRoot 為 false
    //    所以如果希望被添加到父View中雨女,需要在onInflateFinishedListener方法中去手動添加View
    
    This inflater does not support setting a {@link LayoutInflater.Factory}
     nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
     layouts that contain fragments.
    // 4. 不支持設(shè)置LayoutInflater.Factory和Factory2
    // 5. 不支持加載包含F(xiàn)ragment的布局
    

為什么會有這些問題

針對第一點:對于異步加載的布局需要它的父View(ViewGroup)的generateLayoutParams(AttributeSet attrs)方法是線程安全的谚攒。
  • 看ViewGroup的generateLayoutParams(AttributeSet attrs)f方法(注意參數(shù)喲,ViewGroup中有很多同名的重載方法)

    public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    // 可以看到氛堕,直接new了一個對象出來馏臭,如果是在非線程安全的情況下去調(diào)用,會創(chuàng)建多個對象讼稚。
    
針對第二點:所有被創(chuàng)建的View不能在內(nèi)部創(chuàng)建Handler或者是調(diào)用Looper.myLooper()方法括儒。
  • 因為是異步加載,在子線程中如果使用Handler必須在同線程下創(chuàng)建一個looper對象锐想,不然會報錯的帮寻。同樣Looper.myLooper是獲取同線程下的looper對象,你的有才能用赠摇,才能獲取固逗。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浅蚪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烫罩,更是在濱河造成了極大的恐慌惜傲,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贝攒,死亡現(xiàn)場離奇詭異盗誊,居然都是意外死亡,警方通過查閱死者的電腦和手機隘弊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門哈踱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梨熙,你說我怎么就攤上這事嚣鄙。” “怎么了串结?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舅列。 經(jīng)常有香客問我肌割,道長,這世上最難降的妖魔是什么帐要? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任把敞,我火速辦了婚禮,結(jié)果婚禮上榨惠,老公的妹妹穿的比我還像新娘奋早。我一直安慰自己,他們只是感情好赠橙,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布耽装。 她就那樣靜靜地躺著,像睡著了一般期揪。 火紅的嫁衣襯著肌膚如雪掉奄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天凤薛,我揣著相機與錄音姓建,去河邊找鬼。 笑死缤苫,一個胖子當(dāng)著我的面吹牛速兔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播活玲,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涣狗,長吁一口氣:“原來是場噩夢啊……” “哼谍婉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屑柔,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤屡萤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掸宛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體死陆,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年唧瘾,在試婚紗的時候發(fā)現(xiàn)自己被綠了措译。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饰序,死狀恐怖领虹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情求豫,我是刑警寧澤塌衰,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蝠嘉,受9級特大地震影響最疆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚤告,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一努酸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杜恰,春花似錦获诈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逗爹,卻和暖如春终抽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桶至。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工昼伴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镣屹。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓圃郊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親女蜈。 傳聞我的和親對象是個殘疾皇子持舆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

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