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。