Android中异步加载布局的小能手_AsyncLayoutInflater

本文主要从如下几点学习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的构造方法

  • 代码如下

    public AsyncLayoutInflater(@NonNull Context context) {
                  //创建BasicInflater对象,BasicInflater继承至 LayoutInflater
            mInflater = new BasicInflater(context);
           //创建一个Handler对象,目的是线程的切换,从布局加载的工作线程切换到主线程中。
            mHandler = new Handler(mHandlerCallback);
                  //创建一个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!");
            }
          // 构建InflateRequest对象,将resid、parent、callback等变量存储到这个变量中。
            InflateRequest request = mInflateThread.obtainRequest();
              
            request.inflater = this;
            request.resid = resid;
            request.parent = parent;
            request.callback = callback;
              // 然后将带有参数的InflateRequest对象,通过Inflatethread类中的enqueue方法保存到ArrayBlockingQueue队列中
            mInflateThread.enqueue(request);
        }
    

AsyncLayoutInflater中的InflateThread类

  • 代码如下

     // InflateThread是AsyncLayoutInfalter类中的一个静态内部类
    private static class InflateThread extends Thread {
            private static final InflateThread sInstance;
                  // 当类加载的时候,会初始化该类并且开启线程
            static {
                sInstance = new InflateThread();
                sInstance.start();
            }
                  //对外提供获取实例的方法
            public static InflateThread getInstance() {
                return sInstance;
            }
                  //生产者-消费者的模型,阻塞队列
            private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
      //使用了对象池,来缓存创建的InflateRequest对象,防止重复创建
            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发送到主线程中。
               Message.obtain(request.inflater.mHandler, 0, request)
                        .sendToTarget();
            }
    
            @Override
            public void run() {
              //死循环
                while (true) {
                    runInner();
                }
            }
                  
                  //主要创建一个InflateRequest对象
            public InflateRequest obtainRequest() {
              //先从对象缓存池中查询
                InflateRequest obj = mRequestPool.acquire();
              //如果当前InflateRequest对象没有创建过,就创建一个
                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);
            }
                  
                  //将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类

  • 代码如下

    //该类主要是携带数据,赋给Message.obj变量,通过Message将数据发送到主线程中
    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中优先加载 android.widget.", "android.webkit.","android.app."中的控件,之后在按照常规的加载方式去加载
               
                
                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中的数据带到主线程
                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中携带的数据
                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. 所有被创建的View不能在内部创建Handler或者是调用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);是这样调用去加载布局的 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. 不支持设置LayoutInflater.Factory和Factory2
    // 5. 不支持加载包含Fragment的布局
    

为什么会有这些问题

针对第一点:对于异步加载的布局需要它的父View(ViewGroup)的generateLayoutParams(AttributeSet attrs)方法是线程安全的。
  • 看ViewGroup的generateLayoutParams(AttributeSet attrs)f方法(注意参数哟,ViewGroup中有很多同名的重载方法)

    public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    // 可以看到,直接new了一个对象出来,如果是在非线程安全的情况下去调用,会创建多个对象。
    
针对第二点:所有被创建的View不能在内部创建Handler或者是调用Looper.myLooper()方法。
  • 因为是异步加载,在子线程中如果使用Handler必须在同线程下创建一个looper对象,不然会报错的。同样Looper.myLooper是获取同线程下的looper对象,你的有才能用,才能获取。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容