布局加载过程中存在两个耗时点:
布局文件读取慢,涉及IO操作
根据<tag>
创建View慢(createViewFromTag()
),使用反射的方式创建View。布局嵌套层数越多,控件个数越多,反射的次数就会越多 。
当XML文件过大、嵌套过深时,就会导致页面发生卡顿甚至ANR。
解决方案有两种:
直接解决 :不使用IO操作以及反射
侧面缓解 :把耗时操作放到子线程,等待加载完毕返回主线程展示即可。下面提到的AsyncLayoutInflater
就是使用这个方案。
AsyncLayoutInflater 采用异步加载 的方式去加载布局,可以节省主线程时间,并且在异步加载完毕后回到主线程。
使用方法 1 2 3 4 5 6 new AsyncLayoutInflater(this ).inflate(R.layout.XX, null , new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished (@NonNull View view, int i, @Nullable ViewGroup viewGroup) { setContentView(view); } });
基本原理 构造方法 1 2 3 4 5 6 7 8 9 10 11 public final class AsyncLayoutInflater { LayoutInflater mInflater; Handler mHandler; InflateThread mInflateThread; public AsyncLayoutInflater (@NonNull Context context) { mInflater = new BasicInflater(context); mHandler = new Handler(mHandlerCallback); mInflateThread = InflateThread.getInstance(); } }
BasicInflater 自定义加载器。实现类似PhoneLayoutInflater(默认布局加载器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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) { } } return super .onCreateView(name, attrs); } }
InflateThread 创建子线程,将布局加载
请求加入阻塞队列
中,按照插入顺序执行LayoutInflater.inflate()
加载过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 private static class InflateThread extends Thread { private static final InflateThread sInstance; static { sInstance = new InflateThread(); sInstance.start(); } private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10 ); private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10 ); public InflateRequest obtainRequest () { InflateRequest obj = mRequestPool.acquire(); if (obj == null ) { obj = new InflateRequest(); } return obj; } 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); } } public void runInner () { InflateRequest request; try { request = mQueue.take(); } catch (InterruptedException ex) { Log.w(TAG, ex); return ; } try { request.view = request.inflater.mInflater.inflate( request.resid, request.parent, false ); } catch (RuntimeException ex) { 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(); } } }
InflateThread
不管最后inflate()
执行成功或失败,都会把结果发回到Handler进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private Handler.Callback mHandlerCallback = new Handler.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 ; } };
Handler
收到消息后,根据InflateRequest.view
是否为空,判断接下执行步骤:
如果为空,回到主线程进行布局加载任务,加载完成后回调onInflateFinished()
不为空,直接回调onInflateFinished()
inflate() 发起异步加载布局请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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); }
InflateRequest 主线程和子线程之间传递的数据模型,主要封装了异步加载
需要的参数
1 2 3 4 5 6 7 8 9 10 private static class InflateRequest { AsyncLayoutInflater inflater; ViewGroup parent; int resid; View view; OnInflateFinishedListener callback; InflateRequest() { } }
OnInflateFinishedListener 布局加载完成后回调
1 2 3 4 5 public interface OnInflateFinishedListener { void onInflateFinished (@NonNull View view, //加载完成的View @LayoutRes int resid, @Nullable ViewGroup parent) ; }
使用AsyncLayoutInflater
加载布局后,将需要加载的layoutId
以及OnInflateFinishedListener
构造成InflateRequest
,插入到InflateThread
的阻塞队列中,等待执行。任务执行完毕后,返回执行结果(成功返回加载后的View,失败返回null
)。
通过Handler
发送结果回到主线程,返回结果为null
,则在主线程再次执行布局加载
,得到结果后直接回调onInflateFinished()
。
局限及改进 局限
AsyncLayoutInflater
构造的View
,无法直接使用handler
或者调用looper.myLooper
,因为没有进行初始化
AsyncLayoutInflater
构造的View
,不会自动加到parent
中,需要手动加入
AsyncLayoutInflater
不支持设置Factory/Factory2
,未设置mPrivateFactory
所以不支持包含<fragment>
的布局
最多支持10个布局加载,超出的布局需要等待。
改进
AsyncLayoutInflater
是final
的,无法被继承。需要copy
一份代码进行修改。
针对4
可以内部替换成线程池,将加载布局请求放入线程池管理
针对3
可以修改BasicInflater
实现,内部支持factory
设置
1 2 3 4 5 6 7 8 9 10 11 BasicInflater(Context context) { super (context); if (context instanceof AppCompatActivity) { AppCompatDelegate appCompatDelegate = ((AppCompatActivity) context).getDelegate(); if (appCompatDelegate instanceof LayoutInflater.Factory2) { LayoutInflaterCompat.setFactory2(this , (LayoutInflater.Factory2) appCompatDelegate); } } }
参考链接 AsyncLayoutInfalter