Recyclerview 项目有时不会在应用程序启动时显示

Recyclerview items are sometimes not displayed on app launch

我有一个显示可启动应用程序列表的屏幕。该列表是通过 RxJava2 和 LiveData 的组合从 SharedPreferences 中获取的。具体来说,我在片段的 onStart 方法上观察到 LiveData<List<AppModel>> 。使用 RxJava2 成功获取此列表后,我使用 LiveData 使用列表更新 UI 并将其设置为我的 RecyclerView。

但是,我注意到有时我第一次启动应用程序时,应用程序列表已成功获取,但项目没有显示在 UI 上。这是我查看此行为的程序:

  1. 从主屏幕打开应用程序
  2. 如果项目显示成功,关闭应用程序
  3. 从最近列表中删除应用程序
  4. 启动应用程序并再次执行程序,直到项目不再显示。

出于好奇,我将观察 LiveData<List<AppModel>> 的代码移动到 onCreateView,现在每次启动应用程序时,项目都会成功显示。另外,bug只发生在API 22,我在API 27上测试过,bug没有出现。有人知道为什么会这样吗?

以下是存在项目未显示错误的代码:

1) FavoritesFragment.java(保存的应用程序列表通过 RecyclerView 显示):

public class FavoritesFragment extends Fragment {
    public static final String TAG = FavoritesFragment.class.getSimpleName();

    private FaveListAdapter faveListAdapter;
    FragmentFavoritesBinding binding;
    private List<AppModel> faveList = new ArrayList<>();

    @Inject
    public ViewModelFactory viewModelFactory;

    private FavoritesViewModel viewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Injector.getViewModelComponent().inject(this);
        super.onCreate(savedInstanceState);
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(FavoritesViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = FragmentFavoritesBinding.inflate(inflater, container, false);

        binding.button.setOnClickListener((v ->
                navController.navigate(R.id.action_favorites_dest_to_app_list_dest)));

        faveListAdapter = new FaveListAdapter(this::launchApp);
        faveListAdapter.setAppList(faveList);

        faveListAdapter.setOnDeleteItemListener(list -> {
            faveList = list;
            viewModel.saveFaveApps(faveList).observe(getViewLifecycleOwner(), this::handleSaveStatus);
            updateRecyclerView();
        });

        binding.rvNav.setLayoutManager(new LinearLayoutManager(requireContext()));
        binding.rvNav.setAdapter(faveListAdapter);
        Log.d(TAG, "onCreateView: done initial RV setup");
        updateRecyclerView();
        return binding.getRoot();
    }

    @Override
    public void onStart() {
        super.onStart();
        viewModel.loadFaveAppList().observe(this, list -> {
            faveList = list;
            faveListAdapter.swapItems(list);
            updateRecyclerView();
        });
    }

    private void updateRecyclerView() {
        Log.d(TAG, "updateRecyclerView: start");
        if(faveList.isEmpty()) {
            binding.button.setVisibility(View.VISIBLE);
            binding.frameFav.setVisibility(View.GONE);
        } else {
            binding.button.setVisibility(View.GONE);
            binding.frameFav.setVisibility(View.VISIBLE);
        }
    }

    private void launchApp(String packageName) {
       // launch selected app
    }

    private void handleSaveStatus(SaveStatus saveStatus) {
         // change UI/navigate to other screens depending on status
        }
    }
}

2) FavoritesViewModel.java(我使用 RxJava2 从存储库对象获取列表并通过 LiveData 更新 UI)

public class FavoritesViewModel extends ViewModel {

    private final PreferenceRepository preferenceRepository;
    private CompositeDisposable compositeDisposable;

    private List<String> favePackageNameList = new ArrayList<>();

    @Inject
    public FavoritesViewModel(PreferenceRepository preferenceRepository, DataRepository dataRepository) {
        this.preferenceRepository = preferenceRepository;
        this.dataRepository = dataRepository;
        compositeDisposable = new CompositeDisposable();
    }

    public LiveData<List<AppModel>> loadFaveAppList() {
        MutableLiveData<List<AppModel>> listData = new MutableLiveData<>();
        compositeDisposable.add(dataRepository.loadFavesFromPrefs()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(listData::setValue, Throwable::printStackTrace));
        return listData;
    }

