背景 本文是在工作中对App启动耗时中页面展现耗时的一个优化,特意记录优化方案和遇到的问题。
思路 关于view的异步inflate,官方给出了一个方案:AsyncLayoutInflater 。对其原理感兴趣的朋友可以看下这篇文章:https://juejin.cn/post/6844904170508681224 
关于如何减少ViewHolder的inflate时间,基于官方的方法,我做了两套方案:
1.异步初始化viewholder和 2.提前初始化viewholder private  var  sUseAsyncInflate = false private  var  sUseAheadInflate = false fun  init (application: Application ?)     if  (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {                   aheadInflateView(application, sFirstScreenTypes)         sUseAheadInflate = true      } else  {                  sUseAsyncInflate = true      } } 
一、提前初始化viewholder 提前初始化比较简单,只需要在首页展示之前初始化好对应的viewholder,我的方案细节如下:
在Application里仅异步初始化首屏viewholder 
AsyncLayoutInflater异步初始化需要指定一个ViewParent,这种场景是Recyclerview 
Recyclerview必须指定一个LayoutManager,这里和首页一样,使用的LinearLayoutManager 
异步初始化完成后,需要移除itemview的parent 
 
private  var  sFirstScreenTypes: IntArray = intArrayOf(     CommonConstants.card_show_subtype_13,     CommonConstants.card_show_subtype_551,     CommonConstants.card_show_subtype_14 ) private  fun  aheadInflateView (application: Context ?, viewTypes: IntArray )     sFirstScreenTypes = viewTypes     sAsyncLayoutInflater = MyAsyncLayoutInflater(application!!)     sFakeParent = RecyclerView(application)     sFakeParent!!.layoutManager = LinearLayoutManager(application)     for  (viewType in  viewTypes) {         val  model = ViewHolderTypeManager.transformViewHolder(viewType)          var  resid = model.layoutId         if  (resid < 0 ) {             resid = CommonGlobalContext.getAppContext().resources.getIdentifier(                 model.layout, "layout" ,                 CommonGlobalContext.getAppContext().packageName             )         }         sAsyncLayoutInflater!!.inflate(             resid, sFakeParent, viewType, null          ) { view, resid, parent, viewType, fakeViewHolder ->             if  (null  != view.parent) {                  (view.parent as  ViewGroup).removeView(view)             }             DebugLog.d(TAG, "aheadInflateView 完成 $viewType " )             saveAsyncInflateView(application, viewType, view)          }     } } private  fun  saveAsyncInflateView (context: Context , type: Int , view: View ?) Int  {    if  (!typeToContainer.containsKey(type)) {         val  container = ViewHolderContainer(context, type, mReferenceQueue)         container.position = -1          container.isFake = false          container.view = view         typeToContainer[type] = container     } else  {              }     return  -1  } 
ViewHolderContainer 是一个链表类,内部有一个next指针,所以typeToContainer其实是一个type对应一个链表private  val  typeToContainer: MutableMap<Int , ViewHolderContainer?> = HashMap() 
使用的时候直接在onCreateViewHolder中判断即可
二、异步初始化viewholder 异步初始化稍微复杂一点,思路是需要先绑定一个fake viewholder,等 real viewholder infalte完成在替换为real viewholder
基本原理如下:
1.区分占位viewholer和原viewholder 因为是异步的,在real view没有inflate完成时,需要展示一个占位的view。占位view的viewtype = 原viewtype * (-1) 
2.原viewholder inflate完成后,替换掉占位viewholer 占位view在原本view inflate完成后,需要被替换,需要保存占位view的位置,然后更新为 real view
实现方案:
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(type, 0 );  
2.因为不缓存,所以在调用 notifyItemChanged(int position) 时会重新走到 onCreateViewHolder  
简单说下 Recyclerview 分为四级缓存,1.mChangedScrap 2.mAttachedScrap、mCachedViews 3.mViewCacheExtension 4.mRecyclerPool
更多Recyclerview缓存相关的细节分析,请看我的这篇文章:RecyclerView 缓存机制 
下面贴一下大概的实现方案:
@Override public  int  getItemViewType (int  position)     int  type = ViewHolderTypeManager.transformViewHolder((Card) mDataList.get(position)).getType();        if  (mUseAsyncInflate && AsyncInflateHelper.INSTANCE.isFake(mContext, type, originPos, false )) {         type *=-1 ;         mRecyclerView.getRecycledViewPool().setMaxRecycledViews(type, 0 );      } } public  RecyclerView.ViewHolder onCreateViewHolder (@NonNull ViewGroup parent, int  viewType)   {    boolean  useFake = mUseAsyncInflate;     if (useFake) {         View view = AsyncInflateHelper.INSTANCE.getContainerForType(viewType);         if  (!isFake) {             if  (null  != view) {                 DebugLog.d("ConorLee" , "use async inflate viewtype is "  + viewType);                 tmpViewHolder = (BaseNewViewHolder) getViewHolder(view, model.getClassName());             } else  {                 useFake = false ;             }         } else  {             View viewGroup = LayoutInflater.from(mContext).inflate(R.layout.common_album_item_default, parent, false );             tmpViewHolder = new  EmptyViewHolder(mContext, viewGroup);             DebugLog.d("ConorLee" , "fake onCreate is  " + viewType);             int  layoutId = model.getLayoutId();             if  (layoutId < 0 ) {                 layoutId = CommonGlobalContext.getAppContext().getResources().getIdentifier(model.getLayout(), "layout" ,                         CommonGlobalContext.getAppContext().getPackageName());             }             MyAsyncLayoutInflater asyncLayoutInflater = new  MyAsyncLayoutInflater(mContext);             asyncLayoutInflater.inflate(layoutId, parent, viewType, tmpViewHolder, new  MyAsyncLayoutInflater.OnInflateFinishedListener() {                 @Override                  public  void  onInflateFinished (@NonNull View view, int  resid, @Nullable ViewGroup parent, int  viewType, BaseNewViewHolder fakeViewHolder)                       DebugLog.d("ConorLee" , "async inflate complete  " + viewType);                      int  pos = AsyncInflateHelper.INSTANCE.updatePosAndView(viewType, view);                     notifyItemChanged(pos);                      if  (pos == -1 ) {                         DebugLog.d("ConorLee" , "async inflate error " + viewType);                     }                 }             });         }     } } 
在onBindViewHolder()方法中如果发现holder是fake,那么可以直接return,显著减少onBind时间public  void  onBindViewHolder (@NonNull RecyclerView.ViewHolder holder, int  position, @NonNull List<Object> payloads)      int  viewHolderType = holder.getItemViewType();     if  (mUseAsyncInflate && viewHolderType < 0 ) {         AsyncInflateHelper.INSTANCE.isFake(mContext, viewHolderType, position, true );          return ;     }      } 
这里再说明一下:
fakeviewholder是在onCreateViewHolder中创建,并且在onBindViewHolder和position绑定,绑定之后由前面的typeToContainer类保存。 
异步初始化完成后,根据type在typeToContainer中找到第一个fakeviewholder,拿到位置并更新 
 
