使用BottomNavigationView时,如何让Fragment仅加载一次Volley数据?
你遇到的这个问题其实很常见——用BottomNavigationView切换Fragment时,如果每次都是replace操作,Fragment的视图会被销毁重建,导致你的loadRecyclerViewData()方法被重复调用,反复发起不必要的网络请求。下面给你几个实用的解决方案,按推荐程度排序:
方案1:使用ViewModel缓存数据(最推荐,符合Jetpack架构)
ViewModel的生命周期和宿主Activity绑定,即使Fragment被销毁重建(比如切换回来、屏幕旋转),ViewModel里的数据依然保留。这样可以确保数据只请求一次,后续直接复用缓存。
步骤:
- 先创建一个ViewModel类,用来保存你的列表数据:
import androidx.lifecycle.ViewModel; import java.util.ArrayList; import java.util.List; public class FirstFragmentViewModel extends ViewModel { private List<YourDataModel> dataList = new ArrayList<>(); private boolean isDataLoaded = false; public List<YourDataModel> getDataList() { return dataList; } public void setDataList(List<YourDataModel> dataList) { this.dataList = dataList; isDataLoaded = true; } public boolean isDataLoaded() { return isDataLoaded; } }
(这里的YourDataModel是你自己定义的实体类,用来解析JSON数据)
- 在你的Fragment里,获取ViewModel并判断是否需要加载数据:
import androidx.lifecycle.ViewModelProvider; public class FirstFragment extends Fragment { private FirstFragmentViewModel viewModel; private RecyclerView recyclerView; private ProgressBar bar; private boolean isViewInitialized = false; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_first, container, false); recyclerView = view.findViewById(R.id.recyclerView); bar = view.findViewById(R.id.progressBar); // 初始化ViewModel viewModel = new ViewModelProvider(this).get(FirstFragmentViewModel.class); return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); isViewInitialized = true; checkAndLoadData(); } @Override public void onResume() { super.onResume(); // 确保视图初始化完成后再检查数据 if (isViewInitialized) { checkAndLoadData(); } } private void checkAndLoadData() { if (!viewModel.isDataLoaded()) { loadRecyclerViewData(); } else { // 直接用缓存的数据填充RecyclerView bar.setVisibility(View.GONE); setupRecyclerView(viewModel.getDataList()); } } private void loadRecyclerViewData() { StringRequest stringRequest = new StringRequest(Request.Method.GET, URL_DATA, new Response.Listener<String>() { @Override public void onResponse(String s) { bar.setVisibility(View.GONE); try { JSONArray array = new JSONArray(s); List<YourDataModel> list = new ArrayList<>(); for (int i = 0; i < array.length(); i++) { JSONObject o = array.getJSONObject(i); // 解析数据到实体类 YourDataModel model = new YourDataModel(); model.setId(o.getInt("id")); model.setTitle(o.getString("title")); // ... 其他字段解析 list.add(model); } // 把数据存入ViewModel缓存 viewModel.setDataList(list); // 填充RecyclerView setupRecyclerView(list); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { bar.setVisibility(View.GONE); // 处理请求错误,比如显示提示 } }); // 添加请求到Volley队列 Volley.newRequestQueue(getContext()).add(stringRequest); } private void setupRecyclerView(List<YourDataModel> dataList) { YourAdapter adapter = new YourAdapter(dataList); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(adapter); } }
方案2:给Fragment添加加载状态标志位(简单直接,适合小型项目)
如果你不想引入ViewModel,可以给Fragment加一个布尔变量,标记数据是否已经加载完成。为了应对Fragment被系统销毁重建的情况,最好配合onSaveInstanceState保存状态。
代码示例:
public class FirstFragment extends Fragment { private boolean isDataLoaded = false; private RecyclerView recyclerView; private ProgressBar bar; private static final String KEY_LOADED_STATE = "key_loaded_state"; // 缓存数据,避免Fragment重建后丢失 private List<YourDataModel> cachedData; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_first, container, false); recyclerView = view.findViewById(R.id.recyclerView); bar = view.findViewById(R.id.progressBar); // 恢复保存的状态和数据 if (savedInstanceState != null) { isDataLoaded = savedInstanceState.getBoolean(KEY_LOADED_STATE); cachedData = savedInstanceState.getParcelableArrayList("key_cached_data"); } return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (!isDataLoaded) { loadRecyclerViewData(); } else { bar.setVisibility(View.GONE); setupRecyclerView(cachedData); } } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); // 保存加载状态和缓存数据 outState.putBoolean(KEY_LOADED_STATE, isDataLoaded); outState.putParcelableArrayList("key_cached_data", (ArrayList<YourDataModel>) cachedData); } private void loadRecyclerViewData() { StringRequest stringRequest = new StringRequest(Request.Method.GET, URL_DATA, new Response.Listener<String>() { @Override public void onResponse(String s) { bar.setVisibility(View.GONE); try { JSONArray array = new JSONArray(s); cachedData = new ArrayList<>(); for (int i = 0; i < array.length(); i++) { JSONObject o = array.getJSONObject(i); YourDataModel model = new YourDataModel(); model.setId(o.getInt("id")); model.setTitle(o.getString("title")); cachedData.add(model); } setupRecyclerView(cachedData); isDataLoaded = true; } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { bar.setVisibility(View.GONE); } }); Volley.newRequestQueue(getContext()).add(stringRequest); } private void setupRecyclerView(List<YourDataModel> dataList) { YourAdapter adapter = new YourAdapter(dataList); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(adapter); } }
方案3:修改BottomNavigationView切换逻辑,用hide/show替代replace
默认的replace操作会销毁旧Fragment,改用hide和show可以让Fragment实例一直保留,生命周期不会走到onDestroy,所以数据只会加载一次。
步骤:
- 在Activity里初始化所有Fragment并管理切换:
public class MainActivity extends AppCompatActivity { private FirstFragment firstFragment; private SecondFragment secondFragment; private ThirdFragment thirdFragment; private Fragment currentFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化所有Fragment实例 firstFragment = new FirstFragment(); secondFragment = new SecondFragment(); thirdFragment = new ThirdFragment(); // 默认显示第一个Fragment getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, thirdFragment, "third") .add(R.id.fragment_container, secondFragment, "second") .add(R.id.fragment_container, firstFragment, "first") .hide(thirdFragment) .hide(secondFragment) .show(firstFragment) .commit(); currentFragment = firstFragment; // 设置BottomNavigationView监听 BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation); bottomNav.setOnNavigationItemSelectedListener(item -> { switch (item.getItemId()) { case R.id.nav_first: switchFragment(firstFragment); return true; case R.id.nav_second: switchFragment(secondFragment); return true; case R.id.nav_third: switchFragment(thirdFragment); return true; default: return false; } }); } private void switchFragment(Fragment targetFragment) { if (currentFragment != targetFragment) { getSupportFragmentManager().beginTransaction() .hide(currentFragment) .show(targetFragment) .commit(); currentFragment = targetFragment; } } }
- 在FirstFragment里,把加载逻辑放在
onViewCreated里(因为实例不会销毁,只会执行一次):
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); recyclerView = view.findViewById(R.id.recyclerView); bar = view.findViewById(R.id.progressBar); // 只会执行一次,因为Fragment实例一直存在 loadRecyclerViewData(); }
这个方案的优点是简单,但缺点是所有Fragment会一直驻留在内存中,适合Fragment数量少、内存占用低的场景。
内容的提问来源于stack exchange,提问作者ClassA
相关产品推荐
相关产品推荐