    public LiveData<SaveStatus> saveFaveApps(List<AppModel> faveList) {
        MutableLiveData<SaveStatus> saveStatus = new MutableLiveData<>();
        compositeDisposable.add(dataRepository.saveFaveAppListToPrefs(faveList)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe(disposable -> saveStatus.setValue(SaveStatus.SAVING))
            .subscribe(() -> saveStatus.setValue(SaveStatus.DONE),
                    error -> {
                        error.printStackTrace();
                        saveStatus.setValue(SaveStatus.ERROR);
                    })
            );
        return saveStatus;
    }
}

3)FavoritesAdapter.java(实现上下文操作栏逻辑的RecyclerView适配器,也使用DiffUtils)

public class FaveListAdapter extends RecyclerView.Adapter<FaveListAdapter.ViewHolder> {

    public interface FaveItemClickListener {
        void onItemClick(String packageName);
    }

    public interface DeleteItemListener {
        void onDeleteClick(List<AppModel> newAppList);
    }

    private List<AppModel> appList = new ArrayList<>();
    private FaveItemClickListener onFaveItemClickListener;
    private DeleteItemListener onDeleteItemListener;
    private boolean multiSelect = false;
    private List<AppModel> selectedItems = new ArrayList<>();
    private ActionMode.Callback actionModeCallbacks = new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            multiSelect = true;
            menu.add("Delete");
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            for(AppModel app : selectedItems) {
                appList.remove(app);
            }
            if(onDeleteItemListener != null) {
                onDeleteItemListener.onDeleteClick(appList);
            }
            mode.finish();
            return true;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            multiSelect = false;
            selectedItems.clear();
            notifyDataSetChanged();
        }
    };

    public FaveListAdapter(FaveItemClickListener onFaveItemClickListener) {
        this.onFaveItemClickListener = onFaveItemClickListener;
    }

    public void setAppList(List<AppModel> appList) {
        this.appList = appList;
        notifyDataSetChanged();
    }

    public void setOnDeleteItemListener(DeleteItemListener onDeleteItemListener) {
        this.onDeleteItemListener = onDeleteItemListener;
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        private final ImageView appIcon;
        private final TextView appLabel;
        private final ConstraintLayout itemLayout;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            appIcon = itemView.findViewById(R.id.app_icon);
            appLabel = itemView.findViewById(R.id.app_label);
            itemLayout = itemView.findViewById(R.id.item_layout);
        }

        private void selectItem(AppModel app) {
            if(multiSelect) {
                if(selectedItems.contains(app)) {
                    selectedItems.remove(app);
                    itemLayout.setBackgroundColor(Color.WHITE);
                } else {
                    selectedItems.add(app);
                    itemLayout.setBackgroundColor(Color.LTGRAY);
                }
            }
        }

        private void bind(AppModel app, int i) {
            appIcon.setImageDrawable(app.getLauncherIcon());
            appLabel.setText(app.getAppLabel());

            if(selectedItems.contains(app)) {
                itemLayout.setBackgroundColor(Color.LTGRAY);
            } else {
                itemLayout.setBackgroundColor(Color.WHITE);
            }

            this.itemView.setOnClickListener(v ->{
                if(multiSelect) {
                    selectItem(app);
                } else {
                    onFaveItemClickListener.onItemClick(appList.get(i).getPackageName());
                }
            });
            this.itemView.setOnLongClickListener(v -> {
                ((AppCompatActivity) v.getContext()).startSupportActionMode(actionModeCallbacks);
                selectItem(app);
                return true;
            });
        }
    }

    @NonNull
    @Override
    public FaveListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_fave, parent, false);

        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull FaveListAdapter.ViewHolder holder, int position) {
        holder.bind(appList.get(position), position);
    }

    @Override
    public int getItemCount() {
        return appList.size();
    }

    public void swapItems(List<AppModel> apps) {
        final AppModelDiffCallback diffCallback = new AppModelDiffCallback(this.appList, apps);
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
        this.appList.clear();
        this.appList.addAll(apps);
        diffResult.dispatchUpdatesTo(this);
    }
}

片段和片段的 viewLifecycleOwner 具有不同的生命周期。 ViewLifecycleOwner 在onCreateView 中订阅,在onDestroyView 中取消订阅。 Fragment 的生命周期在 onCreate 中订阅并在 onDestroy

中取消订阅

将此代码移至 onCreateView()

viewModel.loadFaveAppList().observe(getViewLifecycleOwner, list -> { <-- change this
            faveList = list;
            faveListAdapter.swapItems(list);
            updateRecyclerView();
        });

https://proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb