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已经体现出来了,因此不要拘泥于某种模版或规范,要灵活运用。