使用FragmentStatePagerAdapter的ViewPager二次进入Fragment空白问题
看起来你遇到的核心问题是:首次进入BoutiqueActivity时,CategorieFragment能正常通过AsyncTask加载数据并更新视图,但返回后再次进入该Activity时,视图更新操作(包括ListView的notifyDataSetChanged和ProgressBar的setVisibility)完全失效。下面我来拆解问题原因并给出针对性的修复方案:
核心问题原因
1. 重复设置ViewPager Adapter导致视图重建
你的BoutiqueActivity在onStart方法中每次都会调用viewPager.setAdapter(adapter),这会导致ViewPager重复绑定Adapter,进而引发Fragment视图的频繁销毁与重建。当AsyncTask完成请求时,它持有的可能已经是被销毁的旧视图引用,此时再调用更新操作自然不会生效。
2. 未及时取消旧的AsyncTask
当用户返回前一个Activity时,当前的CategorieFragment会被销毁,但之前启动的AsyncTask并没有被取消,它会继续在后台运行。当任务完成后,它会尝试操作已被销毁的旧视图,不仅会导致无效操作,还可能引发内存泄漏。
3. AsyncTask持有强引用导致内存泄漏
你的HTTPRequest类持有Fragment、ProgressBar、ListView的强引用,即使Fragment已经被销毁,这些引用依然会阻止GC回收相关对象,导致旧的任务完成后仍能操作无效视图。
具体修复方案
方案1:移除重复设置Adapter的代码
将ViewPager绑定Adapter和TabLayout的代码从onStart移到onCreate中,只执行一次即可:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_boutique); getSupportActionBar().setDisplayHomeAsUpEnabled(true); this.viewPager = this.findViewById(R.id.viewPager); this.viewPager.setSaveFromParentEnabled(false); this.savedInstanceState = savedInstanceState; this.adapter = new ViewPagerAdapter(getSupportFragmentManager()); if (savedInstanceState == null){ this.setupViewPager(); }else { Integer count = savedInstanceState.getInt("tabsCount"); String[] titles = savedInstanceState.getStringArray("titles"); for (int i = 0; i < count; i++) { this.adapter.addFragment(getFragment(i), titles[i]); } } // 移到此处,仅初始化一次 viewPager.setAdapter(adapter); this.tabLayout = this.findViewById(R.id.tabLayout); this.tabLayout.setupWithViewPager(viewPager); } @Override public void onStart(){ super.onStart(); // 移除原有的adapter设置代码 }
方案2:在Fragment销毁时取消AsyncTask
在CategorieFragment中保存当前的AsyncTask引用,并在视图销毁时主动取消任务:
public class CategorieFragment extends Fragment implements ActiviteEnAttenteAvecResultat<Categorie> { protected ArrayList<Categorie> categories; protected ListView listView; protected Drawable substitut; protected ProgressBar loader; // 保存当前请求的引用 private HTTPRequest<Categorie> currentRequest; // ... 其他原有代码 ... @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); this.listView = view.findViewById(R.id.liste); this.loader = view.findViewById(R.id.loader); CategorieAdapter categorieAdapter = new CategorieAdapter(getActivity(), categories, substitut); this.listView.setAdapter(categorieAdapter); // 取消旧的请求(如果存在) if(currentRequest != null && !currentRequest.isCancelled()){ currentRequest.cancel(true); } // 启动新请求并保存引用 currentRequest = CategorieDAO.getInstance(this, this.loader, this.listView).findAll(); } @Override public void onDestroyView() { super.onDestroyView(); // 视图销毁时取消请求,避免无效操作和内存泄漏 if(currentRequest != null && !currentRequest.isCancelled()){ currentRequest.cancel(true); } } // ... 其他原有代码 ... }
方案3:使用弱引用避免内存泄漏
修改HTTPRequest类,将activite、loader、listView改为弱引用,确保视图销毁后这些引用能被GC回收:
public class HTTPRequest<T extends Object> extends AsyncTask<String, Void, String> { protected WeakReference<ProgressBar> loaderRef; protected WeakReference<ListView> listViewRef; protected WeakReference<ActiviteEnAttenteAvecResultat> activiteRef; protected DAO dao; protected String method; protected String error; protected Object data; protected Class deserializationClass; public HTTPRequest(ActiviteEnAttenteAvecResultat activite, DAO dao, String method, Object data, Class deserializationClass, ProgressBar loader, ListView listView){ this.activiteRef = new WeakReference<>(activite); this.dao = dao; this.method = method; this.data = data; this.deserializationClass = deserializationClass; this.loaderRef = new WeakReference<>(loader); this.listViewRef = new WeakReference<>(listView); } // ... 其他构造函数 ... @Override protected void onPreExecute(){ ProgressBar loader = loaderRef.get(); ListView listView = listViewRef.get(); if(loader != null && listView != null){ loader.setVisibility(View.VISIBLE); listView.setVisibility(View.INVISIBLE); } } @Override protected void onPostExecute(String result){ ActiviteEnAttenteAvecResultat activite = activiteRef.get(); ProgressBar loader = loaderRef.get(); ListView listView = listViewRef.get(); // 检查引用是否有效 if(activite == null || loader == null || listView == null){ return; } Gson gson = new Gson(); ArrayList<T> liste = new ArrayList<>(); if(result != null){ switch (this.method){ case (HTTPRequestMethod.GET): JsonParser parser = new JsonParser(); JsonArray array = parser.parse(result).getAsJsonArray(); for (int i = 0; i < array.size(); i++) { Object object = gson.fromJson(array.get(i), this.deserializationClass); liste.add((T) object); } activite.notifyRetourRequeteFindAll(liste); break; } } else { // 可以添加错误提示逻辑 } loader.setVisibility(View.INVISIBLE); listView.setVisibility(View.VISIBLE); } // 移除原有的afficheLoader和cacheLoaderAfficheContenu方法,直接在onPreExecute/onPostExecute中操作弱引用对象 }
方案4:在视图更新前检查有效性
在CategorieFragment的notifyRetourRequeteFindAll方法中,先检查Fragment是否仍依附于Activity、视图是否有效,再执行更新操作:
@Override public void notifyRetourRequeteFindAll(ArrayList<Categorie> liste){ // 检查Fragment状态和视图有效性 if(getActivity() == null || listView == null || listView.getAdapter() == null){ return; } this.categories.clear(); this.categories.addAll(liste); ((BaseAdapter) this.listView.getAdapter()).notifyDataSetChanged(); }
总结
通过以上几个方案的组合修复,你可以解决重复设置Adapter导致的视图重建问题,避免旧AsyncTask的无效操作,同时消除内存泄漏风险,确保每次进入Activity时,Fragment都能正常加载数据并更新视图。
内容的提问来源于stack exchange,提问作者useryg

