现在主流都采用MVP的模式的搭建android的项目,包括连谷歌也在前段时间发布了几个关于mvp模式的写法,其中包括和rxjava和dragger2的融合的项目。所以是时候换一种方式写android的项目
写在前面的话
传统的mvc的模式,写android的项目,随着业务的扩展会使得c层(Activity)的代码变得臃肿和难以维护,如果你只是为了将原先的属于的c层的代码移除到p层,那么只是换汤不换药,只不过原来臃肿的代码换到了p层。所以在之前,我们一定要明白,分层的概念。 关于android的分层的概念其实就是低耦合的具体的表现。
参考:
1. Android项目重构之路:架构篇
2. Android项目重构之路:界面篇
3. Android项目重构之路:实现篇
mvp的模式
最简单的模型图
View: 负责加载视图,更新视图和隐藏视图,总之View管理着视图的所有操作。
Presenter:负责事的逻辑处理,并且根据处理的结果去更新View层和保存Model层
Model:这里的model不是单一的bean的对象,其中包括了data和domain。下面的图表明了三者包涵部分以及协作关系。
针对每层具体的描述
这里以点击登录去登录的过程来分析。
View 层
这层属于整个体系最简单,主要就是用来负责视图操作的,当我们拿到设计需求的时候,就大概知道每个页面的大概会展示的时候会存在的操作,比如loadList(),showError(),loadProgess(),等一些操作。而将所有的视图的操作都给注入进来的presenter去操作。比如这个点击登录按钮,在监听的操作操作,交给presenter去处理。等待presenter的处理完结果的通知页面的操作即可。
另外现在的Data Binding 可以进一步简化View 的代码,关于这方面的学习可以
Data Binding 用户指南(Android)
Presenter
我把这层的想象成调控层,就是接受从View层的信息,从model层去model,然后通过callback的形式去监听model处理完逻辑后,从而通知更新视图和更新数据等。是一个集disaptcher和deliver的中间层。比如登录过程,它接受View传过来的点击事件,然后取到一个user,调用user.login()的方法,并通过监听的方式去得到成功和失败的结果。并根据结果去通知view的去改变视图。
这一块可以实用RxJava,和RxAndroid,从而解决回调嵌套回调的问题。
Model层
个人觉得是整个框架的最难的地方,第一其必须向上隐藏实现的过程和实现细节,第二向下能够满足业务的需求。其中包括大致分为DataManager和interactor两个部分。
DataManager:大致可以分为Local和remote,其中实现必要的添,删,改,查等操作。
其中Local 可以分为
- DB: 用的最多
- DisK : 一般采用LurDiskCache
其中remote,通过api的方式得到数据
- Retrofit
- Volley
- OkHttp
interactor
具体的逻辑操作,比如登录,user.login().
对于这一块可以采用工厂模式向上提供model,供presenter去操作。并添加相应的改,删,增的操作去保存数据。
项目的例子
项目的简单说明
项目就是做一个简单的splashActivity,用来展示splashImage的。接口是来自开源的知乎日报的。
其中SplashContract中声明了view和presenter的所有的方法。
public interface SplashContract {
interface View {
void displayImage(String imageUrl);
void loading();
void showErrorMessage(String message);
}
interface Presenter {
void requestImageUrl();
void attach(View view);
}
}
而在真正实现的类中用来都是用接口来接偶的。
真正实现的view层,只有视图的相关的操作。
public class SplashActivity extends AppCompatActivity implements SplashContract.View {
private ImageView mSplashImage;
private ProgressBar mLoadingBar;
private SplashContract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
mSplashImage = (ImageView) findViewById(R.id.iv_splash);
mLoadingBar = (ProgressBar) findViewById(R.id.pb_splash_loading);
mPresenter = new SplashPresenter.SplashPresenterFactory().newSplashPresenterInstance();
mPresenter.attach(this);
mPresenter.requestImageUrl();
}
@Override
public void displayImage(String imageUrl) {
mLoadingBar.setVisibility(View.GONE);
mSplashImage.setVisibility(View.VISIBLE);
Glide.with(this)
.load(imageUrl)
.asBitmap()
.into(mSplashImage);
}
@Override
public void loading() {
mLoadingBar.setVisibility(View.VISIBLE);
mSplashImage.setVisibility(View.GONE);
}
public void showErrorMessage(String message) {
mSplashImage.setVisibility(View.GONE);
mLoadingBar.setVisibility(View.GONE);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
真正的presenter层,采用rxjava和lambda表达式来写的,其中逻辑操作交给SplashImage去执行。SplashImage是一个抽象,对于presenter层来说,不需要做的SplashImage的具体实现类是什么,我们可以告诉其我们需要什么,通过SplashImageFactory来生成一个我们想要的一个SplashImage的对象。
public class SplashPresenter implements SplashContract.Presenter {
private SplashContract.View splashView;
private SplashImage splashImage;
public SplashPresenter() {
splashImage = new SplashImage.SplashImageFactory().newInstance(SplashImage.DISPLAY_SHOW_LOCAL_IF_HAVE);
}
@Override
public void requestImageUrl() {
splashImage.getSplashImageBean()
.doOnSubscribe(() -> splashView.loading())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(imageBean -> {
splashView.displayImage(imageBean.getImg());
splashImage.saveSplashImageBean(imageBean);
}
, throwable -> splashView.showErrorMessage(throwable.getMessage()));
}
@Override
public void attach(SplashContract.View view) {
splashView = view;
}
public static class SplashPresenterFactory {
public SplashPresenter newSplashPresenterInstance() {
return new SplashPresenter();
}
}
}
而Model层,方法有添,删,改,查等一系列操作,其中实现类RemotoSplashImage(借助Retrofit来实现)和LocalSplashImage(借助SharePreference来实现的),而具体提供什么的样SplashImage的是根据业务的需求来选择的,这里选择的是如果本地存在,就加载到本地,如果本地没有则加载网络的。
public interface SplashImage {
int DISPLAY_ONLY_SHOW_NET = 0;
int DISPLAY_SHOW_LOCAL_IF_HAVE = 1;
Observable<SplashImageBean> getSplashImageBean();
Observable<Boolean> saveSplashImageBean(SplashImageBean bean);
Observable<Boolean> deleteSplashImageBean(SplashImageBean bean);
Observable<SplashImageBean> updateSplashImageBean(SplashImageBean bean);
class SplashImageFactory {
public SplashImageFactory() {
}
public SplashImage newInstance(int priority) {
switch (priority) {
case DISPLAY_SHOW_LOCAL_IF_HAVE:
if(!TextUtils.isEmpty(SharePreferenceManager.getsPreferencesManager(NewAndroidApplication.getApplication()).getLocalSplashImageUrl())){
return new LocalSplashImage();
}
default:
case DISPLAY_ONLY_SHOW_NET:
return new RemotoSplashImage();
}
}
}
}
运行后结果显示
而且根据log的可以看到,确实是第一次加载过后保存在本地就没有请求网络。而是加载本地的
第一次进入的log
之后进入的log
详细项目见CreateNewAndroid。