M:數據層(數據庫,文件,網絡等...)
V:UI層(Activity,Fragment,View以及子類,Adapter以及子類)
P:中介,關聯UI層和數據層,由於V和M是相互看不到對方的,簡單而言就是不能相互持有對方的引用
MVP只是一種思想,不要把它認爲是一種規範,要學會靈活用戶,下面就帶你們走進MVP模式的學習android
需求很簡單,咱們就作一個簡單的登陸功能,當點擊界面上的Login按鈕,會向後臺發送登陸請求。以下圖所示,數據庫
佈局文件服務器
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.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="xdysite.cn.testdemo.MainActivity"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="login" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:onClick="login"/> </android.support.constraint.ConstraintLayout>
注:按鈕點擊會調用login方法網絡
本方案中給出了最樸素的實現方式架構
public class MainActivity extends AppCompatActivity { static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void login(View v) { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder().add("username", "admin").add("password", "12345").build(); Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.i(TAG, "onFailure: " + call.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Log.i(TAG, "onResponse: " + response.body().string()); } }); } }
上圖中當咱們點擊login按鈕的時候會調用MainActivity中的login方法,在login方法中會向服務器發送請求,並等待返回結果(這裏使用了OKHttp,使用異步的方式發送請求)。app
這種設計簡單明瞭,直觀具體,可是它有個侷限性,它將全部的功能所有在一個類中完成,那隻適合單人做戰。咱們想象一下,按照上面的方案,若是咱們讓一我的寫界面(佈局文件),讓一我的寫登陸功能(Activity),那麼寫登陸功能的人某一天將login函數改成了login2了,但他忘記告訴了寫界面的人,那是否是就是出現了問題。即便他告訴了寫界面的人說「你將界面的上的login換爲login2」,人家願不肯換仍是一回事呢!!!異步
本方案中引入MVP思想,對上面的設計優化一下。ide
Model咱們就讓其與服務器打交道,來實現登陸功能的邏輯,咱們實現了一個LoginModel類。函數
public class LoginModel { void login(String username, String password, final OnResultListener listener) { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build(); Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { listener.onResult(response.body().string()); } }); } public interface OnResultListener { void onResult(String result); } }
在LoginModel類對外暴露了一個login的方法來供別人調用,傳入的參數爲用戶名、密碼和監聽器。監聽器做用是當服務器返回結果時調用。
監聽器是LoginModel類的內部接口,須要調用者去實現該接口。佈局
View層就是咱們的Activity和佈局文件。View層將持有的P層的引用。下面是改造後的MainActivity類
public class MainActivity extends AppCompatActivity { static final String TAG = "MainActivity"; LoginPresenter mLoginPresener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLoginPresener = new LoginPresenter(this); } public void login(View v) { mLoginPresener.login("admin", "12345"); } public void showResult(String result) { Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); } }
在MainActivity類中持有了P層(LoginPresenter)的引用,當用戶點擊登陸時,它會調用P層的login方法,而且這裏還提供了一個showResult來顯示登陸結果(成功/失敗)
在P層充當中介的身份,它同時需瞭解V層和M層的狀況,所以P層將會持有V層和M層的引用。
public class LoginPresenter { LoginModel mLoginModel = new LoginModel(); MainActivity mLoginView; public LoginPresenter(MainActivity loginView) { mLoginView = loginView; } public void login(String usernanem, String password) { mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() { @Override public void onResult(String result) { mLoginView.showResult(result); } }); } }
在LoginPresenter類中同時持有了MainActivity的引用和LoginModel的引用,當在MainActivity中調用LoginPresenter的login方法時,LoginPresener會調用LoginModel中的login方法,而後在回調中仍是調用MainActivity的showResult方法。這樣LoginPresenter就完成了中介的職責。
經過MVP咱們將界面的處理和與服務器交互邏輯分離的開來,若是View層代碼被修改了,那麼M層的代碼將不會受任何影響,反之依然。這樣就解決了方案一種出現的爭端。這是MVP最簡單的運用了,說它簡單那麼存在不完善的地方。就拿V和P來講,LoginPresenter中調用了MainActivity的showResult方法,當這個方法被更名的話,在LoginPresenter中也要作一樣的修改。並且在建立LoginPresenter時也只能接受MainActivity對象,當你的老闆有天說咱們的登陸換成LoginFragment了,那你又要再寫一個接受LoginFragment對象的LoginPresenter了。下來咱們繼續優化上的設計
爲了更好的解耦,那麼咱們將會在View層引入接口類,接口的一大特性就是解耦。由於接口意味着規範,接口中的方法必需要實現,並且接口類一旦肯定,那麼不多發生修改了。
Model層的代碼咱們一點都不動
public class LoginModel { void login(String username, String password, final OnResultListener listener) { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build(); Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { listener.onResult(response.body().string()); } }); } public interface OnResultListener { void onResult(String result); } }
在View層中咱們定義了接口類LoginView,目的就是爲了讓V和P解耦。
public interface LoginView { void showResult(String result); }
public class MainActivity extends AppCompatActivity implements LoginView { static final String TAG = "MainActivity"; LoginPresenter mLoginPresener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLoginPresener = new LoginPresenter(this); } public void login(View v) { mLoginPresener.login("admin", "12345"); } @Override public void showResult(String result) { Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); } }
具體類實現了LoginView接口,則代表showResult方法不是MainActivity全部了,它屬於接口類了,成爲了一種規範。
P層如今持有的不是一個具體的View層對象了,它是面向接口的。無論你是Activity仍是Fragment,只要你實現了LoginView接口就好。並且它也不用擔憂View層胡亂改的問題了,只要你實現LoginView這個接口。
public class LoginPresenter { LoginModel mLoginModel = new LoginModel(); LoginView mLoginView; public LoginPresenter(LoginView loginView) { mLoginView = loginView; } public void login(String usernanem, String password) { mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() { @Override public void onResult(String result) { mLoginView.showResult(result); } }); } }
咱們將V和P經過接口的方式進行解耦了,咱們在MVP架構上又往前走了一步。可是,若是仔細研究代碼的話,咱們會發現有內存泄露的問題。當網絡請求發出去後,若是咱們立馬關閉Activity,那麼Activity會獲得釋放嗎?答案是不會,這個留給你們去思考。
補充部分是對方案3的小優化,主要解決Activity引發的內存泄露的問題。
引入attcheView和detachView方法來綁定視圖和解除視圖,如今這兩個方法都比較單薄,可是咱們在這個方法中能夠根據業務須要添加別的邏輯了。
public class LoginPresenter { LoginModel mLoginModel = new LoginModel(); LoginView mLoginView; public void login(String usernanem, String password) { mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() { @Override public void onResult(String result) { if (mLoginView != null) mLoginView.showResult(result); } }); } public void attcheView(LoginView loginView) { mLoginView = loginView; } public void detachView() { mLoginView = null; } }
public class MainActivity extends AppCompatActivity implements LoginView { static final String TAG = "MainActivity"; LoginPresenter mLoginPresener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLoginPresener = new LoginPresenter(); mLoginPresener.attcheView(this); } public void login(View v) { mLoginPresener.login("admin", "12345"); } @Override public void showResult(String result) { Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); mLoginPresener.detachView(); mLoginPresener = null; } }
在onCreate中咱們建立了LoginPresenter對象,並將本身與其綁定。當Acitvity要銷燬時,咱們就解除綁定,這樣就防止發送內存泄露了。
前面的方案將View層進行了處理,使用了接口來使用V和P進行解耦,能不能作進一步處理,好比使用泛型。P層不用關心V層具體是什麼類型。甚至對V層和P層之間的數據交互也作泛型處理。這樣的話耦合程度更小了。
將View層和Presenter層作接口化處理,固然Model層也能作接口化處理,因爲時間有限,這裏只實現前兩個。
public interface IView<D> { void showResult(D result); }
D是表示數據,這樣showResult能夠處理任意類型的數據了
public interface IPresenter<D, V extends IView<D>> { void attcheView(V view); void detachView(); }
在Presenter中咱們對V也作了泛型處理,這樣Presenter能夠既能夠綁定Activity,又能夠綁定Fragment
抽象類是對接口作了一點點實現,這個看我的需求了,若是你感受類太多的話能夠把接口剔除掉,直接使用抽象類來作。
public abstract class AbsPresenter<D, V extends IView<D>> implements IPresenter<D, V> { private V mView; @Override public void attcheView(V view) { mView = view; } @Override public void detachView() { mView = null; } public V getView() { return mView; } }
AbsPresenter類對Presenter接口作了些實現
public abstract class BaseActivity<D, V extends IView<D>, P extends AbsPresenter<D, V>> extends AppCompatActivity{ private P presenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (presenter == null) { presenter = bindPresenter(); presenter.attcheView((V)this); } } public abstract P bindPresenter(); public P fetchPresenter() { return presenter; } @Override protected void onDestroy() { super.onDestroy(); if (presenter != null) presenter.detachView(); } }
BaseActivity類對將視圖的綁定與解除抽了出來
前面的兩部分很抽象了,能夠應對各類場景了,下面咱們就應用到登陸場景中。
首先是LoginModel
其實Model和Presenter直接也能夠解耦的,能夠定義一個Model接口出來,而Presenter持有Model接口的引用便可。可是因爲時間關係。咱們直接上Model了
public class LoginModel { public void login(String username, String password, final OnResultListener listener) { OkHttpClient client = new OkHttpClient(); RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build(); Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { listener.onResult(response.body().string()); } }); } public interface OnResultListener { void onResult(String result); } }
下來是View
public interface LoginView extends MvpView<String>{ } public class MainActivity extends BaseActivity<String, LoginView, LoginPresenter> implements LoginView{ @Override public LoginPresenter bindPresenter() { return new LoginPresenter(); } @Override public void showResult(final String result) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show(); } }); } public void login(View v) { LoginPresenter presenter = fetchPresenter(); if (presenter != null) presenter.login("admin", "12345"); } }
定義了LoginView接口,並在MainActivity中對泛型作了具體化處理,好比數據類型指定爲String類型。並且咱們發現MainActivity中的代碼更少了。
最後是Presenter
public class LoginPresenter extends AbsPresenter<String, LoginView> { LoginModel mLoginModel = new LoginModel(); public void login(String username, String password) { mLoginModel.login(username, password, new LoginModel.OnResultListener() { @Override public void onResult(String result) { MvpView<String> loginView = getView(); if (loginView != null) loginView.showResult(result); } }); } }
咱們經過遞進的方式,一步一步對MVP架構進行完善,並且這是MVP架構中的一種體現方式。其核心思想急就是分離,這種思想在方案2已經體現出來了,因此不要拘泥於某種模版或規範,要靈活運用。