MVP 笔记
公司项目代码中使用MVP
,之前并没有在项目中使用过,记录一下最近一段个人写代码时学到的习惯,代码中有哪些不合理的地方,希望可以留言指出
1. Demo 代码
包项目中,是按照模块分的包,例如新闻
相关的代码就放在同一个news
包下
写代码时一个重要的思路:
V层只负责UI展示,任何数据源尽量不放V层
P层负责逻辑处理,尽量不要有任何Android环境的代码,尤其是 Context,更符合MVP的规范,并且方便单元测试
M层负责数据获取,主要负责从网络、数据库、本地文件中获取数据源
写完代码,检查写的是否合理,简单的办法就是查看各个分层代码中类的引用
1.1 Activity 代码
UI整个Actiivty
就一个Button
,点击Button
请求网络,然后将数据显示
/**
* 展示请求的新闻字符串
*/
public class NewsActivity extends AppCompatActivity
implements NewsContract.View {
private TextView mTvContent;
private NewsContract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news);
mPresenter = new NewsPresenter(this);
initView();
}
/**
* 取消网络请求
*/
@Override
protected void onPause() {
super.onPause();
mPresenter.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDestroy();
mPresenter = null;
}
}
/**
* 展示请求的内容
*
* @param content 数据
*/
@Override
public void showRequestContent(String content) {
mTvContent.setText(content);
}
private void initView() {
mTvContent = findViewById(R.id.activity_news_tv_content);
Button btNews = findViewById(R.id.activity_news_bt_news);
btNews.setOnClickListener(v -> mPresenter.onBtNewsClick());
}
}
在onCreate()
中,正常的初始化,并初始化Presenter
。有时实际项目中,需要考虑下初始化的合理顺序
就一个Button
,在点击监听中,mPresenter.onBtNewsClick()
,调用P
层逻辑代码
在onPause()
中,取消未完成的网络请求
在onDestroy()
方法中,做一些回收销毁工作
void showRequestContent(String content)
就是实现的V
层接口回调的方法,在请求网络后,展示内容
1.2 Base 接口
BasePresenter
public interface BasePresenter {
/**
* 在 Activity onDestroy()生命周期方法
*/
void onDestroy();
}
声明每一个Presenter
都要实现的销毁阶段
方法
BaseView
public interface BaseView {
}
在这个Demo
中,BaseView
里并没有声明啥方法,实际项目代码中根据实际需要声明View
层的共有方法
BaseCallback
public interface BaseCallback {
/**
* 成功回调
*
* @param content 请求内容
*/
void onSuccess(String content);
/**
* 失败回调
*
* @param errorInfo 错误信息
*/
void onError(String errorInfo);
}
要进行网络请求,肯定需要用到Callback
,个人习惯定义一个Base
型接口
1.3 Contract 契约
interface NewsContract {
interface Model {
/**
* 从网络请求新闻内容
*
* @param callback 结果回调
*/
void requestNewsFromNet(BaseCallback callback);
/**
* 取消网络请求
*/
void cancel();
}
interface View extends BaseView {
/**
* 展示请求的内容
*
* @param content 数据
*/
void showRequestContent(String content);
}
interface Presenter extends BasePresenter {
/**
* 点击按钮
*/
void onBtNewsClick();
/**
* 在 onPause() 生命周期
*/
void onPause();
}
}
一般View
层接口主要以show
方法为主
Presenter
层方法个人习惯以onXX()
形式,方便在查看代码,更容易通过名字知道方法负责做啥
1.4 NewPresenter 代码
public class NewsPresenter implements NewsContract.Presenter {
private NewsContract.View mView;
private NewsContract.Model mNewModel;
NewsPresenter(NewsContract.View mView) {
this.mView = mView;
mNewModel = new NewsModel();
}
/**
* 点击按钮,请求网络
*/
@Override
public void onBtNewsClick() {
mNewModel.requestNewsFromNet(new NewsCallback(this));
}
/**
* 取消网络请求
*/
@Override
public void onPause() {
mNewModel.cancel();
}
/**
* 销毁
*/
@Override
public void onDestroy() {
if (mNewModel != null) {
mNewModel = null;
}
if (mView != null) {
mView = null;
}
}
/**
* 成功处理
*
* @param content 内容
*/
private void onRequestSuccess(String content) {
mView.showRequestContent(content);
}
/**
* 失败处理
*
* @param errorInfo 错误信息
*/
private void onRequestError(String errorInfo) {
mView.showRequestContent(errorInfo);
}
/**
* 结果回调
*/
private static class NewsCallback implements BaseCallback {
private WeakReference<NewsPresenter> mReference;
private NewsCallback(NewsPresenter presenter) {
mReference = new WeakReference<>(presenter);
}
@Override
public void onSuccess(String content) {
NewsPresenter newsPresenter = mReference.get();
if (newsPresenter != null) {
newsPresenter.onRequestSuccess(content);
}
}
@Override
public void onError(String errorInfo) {
NewsPresenter newsPresenter = mReference.get();
if (newsPresenter != null) {
newsPresenter.onRequestError(errorInfo);
}
}
}
}
在构造方法关联View
层,并初始化Model
层对象
Model
进行网络请求,需要一个接口,回调Presenter
的代码来更新UI
Model
中的网络耗时任务持有Presenter
对象,而Presenter
又持有View
对象,当Activity
关闭时,Activity
对象无法被回收造成内存泄漏
NewCallback
写成静态内部类,并且使用WeakReference软引用
,使Presenter
能够及时的回收,这样Activity
也能及时回收
1.5 NewsModel 代码
public class NewsModel implements NewsContract.Model {
private final static String NEWS_URL = "http://news-at.zhihu.com/api/4/news/latest";
/**
* 从网络获取新闻内容
*
* @param callback 结果回调
*/
@Override
public void requestNewsFromNet(BaseCallback callback) {
OkGo.<String>get(NEWS_URL).execute(new InnerCallback(callback));
}
/**
* 取消网络请求
*/
@Override
public void cancel() {
OkGo.getInstance().cancelAll();
}
/**
* 结果回调
*/
private static class InnerCallback extends StringCallback {
private WeakReference<BaseCallback> mReference;
private InnerCallback(BaseCallback callback) {
mReference = new WeakReference<>(callback);
}
/**
* 网络
*/
@Override
public void onSuccess(Response<String> response) {
String content = response.body();
BaseCallback callback = mReference.get();
if (callback != null) {
callback.onSuccess(content);
}
}
@Override
public void onError(Response<String> response) {
super.onError(response);
String content = response.body();
BaseCallback callback = mReference.get();
if (callback != null) {
callback.onError(content);
}
}
/**
* 缓存
*/
@Override
public void onCacheSuccess(Response<String> response) {
super.onCacheSuccess(response);
String content = response.body();
BaseCallback callback = mReference.get();
if (callback != null) {
callback.onSuccess(content);
}
}
}
}
Demo
网络请求使用的OkGo
网络框架,很强大蛮好用的网络框架
内部也使用一个静态内部类,使用WeakReference
2. 单元测试 TODO
单元测试主要针对的Presenter
,主要的逻辑都在P
层
View
层主要是UI
,Model
主要是拉取数据,时间紧,就先不做,不赶时再做
P
层单元测试通过后,很大程度就说明逻辑通过
由于P
层不包含Android
环境代码,可以使用JUint + Mockit
来进行单元测试。这也是为啥说Presenter
中尽量不要有Android SDK
代码的原因
单元测试正在学,等实际做一段后再来补充