前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android AsyncLayoutInflater 限制及改进

Android AsyncLayoutInflater 限制及改进

作者头像
用户2898788
发布于 2018-08-21 02:11:52
发布于 2018-08-21 02:11:52
2.8K00
代码可运行
举报
文章被收录于专栏:双十二技术哥双十二技术哥
运行总次数:0
代码可运行

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

  • Android setContentView 源码解析》;
  • 《Android LayoutInflater 源码解析》;
  • 《Android LayoutInflater Factory 源码解析》;
  • 《Android AsyncLayoutInflater 源码解析》;

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

1、注意事项

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.

以上来自 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线程来加载布局;

2、注意事项说明

以上注意事项2、3、6项非常容易明白,下面分析下其余几项;

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

我们看下 ViewGroup 中的 generateLayoutParams 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * 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 不支持设置,那么有些情况下效果肯定是不一样的,使用了异步之后导致效果不一样岂不是很坑,下面我们再具体解决。

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

前面的不支持三个字是不是让你心里一凉,其实这三个字不够准确,应该改为不完全支持。这一条要一篇文章的篇幅才能说明白,我们下篇文章再说哈。

3、可改进点

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 线程加载;

4、封装

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 实现异步加载布局的功能,修改点:
 * 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);
    }

}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 调用入口类;同时解决加载和获取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);
    }

}

5、总结

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

