AsyncLayoutInflater异步加载布局

Quibbler 2021-2-22 1816

AsyncLayoutInflater异步加载布局


        提到应用性能优化,首先就是布局优化:布局臃肿、层级太深等等会导致绘制卡顿,严重的话影响用户体验。在布局优化一文中,提到布局优化相关的知识。

        如果你的应用布局实在是太复杂加载很耗时,甚至造成应用的卡顿,该如何优化呢?思路就是将耗时的布局加载放到子线程中,加载完毕再回到主线程显示,这就是AsyncLayoutInflater的做法。



1、AsyncLayoutInflater

        AsyncLayoutInflater用的很少,因为涉及到异步多线程,还是和View加载相关的,玩不好会出问题。应当尽量把应用布局、性能优化好,避免使用AsyncLayoutInflater


1.1、介绍

        AsyncLayoutInflater是用于异步加载布局的帮助类。内部定义了布局加载完成时的回调接口OnInflateFinishedListener。关于AsyncLayoutInflater的源码,详见AsyncLayoutInflater源码颇析

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


1.2、依赖

        AsyncLayoutInflater属于Jetpack组件库,多数情况下在使用其他AndroidX库的时候会被其它依赖添加到项目中。也可以手动添加下面的依赖到build.gradle中。

    dependencies {
        ...
        //Asynclayoutinflater
        implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
    }



2、AsyncLayoutInflater简单使用

        以前Activity在onCreate()中加载布局直接调用AppCompatActivitysetContentView(int )添加布局,如果布局复杂就会因为布局加载而卡顿,通常我们这样在Activity中设置layout:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);
    }

 

2.1、AsyncLayoutInflater异步加载布局

       如果各种布局优化的手段都使用后,效果仍然不佳,这时候AsyncLayoutInflater就派上用场了:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        new AsyncLayoutInflater(this).inflate(R.layout.activity, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                setContentView(view);
            }
        });
        
    }

        看到没,使用起来很简单:

        传入Context构造AsyncLayoutInflater实例(必须在UI线程创建实例)

        再调用它的inflate()方法,传入要加载的xml布局资源id、父布局(可空)、以及OnInflateFinishedListener回调接口(非空)

        OnInflateFinishedListener回调接口中将布局设置给界面显示,UI线程中回调该方法


2.2、setContentView(...)

        setContentView(...)方法有几个重载,它们在AppCompatActivity中的定义如下:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }
    
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

        都是通过AppCompatDelegate抽象代理类完成布局的添加,AppCompatDelegate的实现类是AppCompatDelegateImpl。当我们传入的参数是int类型的布局xml资源id时,需要先解析xml布局,这是耗时操作。

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        //先解析xml布局,这是耗时的操作
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

        但是当我们借助AsyncLayoutInflater在子线程中先加载完布局,直接将View传入setContentView(...)方法就可以替主线程省去一大笔性能开销。

    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

        


3、注意坑点

        似乎AsyncLayoutInflater的用途不广,是的,别指望它能解决应用布局加载的卡顿问题。看了源码就会更加清楚它内部的实现机制,无非就是:阻塞队列 -> 线程 -> Handler模型。Android中很多优秀的开源库都有这样的设计。

        

3.1、最大10个布局加载任务

        查看源码可以看到,内部设置了异步加载阻塞队列的大小为10,超过这个数量无法将任务加入到队列。虽然不会引起主线程阻塞,但是超出数量的任务会被丢弃,因为内部做了异常捕获。

    private static class InflateThread extends Thread {
        ...
        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
        
        ...
        public void enqueue(InflateRequest request) {
            try {
                mQueue.put(request);
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "Failed to enqueue async inflate request", e);
            }
        }
        
    }

        能不能将AsyncLayoutInflater用到列表适配器AdaptergetView()方法中去加载复杂列表?看来是不可取的,初始滑动列表一下子来十几个任务入队,超出十个的加载任务被丢弃。所以还是直接复用convertView优化列表最方便。


3.2、避免使用Looper或Handler

        因为布局是在子线程中解析加载的,所以构建的View中必须不能直接使用 Handler 或者是调用 Looper.myLooper(),因为异步线程默认没有调用 Looper.prepare()。不过却可以使用下面的方式获取主线程Handler,关于Handler的构造,详见博客创建Handler的几种方法

    Handler handler = new Handler(Looper.getMainLooper())


3.3、attachToRoot默认false

        使用AsyncLayoutInflater加载布局,内部默认异步inflate转换出来的 View 并没有被加到parent中,必须在回调中手动添加;

    request.view = request.inflater.mInflater.inflate(request.resid, request.parent, false);


3.4、不支持Fragment

        继续往下看是不是不想用AsyncLayoutInflater了?没想到缺憾这么多。包换Fragment的layout布局无法使用。注释里

     /* <p>This inflater does not support setting a {@link LayoutInflater.Factory}
      * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
      * layouts that contain fragments.
      */


3.5、不支持Factory和Factory2

        上面的注释也提到了。关于Factory和Factory2以后在深入了解,很多开发者应该都没有使用过。其实这是Android提供的一种hook的方法,开发者通过Factory可以拦截LayoutInflater创建View的过程。

        应用场景:XML布局中自定义标签名称;全局替换系统控件为自定义View;替换应用字体;全局换肤。

    public interface Factory {
        /**
         * Hook you can supply that is called when inflating from a LayoutInflater.
         * You can use this to customize the tag names available in your XML
         * layout files.
         */
        View onCreateView(String name, Context context,AttributeSet attrs);
    }
    
    public interface Factory2 extends Factory {
        /**
         * Version of {@link #onCreateView(String, Context, AttributeSet)}
         * that also supplies the parent that the view created view will be
         * placed in.
         */
        View onCreateView(View parent,String name,Context context,AttributeSet attrs);
    }



参考资料:

        Android Developers > Jetpack > Libraries > Asynclayoutinflater

        Android Developers > Docs > Reference > AsyncLayoutInflater

        Android Developers > Docs > Reference > AsyncLayoutInflater.OnInflateFinishedListener

        

不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com