Android MVP模式實(shí)踐總結(jié)(附帶簡單例子)
最近在做畢業(yè)設(shè)計(jì),題目不是很難,但是后來發(fā)現(xiàn)功能有點(diǎn)多,感覺是苦力活,還好用的是設(shè)計(jì)MVP模式,雖然剛開始也不是特別理解,不過寫的東西多就會(huì)發(fā)現(xiàn)這個(gè)模式的好處。
- 代碼雖然好像多了,但思路非常清晰,敲代碼的速度特別快,特別酸爽。
- 出現(xiàn)Bug很容易知道哪里出差錯(cuò)。
粽子,感覺開發(fā)效率快了,然后錯(cuò)誤少了。
感覺,學(xué)MVP最好還是看下官方的介紹,如果英文過得去的話。
https://github.com/googlesamples/android-architecture/tree/todo-mvp/
首先, 為什么不用傳統(tǒng)的MVC(model, view, controller)呢,因?yàn)閭鹘y(tǒng)的MVC模式在Android中,把View和controller都雜糅到Activity當(dāng)中,導(dǎo)致Activity越來越臃腫,使得難以開發(fā)和維護(hù)。而MVP(model, view, presenter)模式中,則把一些業(yè)務(wù)邏輯分離出來放到Presenter中,在MVP中的M(model)和V(view)是沒有箭頭連接的,也就是MVP中(View只需要做些與用戶相關(guān)的操作,即繪制界面以及與用戶交互),其他的業(yè)務(wù)功能邏輯都交由Presenter來完成。
在官方介紹中有Contract這個(gè)Interface,里面定義了View和Presenter接口,其實(shí)Contract在mvp中什么角色也不是,只是把view和presenter定義在一起方便管理。
現(xiàn)在給個(gè)簡單例子:
- 假設(shè)我們要從網(wǎng)絡(luò)獲取數(shù)據(jù)并顯示用戶的個(gè)人界面;
- 同時(shí)我們可以更改用戶數(shù)據(jù)并上傳到服務(wù)器;
- 為了簡單起見并不真正的獲取網(wǎng)絡(luò)數(shù)據(jù),也省去了異步等操作,在實(shí)際開發(fā)中都是要考慮的。
首先來看View和Presenter的接口定義
public interface BasePresenter {
void start();
}
public interface BaseView<T> {
void setPresenter(T presenter);
}
public interface UserInfoContract {
interface Presenter extends BasePresenter{
void loadUserInfo();
void changeAge();
}
interface View extends BaseView<Presenter>{
void showUserInfoView(UserInfo userInfo);
void showErrorMessage(String errMsg);
UserInfo getUserInfo();
}
}
可以看到在BaseView這個(gè)基類接口中含有setPresenter,從圖上也可以看出,View和Presenter是可以相互交換數(shù)據(jù)的,所以一般都會(huì)互相持有對方的引用。我們再看看它們的實(shí)現(xiàn)類。
public class UserInfoActivity extends AppCompatActivity implements UserInfoContract.View{
TextView nameTxt;
TextView ageTxt;
EditText newAgeEdit;
Button change;
UserInfoContract.Presenter presenter;
UserInfo info;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_info);
nameTxt = (TextView)findViewById(R.id.name);
ageTxt = (TextView)findViewById(R.id.age);
newAgeEdit = (EditText)findViewById(R.id.new_age);
change = (Button)findViewById(R.id.change);
change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String str = newAgeEdit.getText().toString();
if(str.length() !=0 ){
int ageInt = Integer.valueOf(str);
info.setAge(ageInt);
presenter.changeAge();
}
}
});
//創(chuàng)建Presenter
UserInfoContract.Presenter presenter =new UserInfoPresenter(this);
}
@Override
public void showUserInfoView(UserInfo userInfo) {
//更新用戶界面
info = userInfo;
nameTxt.setText(userInfo.getName());
ageTxt.setText(userInfo.getAge() + "");
}
@Override
public void showErrorMessage(String errMsg) {
//顯示錯(cuò)誤信息
Toast.makeText(this,errMsg,Toast.LENGTH_LONG).show();
}
@Override
public UserInfo getUserInfo() {
return info;
}
@Override
public void setPresenter(UserInfoContract.Presenter presenter) {
this.presenter = presenter;
}
}
public class UserInfoPresenter implements UserInfoContract.Presenter {
UserInfoContract.View view;
UserInfo info;
public UserInfo getNetworkData(){
//模擬網(wǎng)絡(luò)io等操作,獲取數(shù)據(jù)
if(info == null){
info = new UserInfo();
info.setAge(16);
info.setId(1);
info.setName("lawliex");
}
return info;
}
public UserInfoPresenter(UserInfoContract.View view) {
this.view = view;
view.setPresenter(this);
start();
}
@Override
public void loadUserInfo() {
UserInfo userInfo = getNetworkData();
//取得數(shù)據(jù)后,因?yàn)閜resenter持有view的引用,可以回調(diào)view的相關(guān)方法,此處假設(shè)加載數(shù)據(jù)成功,回調(diào)顯示正常界面
view.showUserInfoView(userInfo);
//如果發(fā)生錯(cuò)誤則回調(diào)
//view.showErrorMessage("error");
}
@Override
public void changeAge() {
info = view.getUserInfo();
//模擬更新數(shù)據(jù)到服務(wù)器,成功后,更新View的數(shù)據(jù)
start();
}
@Override
public void start() {
loadUserInfo();
}
}
項(xiàng)目源碼