AsyncLayoutInflater源码颇析

QuibblerQuibbler 2021-02-23 约 19 分钟 1832 次阅读

AsyncLayoutInflater源码颇析

别看AsyncLayoutInflater源码只有233行,代码质量非常高。二百行左右的代码,值得开发者好好学习。这就是阅读源码的意义,也要写出这样优秀的代码。

1、构造AsyncLayoutInflater实例

传入Context构造AsyncLayoutInflater实例:

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

初始化AsyncLayoutInflater内部这几个变量:LayoutInflater加载布局、Handler处理回调、InflateThread布局加载任务线程。

1.1、重写优化后的LayoutInflater

内部静态类BasicInflater继承中系统布局加载器LayoutInflater。别小瞧这33行代码,虽然看都能看懂。那是需要对框架有深刻的理解才能想出来,参考LayoutInflater的实现类PhoneLayoutInflater。

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

内部将常用的系统包路径:android.widget、android.webkit、android.app提取出来存放到sClassPrefixList,优先加载这几个包中的类。系统中控件相关的类大部分都在这里,对于绝大多数场景能够提高布局解析创建对象的速度。关于Android中的ClassLoader详见了解Android ClassLoader一文。

1.2、加载回调Handler

通过Handler.Callback接口构造回调Handler,AsyncLayoutInflater在主线程创建,所以该Handler用的也是主线程Looper,回调方法在主线程中执行。这里Callback返回true,不会再执行Handler的handleMessage(msg)方法,关于Callback详见Handler中Callback的作用。

private Callback mHandlerCallback = new Callback() {
    
        @Override
        public boolean handleMessage(Message msg) {
            InflateRequest request = (InflateRequest) msg.obj;
            //如果子线程布局加载解析失败返回null,则在主线程中再次尝试加载。
            if (request.view == null) {
                request.view = mInflater.inflate(
                        request.resid, request.parent, false);
            }
            //回调加载结束监听器OnInflateFinishedListener
            request.callback.onInflateFinished(
                    request.view, request.resid, request.parent);
            //释放Request布局加载请求:从mRequestPool中释放
            mInflateThread.releaseRequest(request);
            return true;
        }
        
    };

1.3、异步加载线程InflateThread

AsyncLayoutInflater异步加载布局的核心就是InflateThread任务线程。在类加载的时候通过静态代码块初始化InflateThread单例sInstance,并开启任务线程。构造AsyncLayoutInflater的时候通过getInstance()获取单例。

private static final InflateThread sInstance;
        
        //静态代码块初始化InflateThread单例sInstance,并开启任务线程
        static {
            sInstance = new InflateThread();
            sInstance.start();
        }
        
        public static InflateThread getInstance() {
            return sInstance;
        }

InflateThread类内部有两个集合:一个是保存布局加载任务的阻塞队列,另一个是用来复用InflateRequest的对象池。

private static class InflateThread extends Thread {
        ...
        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
        ...
    }

2、inflate()异步加载布局

构造好AsyncLayoutInflater实例就可以通过它的inflate()方法开启异步加载布局,OnInflateFinishedListener回调callback必须非空,否则抛出异常。

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

2.1、封装加载请求InflateRequest

内部类InflateRequest将布局加载请求相关的数据封装在一起,回调传递的时候也方便一些。合理的封装是开发者必要的编程素养。

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

2.2、子线程执行加载任务

在前面1.3节提到InflateThread,类被加载的时候通过静态代码块初始化单例并开启线程,执行:

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

很多循环操作一样,抽取出单独的方法runInner(),目的是为了持有对象引用,避免在不确定的时间内保持有效引用。Volley网络请求框架内部的四个网络请求线程循环也是这样做的。

// 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();
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }
            try {
                //在子线程中加载xml布局
                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();
        }

最后,将加载好的布局封装在请求InflateRequest中,并通过Handler发送会主线程处理。

2.3、回调OnInflateFinishedListener

此前早已在AsyncLayoutInflater构造方法中初始化好的主线程Handler,处理子线程布局加载完成的回调:

private Callback mHandlerCallback = new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            InflateRequest request = (InflateRequest) msg.obj;
            //如果在子线程中xml布局加载失败,返回空View。重新在当前主线程中加载一遍
            if (request.view == null) {
                request.view = mInflater.inflate(
                        request.resid, request.parent, false);
            }
            request.callback.onInflateFinished(
                    request.view, request.resid, request.parent);
            //释放当前请求,放回到mRequestPool中以便复用
            mInflateThread.releaseRequest(request);
            return true;
        }
    };

2.4、InflateRequest对象复用

最后释放请求,这里涉及到对象池Pools。简单说明一下最后release()的意图,将InflateRequest释放并放入到对象池中以便后续复用。

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

在AsyncLayoutInflater的inflate()方法中,借助InflateThread调用obtainRequest()方法获取InflateRequest复用:

....
    InflateRequest request = mInflateThread.obtainRequest();
    ....

如果对象池中没有可用的,那么就创建一个新的InflateRequest实例使用。

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

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w