什麼是MVP
M:Model 數據層(網絡,數據庫,文件存儲等等…)
V:View UI層 View、Activity、Fragment/以及他們的子類
P:Presenter 中介(作用:將M層與V層進行關聯,交互的中介)
下面實現一個登陸功能
MVP基本案例
- 實例代碼
項目列表
HttpUtils
/**
* @作者: hyc
* @時間: 2019/12/2
* @說明:模擬網絡請求
**/
public class HttpUtils {
HttpResultListener resultListener;
public HttpUtils(String userName, int password, HttpResultListener resultListener) {
this.resultListener = resultListener;
if (userName.equals("10000") && password == 123456) {
resultListener.onResult("用戶:10000 登錄成功!");
} else {
resultListener.onResult("用戶:10000 登錄失敗");
}
}
public interface HttpResultListener {
void onResult(String result);
}
}
LoginModel
/**
* @作者: hyc
* @時間: 2019/12/2
* @說明:M層,網絡請求
**/
public class LoginModel1 {
public void login(String userName, int password, final HttpUtils.HttpResultListener httpResultListener) {
HttpUtils httpUtils = new HttpUtils(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
httpResultListener.onResult(result);
}
});
}
}
LoginView
public interface LoginView1 {
void loginResult(String result);
}
LoginPresenter
/**
* @作者: hyc
* @時間: 2019/12/2
* @說明:與M、V交互的中介
**/
public class LoginPresenter1 {
LoginModel1 loginModel;
LoginView1 loginView;
public LoginPresenter(LoginView1 loginView) {
this.loginModel = new LoginModel();
this.loginView = loginView;
}
public void login(String userName, int password) {
loginModel.login(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
if (loginView != null) {
loginView.loginResult(result);
}
}
});
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements LoginView1 {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LoginPresenter1 loginPresenter = new LoginPresenter1(MainActivity.this);
loginPresenter.login("10000",123456);
}
});
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
}
R.layout.activity_main
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登錄請求"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
android:textSize="15sp"
android:layout_marginTop="10dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_login"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
運行項目點擊登錄
- 介紹
// MVP實現 -> 簡單案例 -> 分層次設計 ->基本實現
// 團隊開發
// V層 -> MainActivity -> LoginView
// M層 -> 數據層(網絡請求、數據庫、文件等…)-> LoginModel
// P層 ->需要新建 -> LoginPresenter
// 總結:缺點(類結構複雜,接口多),優點(結構清晰,維護性強,有利於團隊開發,模塊維護,功能擴展,降低開發成本)
** // 特別適合大型項目,團隊開發**
優化第一步
分析問題:當Activity關閉的時候,而數據請求依然在進行,需要解除UI層與數據層的關聯
解決方案:方法綁定、解綁
attachView ->綁定 、detachView -> 解綁
把之前創建的LoginModel1、LoginPresenter1、LoginView1複製到另一個包下。
項目列表如下
修改LoginPresenter2
public class LoginPresenter2 {
LoginModel2 loginModel;
LoginView2 loginView;
public LoginPresenter2() {
this.loginModel = new LoginModel2();
}
//綁定
public void attachView(LoginView2 loginView) {
this.loginView = loginView;
}
//解綁
public void detachView() {
this.loginView = null;
}
public void login(String userName, int password) {
loginModel.login(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
if (loginView != null) {
loginView.loginResult(result);
}
}
});
}
}
MainActivity修改
public class MainActivity extends AppCompatActivity implements LoginView2 {
private LoginPresenter2 loginPresenter2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginPresenter2 = new LoginPresenter2();
loginPresenter2.attachView(MainActivity.this);
loginPresenter2.login("10000", 123456);
}
});
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter2.detachView();
}
}
運行項目,結果一樣。
優化第二步
分析問題:
現在寫一個功能,沒啥問題,如果功能多了,綁定和解除綁定會很煩,爲了統一管理綁定
解決方法:抽象類(把公共抽取出來) -> BasePresenter
重複上一步複製操作
項目列表如下
新建BasePresenter
public abstract class BasePresenter {
private LoginView3 loginView;
//綁定
public void attachView(LoginView3 loginView) {
this.loginView = loginView;
}
//解綁
public void detachView() {
this.loginView = null;
}
public LoginView3 getLoginView() {
return loginView;
}
}
修改LoginPresenter
public class LoginPresenter3 extends BasePresenter {
LoginModel3 loginModel;
public LoginPresenter3() {
this.loginModel = new LoginModel3();
}
public void login(String userName, int password) {
loginModel.login(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
if (getLoginView() != null) {
getLoginView().loginResult(result);
}
}
});
}
}
再修改MainActivity
public class MainActivity extends AppCompatActivity implements LoginView3 {
private LoginPresenter3 loginPresenter3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginPresenter3 = new LoginPresenter3();
loginPresenter3.attachView(MainActivity.this);
loginPresenter3.login("10000", 123456);
}
});
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter3.detachView();
}
}
運行項目,結果一樣
優化第三步
分析問題:BasePresenter抽象類寫死了,只有LoginView,可能還會有登錄或者註冊,或者獲取驗證碼,
希望能動態抽象
解決方案:BaseView解決
重複複製操作
項目列表如下
新建BaseView
public interface BaseView {
}
修改BasePresenter
public abstract class BasePresenter4 {
private BaseView loginView;
//綁定
public void attachView(BaseView loginView) {
this.loginView = loginView;
}
//解綁
public void detachView() {
this.loginView = null;
}
public BaseView getLoginView() {
return loginView;
}
}
修改LoginView4
public interface LoginView4 extends BaseView {
void loginResult(String result);
}
修改LoginPresenter4
public class LoginPresenter4 extends BasePresenter4 {
LoginModel4 loginModel;
public LoginPresenter4() {
this.loginModel = new LoginModel4();
}
public void login(String userName, int password) {
loginModel.login(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
if (getLoginView() != null) {
//這裏強轉
LoginView4 view= (LoginView4) getLoginView();
view.loginResult(result);
}
}
});
}
}
修改MainActivity
public class MainActivity extends AppCompatActivity implements LoginView4 {
private LoginPresenter4 loginPresenter4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginPresenter4 = new LoginPresenter4();
loginPresenter4.attachView(MainActivity.this);
loginPresenter4.login("10000", 123456);
}
});
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter4.detachView();
}
}
運行項目,結果一樣
優化第四步
分析問題:每次都需要強制類型轉換。
解決方案:泛型設計
重複複製操作
項目列表如下
修改BasePresenter5
public abstract class BasePresenter5 <V extends BaseView5>{
private V loginView;
//綁定
public void attachView(V loginView) {
this.loginView = loginView;
}
//解綁
public void detachView() {
this.loginView = null;
}
public V getLoginView() {
return loginView;
}
}
修改LoginPresenter5
public class LoginPresenter5 extends BasePresenter5<LoginView5> {
LoginModel5 loginModel;
public LoginPresenter5() {
this.loginModel = new LoginModel5();
}
public void login(String userName, int password) {
loginModel.login(userName, password, new HttpUtils.HttpResultListener() {
@Override
public void onResult(String result) {
if (getLoginView() != null) {
getLoginView().loginResult(result);
}
}
});
}
}
修改MainActivity
public class MainActivity extends AppCompatActivity implements LoginView5 {
private LoginPresenter5 loginPresenter5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginPresenter5 = new LoginPresenter5();
loginPresenter5.attachView(MainActivity.this);
loginPresenter5.login("10000", 123456);
}
});
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter5.detachView();
}
}
運行項目,結果一樣
優化第五步
分析問題:一個Activity需要去綁定與解除綁定,如果Activty和Fragment多了,很多代碼冗餘
解決方案:抽象類 ->抽象出綁定與解除綁定 ->BaseActivity
重複複製操作
項目列表如下
新建BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
private LoginPresenter6 presenter6;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}
private void init() {
presenter6 = createPresenter();
presenter6.attachView(createView());
}
public LoginPresenter6 getPresenter6() {
return presenter6;
}
public abstract LoginPresenter6 createPresenter();
public abstract LoginView6 createView();
@Override
protected void onDestroy() {
super.onDestroy();
presenter6.detachView();
}
}
修改MainActivity
public class MainActivity extends BaseActivity implements LoginView6 {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter6().login("10000", 123456);
}
});
}
@Override
public LoginPresenter6 createPresenter() {
return new LoginPresenter6();
}
@Override
public LoginView6 createView() {
return this;
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
}
運行項目,結果一樣
優化第六步
分析問題:BaseActivity還是寫死了,未滿足要求,只能用LoginPresenter6
解決方案:BaseActivity中抽象實現(BasePresenter和BaseView)
重複複製操作
項目列表如下
修改BaseActivity7
public abstract class BaseActivity7 extends AppCompatActivity {
private BasePresenter7 presenter7;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}
private void init() {
presenter7 = createPresenter();
presenter7.attachView(createView());
}
public BasePresenter7 getPresenter7() {
return presenter7;
}
public abstract BasePresenter7 createPresenter();
public abstract BaseView7 createView();
@Override
protected void onDestroy() {
super.onDestroy();
presenter7.detachView();
}
}
修改MainActivity
public class MainActivity extends BaseActivity7 implements LoginView7 {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LoginPresenter7 presenter7 = (LoginPresenter7) getPresenter7();
presenter7.login("10000", 123456);
}
});
}
@Override
public LoginPresenter7 createPresenter() {
return new LoginPresenter7();
}
@Override
public LoginView7 createView() {
return this;
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
}
運行項目,結果一樣
優化第七步
分析問題:還需要強制類型轉換
解決方案:泛型設計
重複複製操作
項目列表如下
修改BaseActivity8
public abstract class BaseActivity8<V extends BaseView8, P extends BasePresenter8<V>> extends AppCompatActivity {
private P presenter8;
private V view8;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}
private void init() {
presenter8 = createPresenter();
if (presenter8 == null) {
throw new NullPointerException("presenter8 is null");
}
view8 = createView();
if (view8 == null) {
throw new NullPointerException("view8 is null");
}
presenter8.attachView(view8);
}
public P getPresenter8() {
return presenter8;
}
public abstract P createPresenter();
public abstract V createView();
@Override
protected void onDestroy() {
super.onDestroy();
if (presenter8 != null) {
presenter8.detachView();
}
}
}
修改MainActivity
public class MainActivity extends BaseActivity8<LoginView8, LoginPresenter8> implements LoginView8 {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter8().login("10000", 123456);
}
});
}
@Override
public LoginPresenter8 createPresenter() {
return new LoginPresenter8();
}
@Override
public LoginView8 createView() {
return this;
}
@Override
public void loginResult(String result) {
TextView textView = findViewById(R.id.tv_result);
textView.setText(result);
}
}
運行項目,結果一樣
結言
我也是學習別人的自己總結記錄下來,方便以後查看,俗話說,好記性不如爛筆頭,文章寫得不是很好,不喜勿噴,謝謝,有錯誤的地方,歡迎留言
源碼入口github如果有幫助請幫我start 感謝!。