MVP架构学习

MVP架构学习

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

需求

需求很简单,我们就做一个简单的登录功能,当点击界面上的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方法

方案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,使用异步的方式发送请求)。

小结

这种设计简单明了,直观具体,但是它有个局限性,它将所有的功能全部在一个类中完成,那只适合单人作战。我们想象一下,按照上面的方案,如果我们让一个人写界面(布局文件),让一个人写登录功能(Activity),那么写登录功能的人某一天将login函数改为了login2了,但他忘记告诉了写界面的人,那是不是就是出现了问题。即使他告诉了写界面的人说“你将界面的上的login换为login2”,人家愿不愿换还是一回事呢!!!

方案2

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

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

posted @ 2017-07-16 22:22  被罚站的树  阅读(343)  评论(0编辑  收藏  举报