MVP架构学习

MVP架构学习

M:数据层(数据库,文件,网络等...)
V:UI层(Activity,Fragment,View以及子类,Adapter以及子类)
P:中介,关联UI层和数据层,由于V和M是相互看不到对方的,简单而言就是不能相互持有对方的引用
MVP只是一种思想,不要把它认为是一种规范,要学会灵活用户,下面就带你们走进MVP模式的学习android

需求

需求很简单,咱们就作一个简单的登陆功能,当点击界面上的Login按钮,会向后台发送登陆请求。以下图所示,数据库

11

布局文件服务器

<?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方法网络

方案1

本方案中给出了最朴素的实现方式架构

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”,人家愿不肯换仍是一回事呢!!!异步

方案2

本方案中引入MVP思想,对上面的设计优化一下。ide

Model层

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层

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来显示登陆结果(成功/失败)

Presenter层

在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了。下来咱们继续优化上的设计

方案3

为了更好的解耦,那么咱们将会在View层引入接口类,接口的一大特性就是解耦。由于接口意味着规范,接口中的方法必需要实现,并且接口类一旦肯定,那么不多发生修改了。

Model层

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层

在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全部了,它属于接口类了,成为了一种规范。

Presenter

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引发的内存泄露的问题。

对Presenter优化

引入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;
    }
}
对MainActivity进行改进
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要销毁时,咱们就解除绑定,这样就防止发送内存泄露了。

方案4

前面的方案将View层进行了处理,使用了接口来使用V和P进行解耦,能不能作进一步处理,好比使用泛型。P层不用关心V层具体是什么类型。甚至对V层和P层之间的数据交互也作泛型处理。这样的话耦合程度更小了。

通用接口

将View层和Presenter层作接口化处理,固然Model层也能作接口化处理,因为时间有限,这里只实现前两个。

View接口
public interface IView<D> {
    void showResult(D result);
}

D是表示数据,这样showResult能够处理任意类型的数据了

prestener接口
public interface IPresenter<D, V extends IView<D>> {
    void attcheView(V view);

    void detachView();
}

在Presenter中咱们对V也作了泛型处理,这样Presenter能够既能够绑定Activity,又能够绑定Fragment

通用抽象类

抽象类是对接口作了一点点实现,这个看我的需求了,若是你感受类太多的话能够把接口剔除掉,直接使用抽象类来作。

Presenter抽象类
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接口作了些实现

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

相关文章
相关标签/搜索