android-architecture 是Google推荐一些架构,包括各种情况下的mvp和mvvm,最近kotlin兴起,也加入了kotlin分支。由于对kotlin不熟悉,记录下源码阅读中出现的坑。
架构浅析
以TaskDetail模块为例
TaskDetailActivity.kt:
val taskDetailFragment = supportFragmentManager
.findFragmentById(R.id.contentFrame) as TaskDetailFragment? ?:
TaskDetailFragment.newInstance(taskId).also {
replaceFragmentInActivity(it, R.id.contentFrame)
}
// Create the presenter
TaskDetailPresenter(taskId, Injection.provideTasksRepository(applicationContext),
taskDetailFragment)
这里是Presenter拿到了Fragment的实例。
TaskDetailPresenter.kt:
init {
taskDetailView.presenter = this
}
在Presenter中,又把自己的引用给了Fragment。Presenter如何把自己给Fragment呢?如果提前在Fragment中定义Presenter,那更换Presenter就需要改动Fragment了,所以在接口TaskDetailContract
和 BaseView
中
interface TaskDetailContract {
interface View : BaseView<Presenter> {
...
}
interface Presenter : BasePresenter {
fun editTask()
...
}
}
interface BaseView<T> {
var presenter: T
}
在Fragment中使用泛型也可以,但是这样就优雅多了。
总体来看,就是M与P完全独立,两个的交集只在Contract的接口中。
疑问
以TaskDetail模块为例
Fragment何时获得的Presenter的实例?
TaskDetalFragment.kt
override lateinit var presenter: TaskDetailContract.Presenter
...
override fun onResume() {
super.onResume()
presenter.start()
}
在onResume
中,我们发现TaskDetalFragment
直接使用了presenter
实例,而presenter
在整个TaskDetalFragmen
中并没有初始化,只是lateinit
,而且,TaskDetalFragment
实现的TaskDetailContract.View
接口,但kotlin中接口的Field是必须重写的,那么presenter是在哪里init的呢?
回头再看TaskDetailActivity.kt:
val taskDetailFragment = supportFragmentManager
.findFragmentById(R.id.contentFrame) as TaskDetailFragment? ?:
TaskDetailFragment.newInstance(taskId).also {
replaceFragmentInActivity(it, R.id.contentFrame)
}
// Create the presenter
TaskDetailPresenter(taskId, Injection.provideTasksRepository(applicationContext),
taskDetailFragment)
在TaskDetailActivity
中,获得taskDetailFragment
后立刻初始化了TaskDetailPresenter
,并将taskDetailFragment
传入。
我们发现TaskDetailPresenter
并没有返回值。进去看一下:
init {
//这里的taskDetailView就是传入的taskDetailFragment
taskDetailView.presenter = this
}
即TaskDetailPresenter
在初始化的时候把自己的实例赋值给了taskDetailFragment
。
也就是TaskDetailPresenter
与 TaskDetailFragment
互相获得引用,当TaskDetailFragment
发生点击或者其它事件时,逻辑交给Presenter处理,然后再调用Fragment来处理界面相关的行为。
为什么Presenter的实例化在Activity中而不是Fragment中?
由上面看其实完全可以在TaskDetailFragment
的构造器中实例化TaskDetailPresenter
,为什么要在Activity中呢?
为了解耦,如果在Fragment中实例化Presenter,不管更换Fragment还是更换Presenter都会引起两个文件的改动,写测试文件时也需要引入另一个类,而这种写法可以实现两个类互相独立,只需要定好接口就可以。
Fragment的初始化语句解析
以TaskDetail模块为例
TaskDetailActivity.kt:
val taskDetailFragment = supportFragmentManager
.findFragmentById(R.id.contentFrame) as TaskDetailFragment? ?:
TaskDetailFragment.newInstance(taskId).also {
replaceFragmentInActivity(it, R.id.contentFrame)
}
supportFragmentManager.findFragmentById(R.id.contentFrame) as TaskDetailFragment?
这一段为获得一个可以为空的TaskDetailFragment
。
?: TaskDetailFragment.newInstance(taskId)
这一段为如果获得的TaskDetailFragment
为空,就实例化一个新的。
.also { replaceFragmentInActivity(it, R.id.contentFrame) }
这一段为实例化后先执行这一段再赋值给taskDetailFragment
TaskDetailFragment.kt:
companion object {
private val ARGUMENT_TASK_ID = "TASK_ID"
private val REQUEST_EDIT_TASK = 1
fun newInstance(taskId: String?) =
TaskDetailFragment().apply {
arguments = Bundle().apply { putString(ARGUMENT_TASK_ID, taskId) }
}
}
使用工厂模式。因为FragmentManager
经常销毁Fragment,所以重要的数据需要保存在Bundle中,每次都要传入重要数据保存到arguments。然后在Activity中和Fragment获得使用。