#Android# Single Activity Multiple Fragments Architecture
前言
在 Android 开发中,正常且推荐的 App 页面架构应该为「包含多个 Activity,Activity 即页面」,这样做的好处在于系统内存告急时可以回收处于后台的 Activity,保证更多的资源给前台的页面和任务。但是我们知道,启动新 Activity 是在 ActivityManagerService 中运行(非 UI 主线程),它需要执行一系列耗时的操作,我们能明显地意识到「页面切换」这个动作。
而知乎使用的是另外一种解决方案「单个 / 少量 Activity,多个 Fragment,Fragment 即页面」。它消耗更少的资源,能更快地响应页面间切换和交互。但是它也有些短处,在层次深的页面进行现场保存和还原会消耗更多的资源和时间。所以它适合在页面层级结构不深的应用或场合中应用。
你可以在 Kotgo Lastest Release 中下载 Sample Apk 进行尝试:sample-release.apk(可能需要 fan墙)
一些数据
对比 Kotgo 的 0.8.x 和 0.9.x 两个版本(分别使用 Activity 和 Fragment 来构建页面),我使用 Log 来纪录它们切换页面的用时:(单位:毫秒)
- 使用 Activity 切换页面:
- 使用 fragment 切换页面:
如何设计
要使用 Fragment 来取代 Activity 的职能还需要做一系列的工作,我们需要把一些 Activity 中常用的功能搬运到 Fragment 中,并且我们还需要另外维护一份 Fragment 页面的栈队。要知道 FragmentManager 在进行 Fragment 现场还原的时候只恢复内部的事务栈队(Transaction Stack),并不会恢复每个 Fragment 的显示状态,所以会产生 Fragment 重叠的问题。
而需要从 Activity 中「移植」到 Fragment 的一些必要功能包括:
- onBackPressed() 的处理
- onReslut() 的处理
而且为了模仿 startActivityForResult(),我们还需要实现 pushForReslut() 这一函数,可以将一个能产生返回值的 Fragment 加入栈顶。
上面说的这一大堆功能,都在 FragmentActivity.kt 中实现了,它在内部维护了多个列表和栈来解决一系列问题以及实现 FragmentManager 未提供的一些功能。下面来看看我们的 Fragment 应该设计成怎样。
abstract class BaseFragment: WithLifecycleFragment() {
var requestInfo: FragmentActivity.RequestInfo? = null
val fragAct: FragmentActivity?
get() = activity as FragmentActivity?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState != null) {
requestInfo = savedInstanceState.getParcelable("__requestInfo")
}
}
override fun onSaveInstanceState(outState: Bundle?) {
if(requestInfo != null)
outState?.putParcelable("__requestInfo", requestInfo!!)
super.onSaveInstanceState(outState)
}
open fun onBackPressed(): Boolean {
return false
}
open fun onResult(requestCode: Int, resultCode: Int, data: Intent?) { }
protected fun setResult(resultCode: Int, data: Intent? = null) {
requestInfo?.apply {
this.resultCode = resultCode
this.resultData = data
}
}
}
然后我们在 HostActivity/FragmentActivity 中提供对栈进行操作的一些函数,用来跳转到其他 Fragment 或者 Activity 上(因篇幅问题,详细实现可查阅源码):
- push():将 Fragment 加入栈顶并显示
- pushForResult():将 Fragment 加入栈顶并显示,并捕获返回值
- pop():将栈顶的 Fragment 弹出并隐藏
- get():获取栈中指定 Tag 的 Fragment
- getFragmentTopInStack():获取栈顶中的 Fragment
- startActivityForResult():启动另外一个 Activity,并捕获返回值给指定 Fragment
结尾
Single Activity Multiple Fragments 确实是一种不错的架构页面的新方案。它提供了一种新的思路来组织页面,所有页面的 View 保存在碎片中,然后在单个 Activity 中快速切换,思考下,其实这是一种退化(或者说底层化)的思想,用 Fragment 组织 View 分页面,然后全部 View 一碌脑全在单个 Activity 上初始化/显示/隐藏/绘制。
所以说更底层化的思想带来了更快的页面切换与交互。