AsyncLayoutInflater异步加载布局

QuibblerQuibbler 2021-02-22 约 16 分钟 3666 次阅读

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()中加载布局直接调用AppCompatActivity的setContentView(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用到列表适配器Adapter的getView()方法中去加载复杂列表?看来是不可取的,初始滑动列表一下子来十几个任务入队,超出十个的加载任务被丢弃。所以还是直接复用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);
    }

参考资料:

         Jetpack > Libraries > Asynclayoutinflater">Android Developers > Jetpack > Libraries > Asynclayoutinflater

         Docs > Reference > AsyncLayoutInflater">Android Developers > Docs > Reference > AsyncLayoutInflater

         Docs > Reference > AsyncLayoutInflater.OnInflateFinishedListener">Android Developers > Docs > Reference > AsyncLayoutInflater.OnInflateFinishedListener

        

相关推荐

精选
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