Android navcation 主页缓存主页面不缓存子页面方案

14 阅读2分钟

Android navcation 主页缓存主页面不缓存子页面方案

在 Android 中实现主页三个 Fragment 及其数据画面的缓存,同时不缓存其他 Fragment,可以通过以下方案实现:

核心思路

  1. 使用 FragmentStatePagerAdapter/FragmentPagerAdapter​ 管理主页的多个 Fragment
  2. 结合 ViewModel + onSaveInstanceState 保存数据状态
  3. 使用 setOffscreenPageLimit​ 控制 Fragment 缓存数量
  4. Navigation 组件中设置 popUpTo​ 避免其他 Fragment 入栈时缓存

具体实现步骤

1. 主页 Fragment 容器布局 (activity_main.xml)
<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
2. 主页 Activity 实现
class MainActivity : AppCompatActivity() {
    private lateinit var viewPager: ViewPager2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewPager = findViewById(R.id.view_pager)
        setupViewPager()
    }

    private fun setupViewPager() {
        // 创建适配器
        val adapter = HomePagerAdapter(this)
        viewPager.adapter = adapter
    
        // 设置缓存页面数量(左右各缓存2页,实际缓存3页)
        viewPager.offscreenPageLimit = 2
    }
}
3. Fragment 适配器实现
class HomePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    // 主页需要缓存的三个 Fragment
    override fun getItemCount(): Int = 3

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> HomeFragment.newInstance()
            1 -> DashboardFragment.newInstance()
            2 -> NotificationsFragment.newInstance()
            else -> throw IllegalArgumentException()
        }
    }
}
4. 示例 Fragment 实现(带数据缓存)
class HomeFragment : Fragment() {
    private lateinit var viewModel: HomeViewModel
    private var recyclerViewState: Parcelable? = null

    companion object {
        fun newInstance() = HomeFragment()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, s: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        // 初始化 ViewModel
        viewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
    
        // 恢复列表滚动位置
        recyclerView.layoutManager.onRestoreInstanceState(recyclerViewState)
    
        // 观察数据变化
        viewModel.data.observe(viewLifecycleOwner) { data ->
            adapter.submitList(data)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // 保存列表滚动位置
        recyclerViewState = recyclerView.layoutManager.onSaveInstanceState()
    }
}

// ViewModel 保存数据
class HomeViewModel : ViewModel() {
    private val _data = MutableLiveData<List<String>>()
    val data: LiveData<List<String>> get() = _data

    init {
        loadData()
    }

    private fun loadData() {
        // 从网络/数据库加载数据
        _data.value = listOf("Item 1", "Item 2", "Item 3")
    }
}
5. 导航到其他 Fragment 时的处理(避免缓存)
// 在需要跳转的地方
findNavController().navigate(
    R.id.action_to_detail,
    null,
    navOptions {
        // 关键配置:跳过主页缓存
        popUpTo(R.id.main_graph) {
            inclusive = false
            saveState = false
        }
    }
)

关键配置说明

  1. 缓存控制机制

    • offscreenPageLimit = 2​:确保左右各缓存2页(实际缓存当前页+左右页)
    • FragmentStateAdapter​:自动处理 Fragment 生命周期和状态恢复
  2. 数据持久化

    • ViewModel:保存业务数据(配置更改时不丢失)
    • onSaveInstanceState:保存 UI 状态(如列表滚动位置)
    • Bundle 参数:保存简单数据(Fragment 重建时恢复)
  3. 非缓存 Fragment 配置

    navOptions {
        popUpTo(R.id.main_graph) {
            inclusive = false  // 不清除主页
            saveState = false   // 不保存当前状态
        }
    }
    

生命周期对比(验证缓存效果)

操作主页 Fragment其他 Fragment
首次进入onCreateViewonCreateView
切换到同页签无变化-
切换到其他页签onPause-
进入详情页onPauseonCreateView
返回主页onResumeonDestroy
旋转屏幕仅恢复UI状态完全重建

优化建议

  1. 数据加载策略

    viewModel.data.observe(viewLifecycleOwner) { data ->
        if (recyclerView.adapter == null) {
            // 首次初始化
            initRecyclerView(data)
        } else {
            // 仅更新数据
            adapter.submitList(data)
        }
    }
    
  2. 大图处理

    override fun onDestroyView() {
        // 释放图片资源
        imageView.setImageDrawable(null)
        super.onDestroyView()
    }
    
  3. 内存监控

    adb shell dumpsys meminfo your.package.name
    
  4. LeakCanary 检测

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    

此方案确保主页三个 Fragment 保持数据和 UI 状态,其他 Fragment 在离开时被销毁,优化了内存使用和用户体验。