implementation 'com.susion:life-clean:1.0.6'
LifeClean
是一个适用于UI业务的编码框架, 主要具有以下特点:
- 规范
MVP
写法 - 为
Presenter/View
提供LifeCycle
- 及时释放
RxJava Disposable
,避免内容泄漏 - 规范
RecyclerView.Adapter
的使用方式 - 规范全局UI状态的刷新
个人认为MVP
主要是用来做职责分离的, 即Presenter
负责数据的加载逻辑, View
负责数据的展示逻辑。
传统MVP
的写法是将Presenter
和View
都抽取出一个接口,然后实现类之间使用这两个接口做隔离。
在LifeClean
中不会对每一个Presenter
都抽取一个接口, LifeClean
规定:
- 所有的
Presenter
都应该遵守同一个约定(接口) - 所有的
View
都应该使用Presenter
接口来与Presenter
交互
抽象的
Presenter
接口:
// view 向 presenter 发出的事件(信号)
interface Action
//页面需要的状态
interface State
interface Presenter {
fun dispatch(action: Action)
fun <T : State> getState(): T? {
return null
}
}
它定义了Presenter
的能力:
-
dispatch(Action)
:Presenter
可以接收View
发出的信号(Action
) -
getState():T
:Presenter
可以返回给View
一些状态(State
)
这些Action/State
在LifeClean
中都属于View
, View
应该在其所遵循的约定(接口)中定义这些Action/State
, 比如:
基于
RecyclerView
来实现的页面的约定:
interface SimpleRvPageProtocol {
//加载数据
class LoadData(val searchWord: String, val isLoadMore: Boolean) : Action
//查询数据状态
class PageState(val currentPageSize: Int) : State
//刷新页面数据
fun refreshDatas(datas: List<Any>, isLoadMore: Boolean = false, extra: Any = Any())
//刷新页面状态
fun refreshPageStatus(status: String, extra: Any = Any())
}
结合Presenter
的一个具体使用示例:
//View
class GitRepoMvpPage(activity: AppCompatActivity) : SimpleRvPageProtocol, FrameLayout(activity) {
//类型为最抽象的Presenter
private val presenter: Presenter = GithubPresenter(this)
init {
// 通知Presenter做数据的加载
presenter.dispatch(SimpleRvPageProtocol.LoadData("Android", false))
}
override fun refreshDatas(datas: List<Any>, isLoadMore: Boolean, extra: Any) {
//查询数据状态
val currentPageSize =presenter.getState<SimpleRvPageProtocol.PageState>()?.currentPageSize ?: 0
Toast.makeText(context, "当前页 : $currentPageSize", Toast.LENGTH_SHORT).show()
}
...
}
//Presenter
class GithubPresenter(val view: SimpleRvPageProtocol) : Presenter {
private var page = 0
override fun dispatch(action: Action) {
when (action) {
is SimpleRvPageProtocol.LoadData -> {
...
view.refreshDatas(list)
}
}
}
override fun <T : State> getState(): T? {
return SimpleRvPageProtocol.PageState(page) as? T
}
}
在LifeClean
中将View
定义为业务的中心,将Presenter
的能力(Action)都定义到了View
(约定)中,Presenter
可以自己选择性的处理这个Action
, 即View
完全解耦于Presenter
一般会在Presenter
中做资源的加载工作,比如使用RxJava
进行网络请求,那么如何及时的释放Disposable
来避免内存泄漏呢?
在LifeClean
中Presenter
可以通过继承LifePresenter
来观察Activity
的生命周期:
class GithubPresenter(val view: SimpleRvPageProtocol) : LifePresenter() {
override fun onActivityCreate() {
Log.d(TAG, "onActivityCreate")
}
}
即继承LifePresenter
, 然后复写Activity
相关生命周期方法, 那为什么LifePresenter
拥有Activity
的生命周期呢? 内部实现如下:
abstract class LifePresenter : Presenter, LifecycleObserver {
private var lifeOwnerReference = WeakReference<AppCompatActivity>(null)
fun injectLifeOwner(lifecycleOwner: AppCompatActivity) {
lifeOwnerReference = WeakReference(lifecycleOwner)
lifecycleOwner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
open fun onActivityCreate() {
}
}
即LifePresenter
就是一个LifecycleObserver
,它是LifeCycle
的一个观察者, 那injectLifeOwner()
这个方法在哪里调用的呢?
其实在LifeClean
中如果你想让Presenter
感知Activity
的生命周期,那么必须继承LifePresenter
, 并且使用LifeClean
提供的模板方法来创建这个Presenter
:
class GitRepoMvpPage(context: AppCompatActivity) : SimpleRvPageProtocol, FrameLayout(context) {
val presenter: Presenter = LifeClean.createPresenter<GithubPresenter, SimpleRvPageProtocol>(context, this)
}
LifeClean.createPresenter()
会通过反射来构造GithubPresenter
并调用injectLifeOwner()
,使GithubPresenter
可以感知Activity
的生命周期。
这里的View
特指使用ViewGroup
实现的页面,不过由于多继承的问题,在LifeClean
中View
感知Activity
的生命周期的用法与Presenter
并不相同。
首先你的ViewGroup
需要实现LifePage
接口:
interface LifePage : LifecycleObserver
然后使用LifeClean
的模板方法创建这个ViewGroup
:
val lifePage = LifeClean.createPage<GitHubLifePage>(activity)
然后就可以感知Activity
的生命周期了:
class GitHubLifePage(context: AppCompatActivity) : FrameLayout(context),LifePage {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Toast.makeText(context, "接收到Activity的生命周期事件 onResume", Toast.LENGTH_SHORT).show()
}
}
LifeClean
会在View Dettach
时自动解除对Activity
生命周期的观察。
LifeClean
提供了自动释放Disposable
的方法:
fun Disposable.disposeOnStop(lifeOwner: LifecycleOwner?): Disposable?
比如在LifePresenter中释放Disposable
:
apiService.searchRepos(query + IN_QUALIFIER, requestPage, PAGE_SIZE)
.subscribe({...})
.disposeOnDestroy(getLifeOwner())
disposeOnDestroy(getLifeOwner())
会自动在LifeOwner Destroy
时释放掉Disposable
。
LifeClean
中RecyclerView.Adapter
应实现AdapterDataToViewMapping
接口, 它定义了对象与View的映射关系:
interface AdapterDataToViewMapping<T> {
//对象 ——> Type
fun getItemType(data: T): Int
// Type -> View
fun createItem(type: Int): AdapterItemView<*>?
}
RecyclerView
的ItemView
应实现AdapterItemView
接口,这样ItemView
只需要拿到数据做UI渲染即可:
interface AdapterItemView<T> {
fun bindData(data: T, position: Int)
}
CommonRvAdapter
是AdapterDataToViewMapping
的抽象实现类。它要求所有的ItemView
都应该是View
的子类:
//data数据集合应该交给CommonRvAdapter维护
abstract class CommonRvAdapter<T>(val data: MutableList<T> = ArrayList()) :
RecyclerView.Adapter<RecyclerView.ViewHolder>(),
AdapterDataToViewMapping<T> {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val item = createItem(viewType)
?: throw RuntimeException("AdapterDataToViewMapping.createItem cannot return null")
return CommonViewHolder(item)
}
//item必须继承自View
protected class CommonViewHolder<T> internal constructor(var item: AdapterItemView<T>) :
RecyclerView.ViewHolder(if (item is View) item else throw RuntimeException("item view must is view"))
}
即CommonRvAdapter
强调的是: 把对象映射为View
他俩都继承自CommonRvAdapter
, SimpleRvAdapter
提供快速映射对象到View的能力:
val adapter = SimpleRvAdapter<Any>(context).apply {
registerMapping(String::class.java, SimpleStringView::class.java)
registerMapping(Repo::class.java, GitRepoView::class.java)
}
即通过反射来动态构造对象对应的View
,不过这里View
必须要有constructor(context)
构造函数。
MergeAdapter
可以合并多个遵循AdapterDataToViewMapping
接口的RecyclerView.Adapter
,它可以大大提高RecyclerView.Adapter
的复用性:
private val titleAdapter by lazy {
SimpleRvAdapter<Any>(this).apply {
registerMapping(SimpleTitleInfo::class.java, SimpleTitleView::class.java)
}
}
private val descAdapter by lazy {
SimpleRvAdapter<Any>(this).apply {
registerMapping(SimpleDescInfo::class.java, SimpleDescView::class.java)
}
}
private val mergeAdapter by lazy {
MergeAdapter(
adapterTitle,
adapterDesc
)
}
上面mergeAdapter
组合了titleAdapter
和descAdapter
的映射能力。
大多数App的页面状态都是相同的, LifeClean
定义常见的页面状态, 可以用来规范整个App
的页面状态刷新逻辑:
object PageStatus {
//一些常用的页面状态
val START_LOAD_MORE = "start_load_more"
val END_LOAD_MORE = "end_load_more"
val START_LOAD_PAGE_DATA = "start_load_page_data"
val END_LOAD_PAGE_DATA = "end_load_page_data"
val NO_MORE_DATA = "no_more_data"
val EMPTY_DATA = "empty_data"
val NET_ERROR = "net_error"
val TOAST = "show_toast"
val PRIVACY_DATA = "privacy_data"
val CONTENT_DELETE = "content_delete"
val ERROR = "error"
val UNDEFINE = "undefine"
...
}
遵循LifeClean
的思想可以帮助你写出清晰、复用性高的业务代码。
更详细的使用细节请参考Demo。