Android AsyncLayoutInflater 限制及改进

本文概述

建议先回顾下以前四篇文章,这个系列的文章从前日后顺序看最佳:android

上一篇文章中咱们介绍了 AsyncLayoutInflater 的用法及源码实现,那么本文来分析下 AsyncLayoutInflater 使用的注意事项及改进方案。web

一、注意事项

For a layout to be inflated asynchronously it needs to have a parent whose generateLayoutParams(AttributeSet) is thread-safe and all the Views being constructed as part of inflation must not create any Handlers or otherwise call myLooper(). If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.缓存

NOTE that the inflated View hierarchy is NOT added to the parent. It is equivalent to calling inflate(int, ViewGroup, boolean) with attachToRoot set to false. Callers will likely want to call addView(View) in the AsyncLayoutInflater.OnInflateFinishedListener callback at a minimum.安全

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

以上来自 AsyncLayoutInflater 的说明文档:微信

  1. 使用异步 inflate,那么须要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的;
  2. 全部构建的 View 中必须不能建立 Handler 或者是调用 Looper.myLooper;(由于是在异步线程中加载的,异步线程默认没有调用 Looper.prepare );
  3. 异步转换出来的 View 并无被加到 parent view中,AsyncLayoutInflater 是调用了 LayoutInflater.inflate(int, ViewGroup, false),所以若是须要加到 parent view 中,就须要咱们本身手动添加;
  4. AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
  5. 不支持加载包含 Fragment 的 layout;
  6. 若是 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局;

二、注意事项说明

以上注意事项二、三、6两项很是容易明白,下面分析下其他几项;并发

2.1 使用异步 inflate,那么须要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的;

咱们看下 ViewGroup 中的 generateLayoutParams 方法app

/**
     * Returns a new set of layout parameters based on the supplied attributes set.
     * @param attrs the attributes to build the layout parameters from
     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
     *         of its descendants
     */
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
复制代码

generateLayoutParams 方法只是直接new了一个对象,于是非线程安全状况下建立屡次而使用非同一个对象的状况。异步

2.2 AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;

这个很好解释,由于 AsyncLayoutInflater 没有提供相似的Api,可是看过以前文章的小伙伴确定知道这两个类是很是关键的,若是 AsyncLayoutInflater 不支持设置,那么有些状况下效果确定是不同的,使用了异步以后致使效果不同岂不是很坑,下面咱们再具体解决。async

2.3 不支持加载包含 Fragment 的 layout;

前面的不支持三个字是否是让你内心一凉,其实这三个字不够准确,应该改成不彻底支持。这一条要一篇文章的篇幅才能说明白,咱们下篇文章再说哈。

三、可改进点

AsyncLayoutInflater 的代码并很少,并且代码质量也很高,因此其中能够优化的地方寥寥,简单说下个人见解:

  1. InflateThread 使用单线程来作所有的 Inflate 工做,若是一个界面中 Layout 不少不必定能知足需求;同时缓存队列默认 10 的大小限制若是超过了10个则会致使主线程的等待;
  2. AsyncLayoutInflater 只能经过回调的方式返回真正 Inflate 出来的View,可是假设一种场景,使用 AsyncLayoutInflater 去异步加载 Layout 和使用不是同一个类;
  3. AsyncLayoutInflater 中不能 setFactory,这样经过 AsyncLayoutInflater 加载的布局是没法获得系统的兼容(例如 TextView 变为 AppCompatTextView);
  4. 由于有任务排队机制,那么可能出现须要使用时任务仍然没有执行的场景,此时等待任务被执行还不如直接在主线程加载;

那么修改方案也很简单:

  1. 引入线程池,多个线程并发;
  2. 封装 AsyncLayoutInflater,修改调用方法,屏蔽不一样类使用形成的影响;
  3. 直接在 AsyncLayoutInflater 的 Inflater 中进行相关设置;
  4. 在获取加载出来 View 的 Api 中作判断,若是当前任务没有被执行,则直接在 UI 线程加载;

四、封装

由于 AsyncLayoutInflater 是 final 的,于是不能使用继承,咱们就将其 Copy 一份直接修改其中代码,修改点就是 针对章节3中可改进的地方。很少说,直接 Show The Code。

/**
 * 实现异步加载布局的功能,修改点:
 * 1. 单一线程;
 * 2. super.onCreate以前调用没有了默认的Factory;
 * 3. 排队过多的优化;
 */
public class AsyncLayoutInflaterPlus {

    private static final String TAG = "AsyncLayoutInflaterPlus";
    private Handler mHandler;
    private LayoutInflater mInflater;
    private InflateRunnable mInflateRunnable;
    // 真正执行加载任务的线程池
    private static ExecutorService sExecutor = Executors.newFixedThreadPool(Math.max(2,
            Runtime.getRuntime().availableProcessors() - 2));
    // InflateRequest pool
    private static Pools.SynchronizedPool<AsyncLayoutInflaterPlus.InflateRequest> sRequestPool = new Pools.SynchronizedPool<>(10);
    private Future<?> future;

