使用 `FragmentTransaction.replace()` 切換 Fragment A → B,再按返回鍵回到 A 時,`onCreateView()` 或 `onResume()` 再次執行,導致資料重複請求、列表重複載入。
// 容器持有所有 Fragment 實例
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (!fragmentB.isAdded()) {
ft.add(R.id.container, fragmentB, "B");
}
ft.hide(fragmentA).show(fragmentB).addToBackStack(null).commit();
// 返回鍵自動處理 hide/show,Fragment 實例不銷毀,onCreateView 僅執行一次
優點:生命週期簡單、狀態自動保留、程式碼最少。
缺點:所有 Fragment 長駐記憶體,適用於 3-5 個標籤頁場景。
// FragmentPagerAdapter 會保留 Fragment 實例 (detach view 不 destroy)
viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override public Fragment getItem(int pos) { return fragments[pos]; }
@Override public int getCount() { return fragments.length; }
});
配合 `TabLayout` 實現頂部標籤切換,Fragment 只建立一次,`setUserVisibleHint` 或 `setMenuVisibility` 控制可見時載入資料。
// 在 Fragment 內部
private List cachedData;
private boolean isLoaded = false;
@Override
public void onResume() {
super.onResume();
if (!isLoaded) {
loadData(); // 僅第一次載入
isLoaded = true;
}
// 或使用 cachedData 直接更新 UI
}
private void loadData() {
// 網路請求成功後
cachedData = result;
adapter.setData(cachedData);
}
配合 `setRetainInstance(true)` (僅適用非嵌套 Fragment) 可在 Activity 重建時保留實例。
| 方案 | 適用場景 | 記憶體 | 實現難度 |
|---|---|---|---|
| hide/show | 底部導航 3-5 頁 | 中 | 低 |
| ViewPager | 頂部標籤、左右滑動 | 中 | 低 |
| 手動快取 | 複雜嵌套、需精確控制 | 低 | 中 |
| replace + ViewModel (Jetpack) | 現代架構、配合 LiveData | 低 | 中 |
採用 Navigation Component + ViewModel:
Fragment 只負責 UI,資料存放於 `ViewModel` (生命週期跟隨 Activity/Navigation Graph),Fragment 重建時自動獲取相同 ViewModel 實例,資料零重複請求。
// Fragment 內
MyViewModel vm = new ViewModelProvider(requireActivity()).get(MyViewModel.class);
vm.getData().observe(getViewLifecycleOwner(), data -> adapter.submitList(data));
// 首次進入時 vm 發起請求,後續重建直接取緩存