跨页面缓存相同type的ViewHolder,如何回收? 可以参考LeakCanary检测Activity是否泄漏的方法,检测viewholder的View或Context是否被回收。
用弱引用(WeakReference)包裹实体类ViewHolderContainer,然后给这个弱引用绑定一个引用队列。 
如果弱引用的内容被回收,该弱引用会被加入到引用队列。然后某个时间点遍历引用队列,回收ViewHolderContainer 
因为Activity生命周期的原因,如果在onDestroy里立刻去遍历引用队列,可能还未回收,所以onDestroy方法不是一个完美的时间点 
建议可以在上一个页面的onResume方法中延迟去做 
 
清除的方法大概如下:
fun  clearViewContainer ()     var  needLoop = true      while  (needLoop) {         val  ref = mReferenceQueue.poll() as  KeyedWeakReference<*>?         ref?.let {             var  head = typeToContainer[ref.viewHolderType]             var  prev: ViewHolderContainer? = null              while  (head != null ) {                 if  (head.mKeyedWeakReference.get () == null ) {                     DebugLog.d(TAG, "页面退出,回收container type is "  + ref.viewHolderType + " pos is  "  + ref.viewHolderPos)                                          if  (null  == prev) {                         head = head.next                         typeToContainer[ref.viewHolderType] = head                     } else  {                         prev.next = head.next                     }                     break                  }                 prev = head                 head = head.next             }         } ?: let{ needLoop = false }     } } 
结论 采用这两种方式,可以显著减少viewholder的onCreateViewHolder和onBindViewHolder这两个方法的耗时,从而页面整体展现可以较以前提高100~200ms
不足: 
1.异步初始化完成后,其实应该根据当前列表的firstvisiblepos和lastvisiblepos找到对应的fakeviewholder,这样效率更高。目前为了实现简单,每次异步完成都是遍历列表,拿第一个fakeviewholder,儿没有判断是否可见 
2.不支持在viewholder中new Handler(), 因为底层用的官方AsyncLayoutInflater,初始化view在子线程,此时new Handler()会抛出异常 
 
Ref View 的异步 Inflate+ 全局缓存:加速你的页面 
本文链接:http://agehua.github.io/2022/07/21/async-inflate-strategy/ 
            
  
  
  
  
  
      
      ------------------------------------------------------------------------------------------------------------------------------
      
    
      Enjoy it ? Donate me !  欣赏此文?求鼓励,求支持!
     
   
  
  
  
        
      ------------------------------------------------------------------------------------------------------------------------------
      
      
      
           
          
              Enjoy it ? Donate me !  欣赏此文?求鼓励,求支持!