    public AsyncLayoutInflaterPlus(@NonNull Context context) {
        mInflater = new AsyncLayoutInflaterPlus.BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
    }

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull CountDownLatch countDownLatch,
                        @NonNull AsyncLayoutInflaterPlus.OnInflateFinishedListener callback) {
        if (callback == null) {
            throw new NullPointerException("callback argument may not be null!");
        }
        AsyncLayoutInflaterPlus.InflateRequest request = obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        request.countDownLatch = countDownLatch;
        mInflateRunnable = new InflateRunnable(request);
        future = sExecutor.submit(mInflateRunnable);
    }

    public void cancel() {
        future.cancel(true);
    }

    /**
     * 判断这个任务是否已经开始执行
     *
     * @return
     */
    public boolean isRunning() {
        return mInflateRunnable.isRunning();
    }

    private Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            AsyncLayoutInflaterPlus.InflateRequest request = (AsyncLayoutInflaterPlus.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);
            request.countDownLatch.countDown();
            releaseRequest(request);
            return true;
        }
    };

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

    private class InflateRunnable implements Runnable {
        private InflateRequest request;
        private boolean isRunning;

        public InflateRunnable(InflateRequest request) {
            this.request = request;
        }

        @Override
        public void run() {
            isRunning = true;
            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();
        }

        public boolean isRunning() {
            return isRunning;
        }
    }

    private static class InflateRequest {
        AsyncLayoutInflaterPlus inflater;
        ViewGroup parent;
        int resid;
        View view;
        AsyncLayoutInflaterPlus.OnInflateFinishedListener callback;
        CountDownLatch countDownLatch;

        InflateRequest() {
        }
    }

    private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
                "android.widget.",
                "android.webkit.",
                "android.app."
        };

        BasicInflater(Context context) {
            super(context);
            if (context instanceof AppCompatActivity) {
                // 加上这些能够保证AppCompatActivity的状况下,super.onCreate以前
                // 使用AsyncLayoutInflater加载的布局也拥有默认的效果
                AppCompatDelegate appCompatDelegate = ((AppCompatActivity) context).getDelegate();
                if (appCompatDelegate instanceof LayoutInflater.Factory2) {
                    LayoutInflaterCompat.setFactory2(this, (LayoutInflater.Factory2) appCompatDelegate);
                }
            }
        }

        @Override
        public LayoutInflater cloneInContext(Context newContext) {
            return new AsyncLayoutInflaterPlus.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);
        }
    }

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

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

}
复制代码
/**
 * 调用入口类;同时解决加载和获取View在不一样类的场景
 */
public class AsyncLayoutLoader {

    private int mLayoutId;
    private View mRealView;
    private Context mContext;
    private ViewGroup mRootView;
    private CountDownLatch mCountDownLatch;
    private AsyncLayoutInflaterPlus mInflater;
    private static SparseArrayCompat<AsyncLayoutLoader> sArrayCompat = new SparseArrayCompat<AsyncLayoutLoader>();

    public static AsyncLayoutLoader getInstance(Context context) {
        return new AsyncLayoutLoader(context);
    }

    private AsyncLayoutLoader(Context context) {
        this.mContext = context;
        mCountDownLatch = new CountDownLatch(1);
    }

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent) {
        inflate(resid, parent, null);
    }

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
                        AsyncLayoutInflaterPlus.OnInflateFinishedListener listener) {
        mRootView = parent;
        mLayoutId = resid;
        sArrayCompat.append(mLayoutId, this);
        if (listener == null) {
            listener = new AsyncLayoutInflaterPlus.OnInflateFinishedListener() {
                @Override
                public void onInflateFinished(View view, int resid, ViewGroup parent) {
                    mRealView = view;
                }
            };
        }
        mInflater = new AsyncLayoutInflaterPlus(mContext);
        mInflater.inflate(resid, parent, mCountDownLatch, listener);
    }

    /**
     * getLayoutLoader 和 getRealView 方法配对出现
     * 用于加载和获取View在不一样类的场景
     *
     * @param resid
     * @return
     */
    public static AsyncLayoutLoader getLayoutLoader(int resid) {
        return sArrayCompat.get(resid);
    }

    /**
     * getLayoutLoader 和 getRealView 方法配对出现
     * 用于加载和获取View在不一样类的场景
     *
     * @param resid
     * @return
     */
    public View getRealView() {
        if (mRealView == null && !mInflater.isRunning()) {
            mInflater.cancel();
            inflateSync();
        } else if (mRealView == null) {
            try {
                mCountDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            setLayoutParamByParent(mContext, mRootView, mLayoutId, mRealView);
        } else {
            setLayoutParamByParent(mContext, mRootView, mLayoutId, mRealView);
        }
        return mRealView;
    }


    /**
     * 根据Parent设置异步加载View的LayoutParamsView
     *
     * @param context
     * @param parent
     * @param layoutResId
     * @param view
     */
    private static void setLayoutParamByParent(Context context, ViewGroup parent, int layoutResId, View view) {
        if (parent == null) {
            return;
        }
        final XmlResourceParser parser = context.getResources().getLayout(layoutResId);
        try {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            ViewGroup.LayoutParams params = parent.generateLayoutParams(attrs);
            view.setLayoutParams(params);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            parser.close();
        }
    }

    private void inflateSync() {
        mRealView = LayoutInflater.from(mContext).inflate(mLayoutId, mRootView, false);
    }

}
复制代码

五、总结

本文主要是分析 AsyncLayoutInflater 的使用注意事项,并对其中的限制进行了改进,此处再也不累述。

下一篇文章咱们一块儿探究下为何 AsyncLayoutInflater 文档上写不支持包含 Fragment 标签的异步,以及真的不能异步吗?

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都须要,业务增加快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、非频繁跳槽(如两年两跳),欢迎加个人微信详聊:KOBE8242011

欢迎关注
相关文章
相关标签/搜索