前言
升学e网通是杭州铭师堂旗下的一款在线教育产品,集助学、助考、和升学为一体,是国内最领先的高中生综合指导系统,专为高中同学打造的提供视频学习、助学备考、志愿填报、升学报考等服务的平台。在客户端的高速业务迭代下,我们对Android客户端的架构进行了一次升级。我们将用这篇文章将我们最近几个月的技术工作进行分享。
早年,我们采用了大多数客户端采用的 MVP 架构。但是随着业务代码的逐步增加,我们遇到了下面几个头疼的问题。
在我们早期 MVP 的架构中,view 层就是 Actiivity、Fragment 等承载视图的部分,这部分一般都会有自己的生命周期,在 view 层对象中,会持有一个 Presenter 的对象实例。但是我们没有办法保证 presenter 层对象的生命周期和 view 层保持一致。比如团队的同学很早在 v 层的destroy中写了如下代码:
@Override
public void onDestroy() {
this = null;
}
我们这里暂时不讨论这个做法是否有必要或者是否正确,但是这里确实在 view 层对象置空后出现了 presenter 层对 view 层的调用,会发生不可预料的错误。
例如,我们在 presenter 层加入了最经典的 Retrofit + Rxjava 的代码。当弱网情况下,网络请求没有返回,回退界面,如果当前的 Activity 对象被销毁,而 presenter 内的网络回调完成并调用了 view 层的方法刷新 UI,就会出现 crash(NullPointException)。
所以我们每次都需要在网络请求的时候对 Rxjava 的
Flowable
对象添加订阅,在 v 层对象的生命周期中调用取消订阅。
大致的代码如下:
addSubscribe(myApi.requestNetwork(requestModel)
.compose()
.subscribeWith(new MySubscriber() {
@Override
public void onFail(int errorCode, String msg) {
// todo something
}
@Override
public void onNext() {
// todo something
}
}));
在团队人员增加的时候,如果在新同学入职的时候不强调这个规则的时候,很容易就会出现线的 NullPointException 异常。
在mvp中,我们抽象出了一些基础类, 例如
BasePresenterActivity
和
BaseActivity
,这段代码可能是这样的:
public abstract class BasePresenterActivity<T
extends BasePresenter>
extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// todo something
}
this.setContentView(getLayout());
// todo something
}
}
在
onCreate
中,我们拥有不少代码逻辑,在多轮迭代开发和新技术的演进中,我们可能需要其他的相似功能的Activity, 或者在某些 Fragment 中,我们需要类似的逻辑,甚至是重构这部分代码,但是,新上手的同学可能只想关心我需要复制哪些 Activity 相关的逻辑,或者只想关心和生命周期相关的逻辑,这时候,Activity 和生命周期的逻辑就耦合在了一起,终究会变得难以维护。
新同学很可能会因为 “我不知道如果大段大段的代码直接往外移动会不会出错” 的想法,放弃重构。最终导致的结果就是,新逻辑和老逻辑全部耦合在基类,基类变得越来越没有人愿意去维护,也没有人敢去动。
我们使用 MVP 的初衷是为了代码分层解耦,利于阅读和维护,但是在代码量增加后却发现,view 层和 presenter 层通过接口来交互,导致接口中定义的方法越来越多,如果修改一个地方的逻辑,可能需要顺着好多个文件来找被影响的方法并修改。
整理一下 MVP 的数据流向,可以发现 MVP 其实是双向的数据流。view 可以把数据传给 presenter, presenter 也可以把数据带给 view。逻辑复杂了之后极其不方便。
MVP 虽然基本的原理很简单,只是 MVC 的一个改进和变种。但是网上其实也有很多的 MVP 写法。在团队内部,对于是否应该保证 presenter 层只拥有纯 Java/Kotlin 代码,而不出现 Android 的相关包,也有过各自的意见。
综合以上 MVP架构 遇到的问题,升级一套新的架构,让业务代码抽象程度更高,开发更简便,代码更利于维护,迫在眉睫。于是我们开始关注 Google 官方出的 Jetpack 架构组件。
Android Jetpack
是 Google 在今年的 IO 大会上,根据去年 IO 大会上发布的
Android Architecture Component
进一步发布的内容,针对我们的问题,我们关注的主要是架构组件。
我们使用了
Lifecycle
来重构我们的基础
Activity
类,将 lifecycle 相关的内容和具体逻辑分类:
abstract class BaseActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(bindLayout())
lifecycle.addObserver(BaseActivityLifecycle(this))
}
/**
* Activity 的 Layout questionId
*/
abstract fun
bindLayout(): Int
}
BaseActivityLifecycle 的代码如下:
class BaseActivityLifecycle(val context: Context) : LifecycleObserver {
private val value:String? = null
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
// todo something
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
// todo something
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume()) {
// todo something
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
// todo something
}
}
目前,Activity内部的 lifecycle 包含了 EventBus 和我们自己的埋点库等等。我们可以一目了然的看到我们的基类 Activity 在每个生命周期中有哪些三方库或者二方库需要初始化和销毁。如果某个同学需要重构 BaseFragment 类,可以直接复用这个 lifecycle 的代码,也不用担心自己写漏了什么 lifecycle 相关的初始化。
我们使用 ViewModel 来解决自建 MVP 架构中 presenter的生命周期问题。
这里的 ViewModel 和 MVVM 的 ViewModel 并不是一回事,简单理解,其实 ViewModel 仍然是 Presenter。当然,是一个自动管理者生命周期的 Presenter, ViewModel 的官网简介就是:
Manage UI-related data in a lifecycle-conscious way
从文档里面我们可以看到 ViewModel 的基本用法: