写点什么

修复一个 BaseRecyclerViewAdapterHelper 漏洞

作者:Changing Lin
  • 2021 年 11 月 25 日
  • 本文字数:5746 字

    阅读完需:约 19 分钟

1.问题

  • BaseRecyclerViewAdapterHelper 简介:强大而灵活的 RecyclerView Adapter,就是封装了若干 RecyclerView 的适配器的接口和方法,从而简化 Adapter 的实现,提升应用的开发效率,是一个比较流行的项目。

https://github.com/CymChad/BaseRecyclerViewAdapterHelper
复制代码
  • 导入 BaseRecyclerViewAdapterHelper 方法:

implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
复制代码
  • 存在的问题:在 Adapter 的 convert 方法中,调用 BaseViewHolder.setText 方法报了 NullPointException 错误。

2.例程

  • 自定义 Adapter 源码:

public class RecordAdapter extends BaseQuickAdapter<RecordBean, BaseViewHolder> {    private static final int ITEM_WIDTH = (int) AppInfoProvider.dipToPixels(150);    public RecordAdapter(int layoutResId, @Nullable List<RecordBean> data) {        super(layoutResId, data);    }
@Override protected void convert(BaseViewHolder helper, RecordBean item) { helper.setText(R.id.nick_name, item.getUserName());
// helper.setText(R.id.chat_from_voice_length, String.valueOf(item.getFileName())); // 错误代码,NullPointException int type = helper.getItemViewType();
ViewGroup chatContentLayout = helper.getView(R.id.chat_content_layout); if (type == 0) { if (null != chatContentLayout) { View progress = chatContentLayout.findViewById(R.id.progress); if (null != progress) progress.setVisibility(View.GONE); TextView textView = chatContentLayout.findViewById(R.id.chat_from_voice_length); if (null != textView) { textView.getLayoutParams().width = ITEM_WIDTH; textView.requestLayout(); textView.setText(String.valueOf(item.getFileName())); } View chatFromVoiceAnim = chatContentLayout.findViewById(R.id.chat_from_voice_anim); if (null != chatFromVoiceAnim) chatFromVoiceAnim.setVisibility(View.GONE); View chatFromWarpView = chatContentLayout.findViewById(R.id.chat_from_warp_view); if (null != chatFromWarpView) chatFromWarpView.setOnClickListener(v -> { if (getOnItemChildClickListener() != null) { getOnItemChildClickListener().onItemChildClick(RecordAdapter.this, v, helper.getLayoutPosition()); } }); } }else { if (null != chatContentLayout) { View progress = chatContentLayout.findViewById(R.id.voice_progress); if (null != progress) progress.setVisibility(View.GONE); View unreadImgView = chatContentLayout.findViewById(R.id.unread_img_view); if (null != unreadImgView) unreadImgView.setVisibility(View.GONE); TextView textView = chatContentLayout.findViewById(R.id.chat_to_voice_length); if (null != textView) { textView.getLayoutParams().width = ITEM_WIDTH; textView.requestLayout(); textView.setText(String.valueOf(item.getFileName())); } View chatFromVoiceAnim = chatContentLayout.findViewById(R.id.chat_to_voice_anim); if (null != chatFromVoiceAnim) chatFromVoiceAnim.setVisibility(View.GONE); View chatToWarpView = chatContentLayout.findViewById(R.id.chat_to_warp_view); if (null != chatToWarpView) chatToWarpView.setOnClickListener(v -> { if (getOnItemChildClickListener() != null) { getOnItemChildClickListener().onItemChildClick(RecordAdapter.this, v, helper.getLayoutPosition()); } }); } } }}
复制代码
  • BaseViewHolder 对应的布局文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="vertical" >
