调用RecyclerView更新方法时getContext()致App崩溃求助
问题现象
FATAL EXCEPTION: main
Process: com.akstudios.kindergarten, PID: 15680
java.lang.NullPointerException: 尝试对空对象引用调用虚拟方法 'void androidx.recyclerview.widget.RecyclerView.setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager)'
at com.akstudios.kindergarten.GroupbookKrippeFragment.setRecyclerView(GroupbookKrippeFragment.java:56)
访问GroupbookKrippeFragment时频繁触发上述空指针崩溃,尝试过getContext()、onAttach()、GroupbookKrippeFragment.this.getContext()等方式获取上下文,问题依旧。
问题排查
App原本运行稳定,修改以下代码后出现崩溃:
- 在
GroupbookKrippeFragment.java中封装setRecyclerView方法用于更新RecyclerView:
public void setRecyclerView(List<ChildrenTable> childrenTableList) { recycler_view.setLayoutManager(new LinearLayoutManager(getContext())); childAdapter = new ChildAdapter(getContext(), childrenTableList); // childAdapter = new ChildAdapter(getContext()); recycler_view.setAdapter(childAdapter); }
- 在
DatabaseHelper.java的AllChildrenAsyncTask的onPostExecute()中调用该方法:
public class DatabaseHelper { Context context; GroupbookKrippeFragment groupbookKrippeFragment = new GroupbookKrippeFragment(); // ... 其他代码省略 // Show all data from ChildrenTable public void getAllChildrenData() { class AllChildren extends AsyncTask<Void, Void, List<ChildrenTable>> { @Override protected List<ChildrenTable> doInBackground(Void... voids) { List<ChildrenTable> list = DatabaseClient.getInstance(context) .getChildrenDatabase() .childrenDAO() .selectAll(); return list; } @Override protected void onPostExecute(List<ChildrenTable> childrenTable) { super.onPostExecute(childrenTable); if (childrenTable != null && childrenTable.size() > 0) { groupbookKrippeFragment.setRecyclerView(childrenTable); } } } AllChildren allChildren = new AllChildren(); allChildren.execute(); } }
注释掉该调用,将代码移到GroupbookKrippeFragment的onViewCreated中,App恢复稳定,但RecyclerView无法显示和更新数据库数据。
核心原因
DatabaseHelper中直接new GroupbookKrippeFragment()创建的实例,不是当前界面正在显示的Fragment实例。这个新实例的onCreateView从未被调用,recycler_view自然没有被初始化,调用setRecyclerView时就会触发空指针。
解决方案
方案1:接口回调传递数据
- 定义数据回调接口:
public interface OnChildrenDataLoadedListener { void onDataLoaded(List<ChildrenTable> dataList); }
- 修改
DatabaseHelper,持有接口引用而非Fragment实例:
public class DatabaseHelper { Context context; OnChildrenDataLoadedListener listener; // 修改构造方法,传入回调接口 public DatabaseHelper(Context context, OnChildrenDataLoadedListener listener) { this.context = context; this.listener = listener; } public static DatabaseHelper getInstance(Context context, OnChildrenDataLoadedListener listener) { return new DatabaseHelper(context, listener); } // ... 其他代码省略 @Override protected void onPostExecute(List<ChildrenTable> childrenTable) { super.onPostExecute(childrenTable); if (childrenTable != null && childrenTable.size() > 0) { // 通过接口回调传递数据 listener.onDataLoaded(childrenTable); } } }
- 在
GroupbookKrippeFragment中实现接口并初始化DatabaseHelper:
public class GroupbookKrippeFragment extends Fragment implements OnChildrenDataLoadedListener { // ... 成员变量省略 @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_groupbook_krippe, container, false); // ... 初始化控件代码省略 // 传入当前Fragment作为回调实例 helper = DatabaseHelper.getInstance(getContext(), this); helper.getAllChildrenData(); return view; } // 实现接口方法 @Override public void onDataLoaded(List<ChildrenTable> dataList) { setRecyclerView(dataList); } public void setRecyclerView(List<ChildrenTable> childrenTableList) { recycler_view.setLayoutManager(new LinearLayoutManager(getContext())); childAdapter = new ChildAdapter(getContext(), childrenTableList); recycler_view.setAdapter(childAdapter); } }
方案2:LiveData自动观察数据库数据(推荐)
如果使用Room数据库,推荐用LiveData自动监听数据变化,无需手动维护AsyncTask:
- 修改DAO层,返回LiveData:
@Dao public interface ChildrenDAO { @Insert void insertData(ChildrenTable childrenTable); @Query("SELECT * FROM children_table") LiveData<List<ChildrenTable>> selectAllLive(); }
- 在
GroupbookKrippeFragment中观察数据:
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 初始化RecyclerView基础配置 recycler_view.setLayoutManager(new LinearLayoutManager(getContext())); childAdapter = new ChildAdapter(getContext(), new ArrayList<>()); recycler_view.setAdapter(childAdapter); // 观察数据库数据变化,自动更新RecyclerView DatabaseClient.getInstance(getContext()) .getChildrenDatabase() .childrenDAO() .selectAllLive() .observe(getViewLifecycleOwner(), childrenTableList -> { if (childrenTableList != null && !childrenTableList.isEmpty()) { childAdapter.updateData(childrenTableList); } }); }
- 给
ChildAdapter添加数据更新方法:
public class ChildAdapter extends RecyclerView.Adapter<ChildAdapter.ViewHolder> { private Context context; private List<ChildrenTable> dataList; public ChildAdapter(Context context, List<ChildrenTable> dataList) { this.context = context; this.dataList = dataList; } // 添加更新数据方法 public void updateData(List<ChildrenTable> newData) { dataList.clear(); dataList.addAll(newData); notifyDataSetChanged(); } // ... 其他Adapter代码省略 }
内容的提问来源于stack exchange,提问作者EXTSY

