Android源码系列(19) -- AsyncLayoutInflater

栏目: Android · 发布时间: 2个月前

来源: phantomvk.github.io

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一、类签名

这是用于异步填充 View 的帮助类。在主线程构建 AsyncLayoutInflater 实例,并调用方法 inflate(int, ViewGroup, OnInflateFinishedListener) 。视图填充完毕后在主线程回调 OnInflateFinishedListener 通知调用者。

public final class AsyncLayoutInflater

这适用于懒创建或响应用户交互的UI部分。有利于主线程继续响应用户时,重量级填充操作继续执行。填充布局需要来自 ViewGroup.generateLayoutParams(AttributeSet) 的父布局,该方法线程安全。所有视图在构建过程中,禁止创建任何 Handler 或调用 Looper#myLooper() 。若布局无法在子线程进行异步填充,则操作会回退到主线程上执行。

注意,视图填充完成后不会加入到父布局中。这相当于调用 LayoutInflater.inflate(int, ViewGroup, boolean) 时参数 attachToRootfalse 。而调用者很可能希望在 OnInflateFinishedListener 通过调用 ViewGroup.addView(View) 把视图放入父布局。

本填充器不支持设置 LayoutInflater.FactoryLayoutInflater.Factory2 。类似,也不支持填充包含 fragment 的布局。源码版本 Android 28

二、数据成员

// 负责填充的LayoutInflater实例
LayoutInflater mInflater;

// Handler
Handler mHandler;

// 线程
InflateThread mInflateThread;

收到请求后进行填充操作

private Callback mHandlerCallback = new Callback() {
    @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;
    }
};

三、构造方法

构造方法内初始化了实际负责填充工作的填充器 BasicInflater

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

四、成员方法

主线程通过此方法添加新填充任务。

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

五、OnInflateFinishedListener

在子线程处理完毕后,通过此回调把填充完成的 View 返回给调用者。创建完成的 View 需要调用者自行添加到 ViewGroup 中,而不能像普通 LayoutInflater 那样直接加入到父布局。

public interface OnInflateFinishedListener {
    void onInflateFinished(@NonNull View view, @LayoutRes int resid,
            @Nullable ViewGroup parent);
}

六、InflateRequest

这个为构建任务的请求,请求包含参数:用于测绘的父布局、资源id、回调监听、已填充视图。初始化完成后的请求放入 InflateThread 的阻塞队列等待处理。

private static class InflateRequest {
    // AsyncLayoutInflater
    AsyncLayoutInflater inflater;
    // 父布局
    ViewGroup parent;
    // 需填充资源的id
    int resid;
    // 填充完成的视图,为填充完成则为null
    View view;
    // 填充完成的回调
    OnInflateFinishedListener callback;

    InflateRequest() {
    }
}

七、BasicInflater

此类继承父类 LayoutInflater 并重写父类方法 onCreateView(String name, AttributeSet attrs) 。在此方法内,调用父类方法 createView 反射构建目标视图。

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

八、InflateThread

负责填充视图的线程。线程内包含任务阻塞队列,这个队列的空间为10。当放入任务数量超过10时,如果主线程继续放入新任务,则主线程会被阻塞直到队列出现空余位置。这个类是单例,所以多个 AsyncLayoutInflater 实例共享一个工作线程及内部线程池。

如果有非常多任务需要异步填充,由于每个填充的视图平均需要10ms的时间,所以会出现视图排队填充完成,造成长时间等待。

private static class InflateThread extends Thread {
    private static final InflateThread sInstance;
    static {
        sInstance = new InflateThread();
        sInstance.start();
    }

    public static InflateThread getInstance() {
        return sInstance;
    }

    // 等待处理InflateRequest实例的阻塞队列
    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 {
            // 从InflateRequest取出资源id、父布局样式作为参数,通过BasicInflater构建视图
            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();
    }

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

    // 从缓存池中获取一个有效InflateRequest用于构建任务参数
    public InflateRequest obtainRequest() {
        // 先尝试从缓存池中获取对象
        InflateRequest obj = mRequestPool.acquire();
        if (obj == null) {
            // 若缓存池没有缓存对象才创建新对象
            obj = new InflateRequest();
        }
        return obj;
    }
    
    // 填充任务完成后,释放InflateRequest并放回缓存池中等待复用
    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);
        }
    }
}

九、缺点

  1. 不支持多线程并发处理任务;
  2. 类修饰为 final ,不能通过继承重写父类实现;
  3. 任务等待队列不能自定义初始化大小;
  4. 不支持设置 LayoutInflater.FactoryLayoutInflater.Factory2

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

为你推荐:

相关软件推荐:

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

STL源码剖析

STL源码剖析

侯捷 / 华中科技大学出版社 / 2002-6 / 68.00元

学习编程的人都知道,阅读、剖析名家代码乃是提高水平的捷径。源码之前,了无秘密。大师们的缜密思维、经验结晶、技术思路、独到风格,都原原本本体现在源码之中。 这本书所呈现的源码,使读者看到vector的实现、list的实现、heap的实现、deque的实现、Red Black tree的实现、hash table的实现、set/map的实现;看到各种算法(排序、查找、排列组合、数据移动与复制技术......一起来看看 《STL源码剖析》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换