<TextView android:id="@+id/time_tv" style="@style/ChattingUISystem" android:layout_gravity="center_horizontal" android:layout_marginTop="5dp" android:background="@drawable/chatsystem_bg" android:gravity="center" />
<RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal" android:padding="3dip" android:layout_marginHorizontal="@dimen/item_horizontal_margin" >
<ImageView android:id="@+id/chat_head_iv" style="@style/chat_head_tv" android:layout_alignParentRight="true" />
<TextView android:id="@+id/nick_name" style="@style/chat_from_nick_name" />
<RelativeLayout android:id="@+id/chat_content_layout" android:layout_width="275dip" android:layout_height="wrap_content" android:layout_below="@+id/nick_name" android:layout_toLeftOf="@+id/chat_head_iv" >
<LinearLayout android:id="@+id/chat_from_warp_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_marginRight="4dp" android:background="@drawable/chat_from_warp_bg" android:backgroundTint="@color/chat_from_bg_color" android:clickable="true" android:focusable="true" android:gravity="center" android:orientation="horizontal" >
<LinearLayout android:id="@+id/chat_from_voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="left" android:orientation="horizontal" >
<ImageView android:id="@+id/chat_from_voice_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:src="@drawable/voice_paly_right_3" />
<ImageView android:id="@+id/chat_from_voice_anim" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:background="@drawable/voice_play_right" /> </LinearLayout>
<TextView android:id="@+id/chat_from_voice_length" android:layout_width="40dp" android:layout_height="wrap_content" android:gravity="center" android:singleLine="true" android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/SmallerTextSize" /> </LinearLayout>
<ProgressBar android:id="@+id/progress" style="@android:style/Widget.ProgressBar.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="3dp" android:layout_toLeftOf="@id/chat_from_warp_view" />
<ImageView android:id="@+id/failed_img_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="3dp" android:layout_toLeftOf="@id/chat_from_warp_view" android:background="@drawable/im_send_failed_bg" android:visibility="gone" />
<ImageView android:id="@+id/imageview_read" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="1dp" android:layout_marginTop="3dp" android:layout_toLeftOf="@id/chat_from_warp_view" android:background="@drawable/send_to" android:visibility="gone" /> </RelativeLayout> </RelativeLayout>
</LinearLayout>
复制代码

3.原理分析

  • 首先查看 BaseViewHolder.setText 方法:

// viewId = R.id.chat_from_voice_length,我们想找到上面layout中的chat_from_voice_length的TextViewpublic BaseViewHolder setText(@IdRes int viewId, CharSequence value) {  TextView view = getView(viewId);  view.setText(value);  return this;}
复制代码
  • 继续查看 BaseViewHolder.getView 方法:

public <T extends View> T getView(@IdRes int viewId) {  View view = views.get(viewId); // 可以看到这是一个缓存机制,仅在第一次时查找viewId对应的控件  if (view == null) {    view = itemView.findViewById(viewId); // 在itemView中查询R.id.chat_from_voice_length    views.put(viewId, view);  }  return (T) view;}
复制代码
  • 继续查看 itemView 是怎么生成的

public abstract static class ViewHolder {  @NonNull  public final View itemView; // 可以看到 itemView 是RecyclerView.ViewHolder中的成员  ...  public ViewHolder(@NonNull View itemView) { // 并且在BaseViewHoler实例化的时候会传递进来    if (itemView == null) {      throw new IllegalArgumentException("itemView may not be null");    }    this.itemView = itemView;  }}
复制代码
  • 回到 BaseQuickAdapter 中:

		protected K createBaseViewHolder(ViewGroup parent, int layoutResId) {        return createBaseViewHolder(getItemView(layoutResId, parent));    }         protected View getItemView(@LayoutRes int layoutResId, ViewGroup parent) {        return mLayoutInflater.inflate(layoutResId, parent, false);    }
复制代码
  • 由上面分析可知,itemView.findViewById 其实相当于 View.findViewById

 // View.java        @Nullable    public final <T extends View> T findViewById(@IdRes int id) {        if (id == NO_ID) {            return null;        }        return findViewTraversal(id);    }				protected <T extends View> T findViewTraversal(@IdRes int id) {        if (id == mID) {            return (T) this; // 即表示 itemView只能找到自己,或者这个方法是一个虚拟的,可被子类重写        }        return null;    }    
复制代码
  • 查找 View 的子类,itemView 其实应该是一个 ViewGroup,

// ViewGroup.java		@Override    protected <T extends View> T findViewTraversal(@IdRes int id) {        if (id == mID) {            return (T) this;        }
final View[] where = mChildren; final int len = mChildrenCount;
for (int i = 0; i < len; i++) { // 遍历当前ViewGroup所有子View,并递归查找,直到找到为止 View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id);
if (v != null) { return (T) v; } } }
return null; }
复制代码
  • 那么问题来了,由于面向对象编程的多态特性,在执行 View.findViewTraversal 时,理论上会调用子类 ViewGroup.findViewTraversal 方法,在 layout 文件中,应该可以找到 R.id.chat_from_voice_length 才对。那为什么还会报错呢?

发布于: 2021 年 11 月 25 日阅读数: 7
用户头像

Changing Lin

关注

获得机遇的手段远超于固有常规之上~ 2020.04.29 加入

我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。

评论

发布
暂无评论
修复一个BaseRecyclerViewAdapterHelper漏洞