更改配置后从后台线程更新 recyclerview 项目
Update recyclerview item from background thread after changing configuration
我在 Fragment
中有一个 RecyclerView
。我还有一个 AsyncTask
从后台线程更新 RecyclerView
的项目。问题是当我旋转设备(或导致任何其他配置更改)时,AsyncTask
无法更新视图。
这里可以看到简化的代码:
HomeFragment
:
public class HomeFragment extends Fragment {
public HomeFragment() {
}
public static HomeFragment newInstance() {
return new HomeFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
RecyclerView mRecyclerView = requireView().findViewById(R.id.recyclerview);
Activity activity = requireActivity();
ArrayList<String> items = new ArrayList<>(Arrays.asList("Item1", "Item2", "Item3"));
ListAdapter adapter = new ListAdapter(activity, items);
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
}
}
RecyclerView的适配器及其内部AsyncTask
class:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
private final ArrayList<String> items;
private final LayoutInflater mInflater;
private RecyclerView mRecyclerView;
public ListAdapter(Context context, ArrayList<String> items) {
mInflater = LayoutInflater.from(context);
this.items = items;
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@NonNull
@Override
public ListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View mItemView = mInflater.inflate(R.layout.item, parent, false);
return new ViewHolder(mItemView, this);
}
@Override
public void onBindViewHolder(@NonNull ListAdapter.ViewHolder holder, int position) {
holder.name.setText(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
final ListAdapter mAdapter;
final TextView name;
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
Task task = new Task();
task.execute();
});
}
}
class Task extends AsyncTask<Void, Integer, Void> {
@Override
protected void onProgressUpdate(Integer... values) {
mRecyclerView.post(() ->
((ViewHolder) Objects.requireNonNull(mRecyclerView.findViewHolderForLayoutPosition(0))).
name.setText(String.valueOf(values[0])));
mRecyclerView.invalidate();
}
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i <= 30; i++) {
publishProgress(i);
SystemClock.sleep(500);
}
return null;
}
@Override
protected void onPostExecute(Void unused) {
((ViewHolder) Objects.requireNonNull(mRecyclerView.findViewHolderForLayoutPosition(0))).name.setText("task is finished");
}
}
}
如您所见,我使用了 mRecyclerView.post()
方法来避免接触来自非 UI 线程的视图。如何在旋转设备后更新项目?
我之前尝试过的:
- 改变适配器的数据集并调用
notifyDataSetChanged()
- 在 Fragment 中创建一个方法来更新项目并从
AsyncTask
调用它
- 为 RecyclerView 重置适配器
- ...
您可以使用以下代码在屏幕旋转中更新回收器视图。
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(adapter!=null){
adapter.notifyDataSetChanged();
}
}
在 onCreate()
、onCreateView()
或 onActivityCreated()
方法中调用 setRetainInstance(true)
。
异步任务默认在后台线程上工作,我们无法从后台线程更新我们的 ui 它用于网络调用等密集型工作。请尝试 运行ning a
runOnUiThread(new Runnable() {
@Override
public void run() {
// WORK on UI thread here
}
});
异步任务的 onPostExecute 方法中的语句,因为它表示您的后台处理已完成。将更新视图的代码放入此 运行() 方法
AsyncTask
自 API 级别 30 起已弃用。它存在问题,特别是对于配置更改 per documentation:
AsyncTask was intended to enable proper and easy use of the UI thread.
However, the most common use case was for integrating into UI, and
that would cause Context leaks, missed callbacks, or crashes on
configuration changes.
而且由于这些问题,它不应该用于长时间的 运行ning 任务。
AsyncTasks should ideally be used for short operations (a few seconds
at the most.) If you need to keep threads running for long periods of
time, it is highly recommended you use the various APIs provided by
the java.util.concurrent package such as Executor, ThreadPoolExecutor
and FutureTask.
问题:
当您 运行 在 doInBackground
中执行任务时,当配置更改时,会重新创建 activity(即旧的 activity 被销毁,新的 activity 已创建);但是附加到旧 activity 的 AsyncTask
继续 运行 (导致内存泄漏);等稍后 onPostExecute()
准备好将结果发布到 UI 线程时;它会将它们释放到已销毁的 activity 而不是屏幕上显示的当前 activity;因此用户不会看到它们。因此,您的案件结果被驳回。
因此,建议查看其他后台线程替代方案,例如 Executors
或使用回调来更新 UI 或使用 ViewModel
(在配置更改后仍然存在)& LiveData
将结果发布到 UI.
带执行器的解决方案
- 此处提供
Executor
:
听众:
interface ProgressListener {
void onPostExecute();
void onProgressUpdate(int value);
}
片段:
ListAdapter adapter = new ListAdapter(activity, items, new ProgressListener() {
@Override
public void onPostExecute() {
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.
findViewHolderForLayoutPosition(0))).name.setText("task is finished");
}
@Override
public void onProgressUpdate(int value) {
recyclerView.post(() ->
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.findViewHolderForLayoutPosition(0))).
name.setText(String.valueOf(value)));
recyclerView.invalidate();
}
});
mRecyclerView.setAdapter(adapter);
适配器:只使用单线程执行器:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
ProgressListener progressListener;
public ListAdapter(Context context, ArrayList<String> items, ProgressListener listener) {
mInflater = LayoutInflater.from(context);
this.items = items;
progressListener = listener;
}
// ........... CODE IS OMITTED
// ViewHolder constructor
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
Executors.newSingleThreadExecutor().execute(() -> {
for (int i = 0; i <= 30; i++) {
progressListener.onProgressUpdate(i);
SystemClock.sleep(500);
}
progressListener.onPostExecute();
});
});
}
}
使用 AsyncTask
- 如果你还需要使用AsyncTask来解决这个问题,不推荐如前所述。您需要以某种方式保留适配器和 AsnyncTask 实例;这里使用了一个 ViewModel:
创建 ViewModel 以保存适配器和 AsyncTask 实例
public class MainViewModel extends AndroidViewModel {
AsyncTask<Void, Integer, Void> myTask;
ListAdapter adapter;
public MainViewModel(@NonNull Application application) {
super(application);
}
}
在片段中:仅当适配器为空时才实例化适配器,并将 AsynkTask 的实例传递给适配器:
if (viewModel.adapter == null)
viewModel.adapter = new ListAdapter(activity, items, viewModel);
recyclerView.setAdapter(viewModel.adapter);
最后在适配器中使用ViewModel AsynkTask
实例:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
// ......... code is omitted
private AsyncTask<Void, Integer, Void> task;
public ListAdapter(Context context, ArrayList<String> items, AsyncTask<Void, Integer, Void> myTask) {
mInflater = LayoutInflater.from(context);
this.items = items;
this.task = myTask;
}
// ......... code is omitted
public class ViewHolder extends RecyclerView.ViewHolder {
final ListAdapter mAdapter;
final TextView name;
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
task = new Task();
task.execute();
});
}
}
}
我在 Fragment
中有一个 RecyclerView
。我还有一个 AsyncTask
从后台线程更新 RecyclerView
的项目。问题是当我旋转设备(或导致任何其他配置更改)时,AsyncTask
无法更新视图。
这里可以看到简化的代码:
HomeFragment
:
public class HomeFragment extends Fragment {
public HomeFragment() {
}
public static HomeFragment newInstance() {
return new HomeFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
RecyclerView mRecyclerView = requireView().findViewById(R.id.recyclerview);
Activity activity = requireActivity();
ArrayList<String> items = new ArrayList<>(Arrays.asList("Item1", "Item2", "Item3"));
ListAdapter adapter = new ListAdapter(activity, items);
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
}
}
RecyclerView的适配器及其内部AsyncTask
class:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
private final ArrayList<String> items;
private final LayoutInflater mInflater;
private RecyclerView mRecyclerView;
public ListAdapter(Context context, ArrayList<String> items) {
mInflater = LayoutInflater.from(context);
this.items = items;
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@NonNull
@Override
public ListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View mItemView = mInflater.inflate(R.layout.item, parent, false);
return new ViewHolder(mItemView, this);
}
@Override
public void onBindViewHolder(@NonNull ListAdapter.ViewHolder holder, int position) {
holder.name.setText(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
final ListAdapter mAdapter;
final TextView name;
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
Task task = new Task();
task.execute();
});
}
}
class Task extends AsyncTask<Void, Integer, Void> {
@Override
protected void onProgressUpdate(Integer... values) {
mRecyclerView.post(() ->
((ViewHolder) Objects.requireNonNull(mRecyclerView.findViewHolderForLayoutPosition(0))).
name.setText(String.valueOf(values[0])));
mRecyclerView.invalidate();
}
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i <= 30; i++) {
publishProgress(i);
SystemClock.sleep(500);
}
return null;
}
@Override
protected void onPostExecute(Void unused) {
((ViewHolder) Objects.requireNonNull(mRecyclerView.findViewHolderForLayoutPosition(0))).name.setText("task is finished");
}
}
}
如您所见,我使用了 mRecyclerView.post()
方法来避免接触来自非 UI 线程的视图。如何在旋转设备后更新项目?
我之前尝试过的:
- 改变适配器的数据集并调用
notifyDataSetChanged()
- 在 Fragment 中创建一个方法来更新项目并从
AsyncTask
调用它
- 为 RecyclerView 重置适配器
- ...
您可以使用以下代码在屏幕旋转中更新回收器视图。
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(adapter!=null){
adapter.notifyDataSetChanged();
}
}
在 onCreate()
、onCreateView()
或 onActivityCreated()
方法中调用 setRetainInstance(true)
。
异步任务默认在后台线程上工作,我们无法从后台线程更新我们的 ui 它用于网络调用等密集型工作。请尝试 运行ning a
runOnUiThread(new Runnable() {
@Override
public void run() {
// WORK on UI thread here
}
});
异步任务的 onPostExecute 方法中的语句,因为它表示您的后台处理已完成。将更新视图的代码放入此 运行() 方法
AsyncTask
自 API 级别 30 起已弃用。它存在问题,特别是对于配置更改 per documentation:
AsyncTask was intended to enable proper and easy use of the UI thread. However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes.
而且由于这些问题,它不应该用于长时间的 运行ning 任务。
AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.
问题:
当您 运行 在 doInBackground
中执行任务时,当配置更改时,会重新创建 activity(即旧的 activity 被销毁,新的 activity 已创建);但是附加到旧 activity 的 AsyncTask
继续 运行 (导致内存泄漏);等稍后 onPostExecute()
准备好将结果发布到 UI 线程时;它会将它们释放到已销毁的 activity 而不是屏幕上显示的当前 activity;因此用户不会看到它们。因此,您的案件结果被驳回。
因此,建议查看其他后台线程替代方案,例如 Executors
或使用回调来更新 UI 或使用 ViewModel
(在配置更改后仍然存在)& LiveData
将结果发布到 UI.
带执行器的解决方案
- 此处提供
Executor
:
听众:
interface ProgressListener {
void onPostExecute();
void onProgressUpdate(int value);
}
片段:
ListAdapter adapter = new ListAdapter(activity, items, new ProgressListener() {
@Override
public void onPostExecute() {
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.
findViewHolderForLayoutPosition(0))).name.setText("task is finished");
}
@Override
public void onProgressUpdate(int value) {
recyclerView.post(() ->
((ListAdapter.ViewHolder) Objects.requireNonNull(recyclerView.findViewHolderForLayoutPosition(0))).
name.setText(String.valueOf(value)));
recyclerView.invalidate();
}
});
mRecyclerView.setAdapter(adapter);
适配器:只使用单线程执行器:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
ProgressListener progressListener;
public ListAdapter(Context context, ArrayList<String> items, ProgressListener listener) {
mInflater = LayoutInflater.from(context);
this.items = items;
progressListener = listener;
}
// ........... CODE IS OMITTED
// ViewHolder constructor
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
Executors.newSingleThreadExecutor().execute(() -> {
for (int i = 0; i <= 30; i++) {
progressListener.onProgressUpdate(i);
SystemClock.sleep(500);
}
progressListener.onPostExecute();
});
});
}
}
使用 AsyncTask
- 如果你还需要使用AsyncTask来解决这个问题,不推荐如前所述。您需要以某种方式保留适配器和 AsnyncTask 实例;这里使用了一个 ViewModel:
创建 ViewModel 以保存适配器和 AsyncTask 实例
public class MainViewModel extends AndroidViewModel {
AsyncTask<Void, Integer, Void> myTask;
ListAdapter adapter;
public MainViewModel(@NonNull Application application) {
super(application);
}
}
在片段中:仅当适配器为空时才实例化适配器,并将 AsynkTask 的实例传递给适配器:
if (viewModel.adapter == null)
viewModel.adapter = new ListAdapter(activity, items, viewModel);
recyclerView.setAdapter(viewModel.adapter);
最后在适配器中使用ViewModel AsynkTask
实例:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> {
// ......... code is omitted
private AsyncTask<Void, Integer, Void> task;
public ListAdapter(Context context, ArrayList<String> items, AsyncTask<Void, Integer, Void> myTask) {
mInflater = LayoutInflater.from(context);
this.items = items;
this.task = myTask;
}
// ......... code is omitted
public class ViewHolder extends RecyclerView.ViewHolder {
final ListAdapter mAdapter;
final TextView name;
public ViewHolder(@NonNull View itemView, ListAdapter mAdapter) {
super(itemView);
this.mAdapter = mAdapter;
this.name = itemView.findViewById(R.id.file_name);
itemView.setOnClickListener(v -> {
task = new Task();
task.execute();
});
}
}
}