下一篇文章我们一起探究下为什么 AsyncLayoutInflater 文档上写不支持包含 Fragment 标签的异步,以及真的不能异步吗?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 双十二技术哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android AsyncLayoutInflater 源码解析
我们已经学习了 Layout 相关的方方面面,本文就来学习下一个相对新颖的知识点:AsyncLayoutInflater;说它相对新颖是因为它是Android 24.1.0版本之后才有的。
用户2898788
2018/08/21
1.4K0
Android AsyncLayoutInflater 源码解析
Android | 带你探究 LayoutInflater 布局解析原理
https://juejin.im/post/6886052422260228103
用户1907613
2020/11/09
6180
Android | 带你探究 LayoutInflater 布局解析原理
Android应用setContentView与LayoutInflater加载解析机制源码分析
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】
全栈程序员站长
2022/09/13
5050
遇见LayoutInflater&Factory
在我们写listview的adapter的getView方法中我们都会通过LayoutInflater.from(mContext)获取LayoutInflater实例。 现在我们通过源码来分析一下LayoutInflater实例的获取:
静默加载
2020/05/29
1K0
Android LayoutInflater 源码解析
在上篇文章中我们学习了setContentView的源码,还记得其中的LayoutInflater吗?本篇文章就来学习下LayoutInflater。
用户2898788
2018/08/21
9600
Android LayoutInflater 源码解析
LayoutInflater 源码解析及应用(解决插件化中类型转换异常)
这里明显全路径相同,那就是加载这两个类的 ClassLoader 不同,验证一下:
trampcr
2019/11/27
1.4K0
爆表!RecyclerView性能提升200%,异步预加载大杀器!
首先需要强调的是,这篇文章是对我之前写的《浅谈RecyclerView的性能优化》文章的补充,建议大家先读完这篇文章后再来看这篇文章,味道更佳。
xuexiangjys
2023/09/01
2.1K0
爆表!RecyclerView性能提升200%,异步预加载大杀器!
源码分析 | 布局文件加载流程
setContentView 方法如下所示,调用的是 window 中的 setContentView,但是 window 中的只是一个抽象方法:
345
2022/02/11
5050
源码分析 | 布局文件加载流程
Android fragment 标签加载过程分析
在上一篇文章中我们介绍了 AsyncLayoutInflater 使用的注意事项及改进方案。
用户2898788
2018/09/28
1.8K0
Android fragment 标签加载过程分析
绘制优化
过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,会导致某些像素区域被绘制了多次,同时也会浪费大量的 CPU 以及 GPU 资源。 在 Android 手机的开发者选项中,有一个『调试 GPU 过度绘制』的选项,该选项开启之后,手机显示如下,显示出来的蓝色、绿色的色块就是过度绘制信息。
Yif
2020/04/23
9240
绘制优化
深入理解LayoutInflater.inflate()
形如 LayoutInflater.from(context).inflate(R.layout.test,root,true) 的使用在android开发中很常见,但许多人不但不清楚LayoutInflater的inflate()方法的细节,而且甚至在误用它。
见得乐
2022/09/08
8870
LayoutInflater 布局渲染工具原理分析
LayoutInflater其实是一个布局渲染工具,其本质就只是一个工具,说白了LayoutInflater的作用就是根据xml布局文件构建View树,自定义View的时候经常用到,常用的做法如下: View tmpView= LayoutInflater.from(context). inflate(R.layout.content,container,false); 首先通过LayoutInflater.from静态函数获得一个LayoutInflater实例,其实是是个PhoneLayoutInfl
用户1269200
2018/02/01
5610
Android UI布局优化之ViewStub[通俗易懂]
尊重原创,转载请注明出处:http://blog.csdn.net/a740169405/article/details/50351013
全栈程序员站长
2022/09/13
1.3K0
Android UI布局优化之ViewStub[通俗易懂]
Android LayoutInflater的用法详解
相信我们在开发过程中肯定接触过LayoutInflater,比如ListView的适配器里的getView方法里通过LayoutInflater.from(Context).inflater来加载xml布局,在Fragment里的onCreateView里面也是一样,加载布局一共三种方法。 1,在Activity里面调用getLayoutInflater() 2, 通过LayoutInflater.from(context).inflater() 3, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) 以上的三种方式从实现上都是一样的,Activity里面的getLayoutInflater()实际上调用的是PhoneWindow的实现,而PhoneWindow里源码的处理是LayoutInflater.from(context).inflater(),往下查找最终调用context.getSystemService。 context.getSystemService是Android里一个比较重要的api,是Activity的一个方法,根据传入的Name来取得对应的Object,然后转换成相应的服务对象。以下是系统相应的服务。 传入的Name返回的对象说明 WINDOW_SERVICE WindowManager 管理打开的窗口程序 LAYOUT_INFLATER_SERVICE LayoutInflater 取得xml里定义的view ACTIVITY_SERVICE ActivityManager 管理应用程序的系统状态 POWER_SERVICE PowerManger 电源的服务 ALARM_SERVICE AlarmManager 闹钟的服务 NOTIFICATION_SERVICE NotificationManager 状态栏的服务 KEYGUARD_SERVICE KeyguardManager 键盘锁的服务 LOCATION_SERVICE LocationManager 位置的服务,如GPS SEARCH_SERVICE SearchManager 搜索的服务 VEBRATOR_SERVICE Vebrator 手机震动的服务 CONNECTIVITY_SERVICE Connectivity 网络连接的服务 WIFI_SERVICE WifiManager Wi-Fi服务 TELEPHONY_SERVICE TeleponyManager 电话服务
萬物並作吾以觀復
2018/09/13
9120
android学习笔记----ListView和各种适配器简介
将数据库显示到ListView的小Demo源码地址:https://github.com/liuchenyang0515/ListView_DataBase
砖业洋__
2023/05/06
2.5K0
android学习笔记----ListView和各种适配器简介
Android setContentView流程[通俗易懂]
创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null
全栈程序员站长
2022/09/13
8210
你真懂的ViewStub,include,merge么
注意事项 使用include最常见的问题就是findViewById查找不到目标控件,这个问题出现的前提是在include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。例如上述例子中,include时设置了该布局的id为my_title_ly,而my_title_layout.xml中的根视图的id为my_title_parent_id。此时如果通过findViewById来找my_title_parent_id这个控件,然后再查找my_title_parent_id下的子控件则会抛出空指针。代码如下 :
老马的编程之旅
2022/06/22
4620
Android LayoutInflater Factory 源码解析
在上一篇文章《Android LayoutInflater 源码解析》中我们说到 View 的 inflate 中有一个方法 createViewFromTag,会首先尝试通过 Factory 来 CreateView。
用户2898788
2018/08/21
8681
Android LayoutInflater Factory 源码解析
ViewStub详解
注:其实也可以也可以用动态添加的方法添加View:在java/kotlin代码中动态初始化View,然后添加到对应的viewgroup中。
全栈程序员站长
2022/09/13
7600
Android View 源码解析(二) - LayoutInflater
之前我们分析了setContentView方法的相关代码 接下来说说LayoutInflater的方法
Android架构
2019/06/25
5280
推荐阅读
相关推荐
Android AsyncLayoutInflater 源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验