MVP開發(fā)模式
MVP圖解.png
- Model: 主要用于業(yè)務(wù)操作,如:網(wǎng)絡(luò)請求谆焊,數(shù)據(jù)存儲等
- Presenter: 主要用于邏輯處理惠桃,溝通 M 與 V ,盡可能不包含Android的代碼
- View: 主要用于規(guī)定界面的行為
優(yōu)點
- 由于Presenter層的出現(xiàn),減少了View的邏輯操作和負(fù)擔(dān)辜王。這樣使 View 與 Model 之間耦合度低
- 合理規(guī)劃的話劈狐,模塊分明,模塊復(fù)用率高呐馆,便于測試
缺點
- 由于抽出了一層 Presenter肥缔,所以導(dǎo)致類和代碼有一定的增加
- 如果不能進(jìn)行合理規(guī)劃的將會導(dǎo)致后期模塊雜亂,代碼冗余度高
- 糾結(jié)將 服務(wù) 和 廣播 放在何處
- Presenter 會持有 View 的引用汹来,如果不進(jìn)行解綁會造成內(nèi)存泄露
說到最后续膳,其實 MVP 只是一種思想,沒有什么固定的代碼收班,固定的格式坟岔。因為多在實踐中,慢慢理解和多多總結(jié)摔桦。不過在開發(fā)前一定要做好項目分析規(guī)劃炮车,切忌立馬動手寫代碼。
MVP架構(gòu)封裝使用(登錄功能示范)
GitHub傳送門 --->MVPzzz(包含以下示例)
MVPzzz架構(gòu)封裝(未完成)
導(dǎo)包
Step 1.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
- 在所有的 repositories 中都加入上面語句酣溃,否則無法導(dǎo)入成功
Step 2.
dependencies {
compile 'com.github.KittoZZZ:MVPzzz:v.0.0.2'
}
包含契約類
目錄結(jié)構(gòu).png
1.建立View和Presenter的契約類
public class LoginContract {
public interface ILoginView extends IBaseView {
void LoginSuccess();
void loginFail(String msg);
}
public interface ILoginPresenter {
void toLogin(User user);
}
}
View層接口必須繼承IBaseView
可以很明顯的看出 View 和 Presenter 的關(guān)系
2.新建Model類(結(jié)合RxJava)
public class UserModel extends BaseModel {
public Observable<String> toLogin(User user) {
//Retrofit2
String result = "fail";
if ("zzz".equals(user.getAccount()) && "123".equals(user.getPassword())) {
result = "success";
}
return Observable.just(result);
}
}
- BaseModel中并為沒有實現(xiàn)什么功能瘦穆,只是先留出,后序可能進(jìn)行一些更改
- 只是用于模擬所以并沒有進(jìn)行網(wǎng)絡(luò)請求赊豌,寫死數(shù)據(jù)
3.新建Presenter類實現(xiàn)契約類中的P層接口
public class LoginPresenter extends BasePresenter<LoginContract.ILoginView> implements ILoginContract.ILoginPresenter {
@InjectModel
private UserModel userModel;
@Override
public void toLogin(User user) {
userModel.toLogin(user)
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Exception {
if ("success".equals(result)) {
getView().LoginSuccess();
} else {
getView().loginFail(result);
}
}
});
}
}
- getView() 方法用于調(diào)用 View 層的接口方法扛或,如:LoginSuccess(),loginFail()
- 必須要寫B(tài)asePresenter泛型,LoginContract.ILoginView碘饼,否則無法調(diào)用它的方法
- 使用注解 @InjectModel 進(jìn)行注入 Model 對象
4.新建Activity類實現(xiàn)契約類中的V層接口
public class LoginActivity extends BaseMvpActivity implements LoginContract.ILoginView {
private Button btnLogin;
private EditText etAccount;
private EditText etPassword;
@InjectPresenter
private LoginPresenter loginPresenter;
@Override
protected int setContentView() {
return R.layout.activity_login;
}
@Override
protected void initView() {
etAccount = this.findViewById(R.id.et_account);
etPassword = this.findViewById(R.id.et_password);
btnLogin = this.findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = etAccount.getText().toString().trim();
String password = etPassword.getText().toString().trim();
User user = new User(account, password);
loginPresenter.toLogin(user);
}
});
}
@Override
protected void initData() {
}
@Override
public void LoginSuccess() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
@Override
public void loginFail(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
- 繼承 BaseMvpActivity 實現(xiàn) LoginContract.ILoginView 熙兔,重寫方法
- 使用注解 @InjectPresenter 進(jìn)行注入 Presenter 對象
去除契約類(多個Presenter例子)
"根據(jù)設(shè)計原則 第一條 單一原則 LoginContract 這個類就把v和p耦合了 應(yīng)該把view接口 和 presenter分離開 作為兩個獨立接口 然后分別由view和presenter子類實現(xiàn)"
上面是來自大牛的評論,經(jīng)過思考確實將 V 層和 P 層的耦合了艾恼,所以思考將契約類去除的做法住涉。
假設(shè),在登錄界面中的記住密碼功能钠绍,需要在登錄成功后將賬號和密碼進(jìn)行保存在本地舆声。下次登錄的時候,直接讀取顯示在對應(yīng)的輸入框中柳爽。
目錄結(jié)構(gòu)改.png
- 從目錄結(jié)構(gòu)中可以看出多了 UserDataPresenter 與 DataModel 兩個關(guān)鍵的類
- 主要是 V 層在初始化的時候調(diào)用 UserDataPresenter 去讀取媳握,登錄成功的時候保存數(shù)據(jù)
- 由于只是個例子所以 DataModel 使用 SP 進(jìn)行數(shù)據(jù)的存儲
- AppContext 只是用于在 M 層獲取 ApplicationContext 的工具類
LoginActivity部分代碼
public class LoginActivity extends BaseMvpActivity implements ILoginView, IUserDataView {
...
@InjectPresenter
private LoginPresenter loginPresenter;
@InjectPresenter
private UserDataPresenter userDataPresenter;
...
@Override
protected void initData() {
btnLogin.setClickable(false);
userDataPresenter.loadLastData();
}
@Override
public void showLoginLoading() {
Log.i("login", "showLoginLoading: 正在登陸");
}
@Override
public void hideLoginLoading() {
Log.i("login", "hideLoginLoading: 登錄結(jié)束");
}
@Override
public void loginSuccess() {
Log.i("login", "loginSuccess: 登錄成功");
...
userDataPresenter.saveData(user);
}
@Override
public void loginFail(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void saveDataSuccess() {
Log.i("login", "saveDataSuccess: 保存數(shù)據(jù)成功");
...
}
@Override
public void saveDataFail(String msg) {
Log.i("login", "saveDataFail: 保存數(shù)據(jù)失敗 " + msg);
}
@Override
public void readDataSuccess(User user) {
Log.i("login", "readDataSuccess: 讀取數(shù)據(jù)成功");
btnLogin.setClickable(true);
if (!TextUtils.isEmpty(user.getAccount()) && !TextUtils.isEmpty(user.getPassword())) {
etAccount.setText(user.getAccount());
etPassword.setText(user.getPassword());
// loginPresenter.toLogin(user);
}
}
@Override
public void readDataFail(String msg) {
Log.i("login", "loadDataFail: 讀取數(shù)據(jù)失敗" + msg);
}
}
- 在界面打開的時候開始讀取保存本地的數(shù)據(jù),此時登錄按鈕為不可點擊
- 讀取成功磷脯,將登錄按鈕設(shè)為可點擊蛾找,并且將數(shù)據(jù)現(xiàn)在輸入框中
- 如果自動登錄的話,如果滿足條件(賬號密碼不為空)則直接調(diào)用 P 層去登錄
UserDataPresenter代碼
public class UserDataPresenter extends BasePresenter<IUserDataView> implements IUserDataPresenter {
@InjectModel
private DataModel dataModel;
@Override
public void saveData(User user) {
dataModel.saveUserData(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String result) throws Exception {
if ("success".equals(result)) {
getView().saveDataSuccess();
} else {
getView().saveDataFail("保存失敗");
}
}
});
}
@Override
public void loadLastData() {
dataModel.readUserData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<User>() {
@Override
public void accept(User user) throws Exception {
getView().readDataSuccess(user);
}
});
}
}
DataModel代碼就不展示赵誓,主要理解思想打毛,詳情可以看 --->MVPzzz(包含示例)
- 上面展示的是多個 Presenter 情況使用注解來實例化柿赊,這種方式同樣適用于多個 Model 的情況
- 正是這個例子,讓我覺得V層與P層是一一對應(yīng)的形式存在幻枉,可以說是耦合的
- 保留著契約類可以清晰的看出V和P的關(guān)系碰声,可能是個人水平較低,我還是會選擇保留契約類的做法
- 目前對于 MVP 開發(fā)模式的疑惑開始增多了.......
- Fragment 的使用與 Activity 類似