公司項(xiàng)目代碼中使用MVP
弛针,之前并沒有在項(xiàng)目中使用過(guò)叠骑,記錄一下最近一段個(gè)人寫代碼時(shí)學(xué)到的習(xí)慣,代碼中有哪些不合理的地方削茁,希望可以留言指出
1. Demo 代碼
項(xiàng)目中宙枷,是按照模塊分的包,例如新聞
相關(guān)的代碼就放在同一個(gè)news
包下
寫代碼時(shí)一個(gè)重要的思路:
V層只負(fù)責(zé)UI展示茧跋,任何數(shù)據(jù)源盡量不放V層
P層負(fù)責(zé)邏輯處理朦拖,盡量不要有任何Android環(huán)境的代碼,尤其是 Context厌衔,更符合MVP的規(guī)范璧帝,并且方便單元測(cè)試
M層負(fù)責(zé)數(shù)據(jù)獲取,主要負(fù)責(zé)從網(wǎng)絡(luò)富寿、數(shù)據(jù)庫(kù)睬隶、本地文件中獲取數(shù)據(jù)源
寫完代碼锣夹,檢查寫的是否合理,簡(jiǎn)單的辦法就是查看各個(gè)分層代碼中類的引用
1.1 Activity 代碼
整個(gè)Actiivty
就一個(gè)Button
苏潜,點(diǎn)擊Button
請(qǐng)求網(wǎng)絡(luò)银萍,然后將數(shù)據(jù)顯示
/**
* 展示請(qǐng)求的新聞字符串
*/
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();
}
/**
* 取消網(wǎng)絡(luò)請(qǐng)求
*/
@Override
protected void onPause() {
super.onPause();
mPresenter.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDestroy();
mPresenter = null;
}
}
/**
* 展示請(qǐng)求的內(nèi)容
*
* @param content 數(shù)據(jù)
*/
@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
贴唇。有時(shí)實(shí)際項(xiàng)目中,需要考慮下初始化的合理順序
就一個(gè)Button
飞袋,在點(diǎn)擊監(jiān)聽中戳气,mPresenter.onBtNewsClick()
,調(diào)用P
層邏輯代碼
在onPause()
中,取消未完成的網(wǎng)絡(luò)請(qǐng)求
在onDestroy()
方法中巧鸭,做一些回收銷毀工作
void showRequestContent(String content)
就是實(shí)現(xiàn)的V
層接口回調(diào)的方法瓶您,在請(qǐng)求網(wǎng)絡(luò)后,展示內(nèi)容
1.2 Base 接口
BasePresenter
public interface BasePresenter {
/**
* 在 Activity onDestroy()生命周期方法
*/
void onDestroy();
}
聲明每一個(gè)Presenter
都要實(shí)現(xiàn)的銷毀階段
方法
BaseView
public interface BaseView {
}
在這個(gè)Demo
中纲仍,BaseView
里并沒有聲明啥方法呀袱,實(shí)際項(xiàng)目代碼中根據(jù)實(shí)際需要聲明View
層的共有方法
BaseCallback
public interface BaseCallback {
/**
* 成功回調(diào)
*
* @param content 請(qǐng)求內(nèi)容
*/
void onSuccess(String content);
/**
* 失敗回調(diào)
*
* @param errorInfo 錯(cuò)誤信息
*/
void onError(String errorInfo);
}
要進(jìn)行網(wǎng)絡(luò)請(qǐng)求,肯定需要用到Callback
郑叠,個(gè)人習(xí)慣定義一個(gè)Base
型接口
1.3 Contract 契約
interface NewsContract {
interface Model {
/**
* 從網(wǎng)絡(luò)請(qǐng)求新聞內(nèi)容
*
* @param callback 結(jié)果回調(diào)
*/
void requestNewsFromNet(BaseCallback callback);
/**
* 取消網(wǎng)絡(luò)請(qǐng)求
*/
void cancel();
}
interface View extends BaseView {
/**
* 展示請(qǐng)求的內(nèi)容
*
* @param content 數(shù)據(jù)
*/
void showRequestContent(String content);
}
interface Presenter extends BasePresenter {
/**
* 點(diǎn)擊按鈕
*/
void onBtNewsClick();
/**
* 在 onPause() 生命周期
*/
void onPause();
}
}
一般View
層接口主要以show
方法為主
Presenter
層方法個(gè)人習(xí)慣以onXX()
形式夜赵,方便在查看代碼,更容易通過(guò)名字知道方法負(fù)責(zé)做啥
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();
}
/**
* 點(diǎn)擊按鈕乡革,請(qǐng)求網(wǎng)絡(luò)
*/
@Override
public void onBtNewsClick() {
mNewModel.requestNewsFromNet(new NewsCallback(this));
}
/**
* 取消網(wǎng)絡(luò)請(qǐng)求
*/
@Override
public void onPause() {
mNewModel.cancel();
}
/**
* 銷毀
*/
@Override
public void onDestroy() {
if (mNewModel != null) {
mNewModel = null;
}
if (mView != null) {
mView = null;
}
}
/**
* 成功處理
*
* @param content 內(nèi)容
*/
private void onRequestSuccess(String content) {
mView.showRequestContent(content);
}
/**
* 失敗處理
*
* @param errorInfo 錯(cuò)誤信息
*/
private void onRequestError(String errorInfo) {
mView.showRequestContent(errorInfo);
}
/**
* 結(jié)果回調(diào)
*/
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);
}
}
}
}
在構(gòu)造方法關(guān)聯(lián)View
層油吭,并初始化Model
層對(duì)象
Model
進(jìn)行網(wǎng)絡(luò)請(qǐng)求,需要一個(gè)接口署拟,回調(diào)Presenter
的代碼來(lái)更新UI
Model
中的網(wǎng)絡(luò)耗時(shí)任務(wù)持有Presenter
對(duì)象,而Presenter
又持有View
對(duì)象歌豺,當(dāng)Activity
關(guān)閉時(shí)推穷,Activity
對(duì)象無(wú)法被回收造成內(nèi)存泄漏
NewCallback
寫成靜態(tài)內(nèi)部類,并且使用WeakReference軟引用
类咧,使Presenter
能夠及時(shí)的回收馒铃,這樣Activity
也能及時(shí)回收
1.5 NewsModel 代碼
public class NewsModel implements NewsContract.Model {
private final static String NEWS_URL = "http://news-at.zhihu.com/api/4/news/latest";
/**
* 從網(wǎng)絡(luò)獲取新聞內(nèi)容
*
* @param callback 結(jié)果回調(diào)
*/
@Override
public void requestNewsFromNet(BaseCallback callback) {
OkGo.<String>get(NEWS_URL).execute(new InnerCallback(callback));
}
/**
* 取消網(wǎng)絡(luò)請(qǐng)求
*/
@Override
public void cancel() {
OkGo.getInstance().cancelAll();
}
/**
* 結(jié)果回調(diào)
*/
private static class InnerCallback extends StringCallback {
private WeakReference<BaseCallback> mReference;
private InnerCallback(BaseCallback callback) {
mReference = new WeakReference<>(callback);
}
/**
* 網(wǎng)絡(luò)
*/
@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
網(wǎng)絡(luò)請(qǐng)求使用的OkGo
網(wǎng)絡(luò)框架,很強(qiáng)大蠻好用的網(wǎng)絡(luò)框架
內(nèi)部也使用一個(gè)靜態(tài)內(nèi)部類痕惋,使用WeakReference
2. 單元測(cè)試 TODO
單元測(cè)試主要針對(duì)的Presenter
区宇,主要的邏輯都在P
層
View
層主要是UI
,Model
主要是拉取數(shù)據(jù)值戳,時(shí)間緊议谷,就先不做,不趕時(shí)再做
P
層單元測(cè)試通過(guò)后堕虹,很大程度就說(shuō)明邏輯通過(guò)
由于P
層不包含Android
環(huán)境代碼卧晓,可以使用JUint + Mockit
來(lái)進(jìn)行單元測(cè)試芬首。這也是為啥說(shuō)Presenter
中盡量不要有Android SDK
代碼的原因
單元測(cè)試正在學(xué),等實(shí)際做一段后再來(lái)補(bǔ)充