本文MVP的sample实现效果:
github地址:https://github.com/xurui1995/MvpSample
老规矩,在说对MVP模式的理解之前还是要再谈谈MVC模式,了解了MVC的缺点。我们才知道为什么要用MVP。
关于MVC的图解,我在网上找到了一些图。如下:
MVC模式在开发web或者管理系统中应用很多,我们的View与人交互,人点击鼠标或者输入一些东西时,View会发送相应的指令给Controller,Controller接到指令,再去调用Model的方法去更新数据(大多是对数据的增删改查),Model处理完,View刷新显示。
MVC模式的缺点:
1:在android中,如果我们要用mvc模式,那么每层代表什么呢?
你可能会说:View对应android的layout.xml,Model对应android中对数据库的操作对网络等操作放在这里进行,Controller对应的则是Activity!
你说的都对,但是你不觉得这样的对应关系并不好吗,如果layout.xml对应View,那如果我们想动态的控制添加一些视图控件或者改变背景,那么该怎么办呢?
答曰:在Activity中添加代码。!!!这就是缺点之一所在:Activity既当爹(View)又当妈(Controller),layout.xml代表的View层控制能力太弱。
2:再看一遍我们的MVC的结构图,View和Model是互相联系的,存在耦合关系,这就给测试维护带来了难度。当我们想更换项目中的某个零件时,缺发现 太难拆下来!这个零件类的方法散布多处。关于MVC的结构图,忘了在哪听过一句经典的话,写三个字母,M,V,C,随便用线或箭头连字母,最后就是MVC的结构图。
说完了MVC,该主角登场了,上我们MVP的结构图。
好处不言而喻,View和Model无法通信了。
View层只负责与View有关的,操作View层时发出的事件传递给Presenter,Presenter去操作Model,操作完Model,再去通知View相应更新。
关于MVP的更多概念:
接下来,看看我们在项目中如何使用MVP模式,这里顺便使用了Retrofit和RXjava,建议你先了解它们的用法。
首先看我们的需求:输入Github登录名,点击搜索按钮,搜索并显示结果(登录名,昵称, followers,following)。
最终的项目结构:
bean类:
public class User { private String login; private String name; private int followers; private int following; public int getFollowers() { return followers; } public void setFollowers(int followers) { this.followers = followers; } public int getFollowing() { return following; } public void setFollowing(int following) { this.following = following; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
retrofit类主要是封装了利用Retrofit的网络请求
public interface GithubService { @GET("/users/{user}") Observable<User> getUser(@Path("user") String username); }
public class HttpMethods { public static final String BASE_URL = "https://api.github.com"; private static final int DEFAULT_TIMEOUT = 5; private Retrofit retrofit; private GithubService mGithubService; //构造方法私有 private HttpMethods() { //手动创建一个OkHttpClient并设置超时时间 OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); retrofit = new Retrofit.Builder() .client(httpClientBuilder.build()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl(BASE_URL) .build(); mGithubService = retrofit.create(GithubService.class); } private static class SingletonHolder{ private static final HttpMethods INSTANCE = new HttpMethods(); } //获取单例 public static HttpMethods getInstance(){ return SingletonHolder.INSTANCE; } public void getUser(Subscriber<User> subscriber ,String loginName){ mGithubService.getUser(loginName) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } }
接下来思考我们的MVP模式了,一些从我们的View层开始,我们需要先列出和View相关的方法(不涉及逻辑)。
1.显示xml的试图
2.ProgressDialog显示
3.ProgressDialog消失
4.显示错误信息
接下来看具体代码:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" > <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="80dp"/> <EditText android:id="@+id/ed_text" android:layout_centerInParent="true" android:hint="请输入搜索登录名" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:id="@+id/search_btn" android:text="查询" android:layout_centerHorizontal="true" android:layout_below="@+id/ed_text" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>
BaseView,BasePresentor , BaseModel三个接口
public interface BaseView { void showProgressDialog(); void hideProgressDialog(); void showText(User userbean); void showErrorMessage(String text); }
public interface BasePresenter<T extends BaseView> { void attachView(T view); void detachView(); void searchUser(String loginName); }
public interface BaseModel { void getUser(Subscriber<User> subscribe,String loginName); }
第二个接口interface BasePresenter<T extends BaseView>正是关键,至于为什么,可以用实现类去解释。
MainActivity实现BaseView接口,作为View层。
public class MainActivity extends AppCompatActivity implements BaseView { @InjectView(R.id.tv) TextView mTextView; @InjectView(R.id.search_btn) Button mButton; @InjectView(R.id.ed_text) EditText mEditText; private ProgressDialog dialog; private MainPresenter mMainPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.inject(this); initView(); mMainPresenter=new MainPresenter(); mMainPresenter.attachView(this); } /** * 一些初始化,这里为ProgressDialog的初始化 */ private void initView() { dialog=new ProgressDialog(this); dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); dialog.setMessage("正在搜索中"); } @OnClick(R.id.search_btn) void search(View view){ mMainPresenter.searchUser(mEditText.getText().toString()); } @Override public void showProgressDialog() { dialog.show(); } @Override public void hideProgressDialog() { dialog.dismiss(); } @Override public void showText(User userbean) { String temp=getResources().getString(R.string.user_format); String str=String.format(temp,userbean.getLogin(),userbean.getName(),userbean.getFollowers(),userbean.getFollowing()); mTextView.setText(str); } @Override public void showErrorMessage(String text) { Toast.makeText(this,text,Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); if(mMainPresenter!=null) mMainPresenter.detachView(); } }
当点击Button产生事件时,是将逻辑交给MainPresenter去处理的,对应关系 V ——> P
下面看MainPresenter代码和Model代码。
public class MainPresenter implements BasePresenter { private BaseView mMainView; private MainModel mModel; public MainPresenter() { mModel=new MainModel(); } @Override public void attachView(BaseView view) { mMainView=view; } @Override public void detachView() { mMainView=null; } @Override public void searchUser(String loginName) { if(TextUtils.isEmpty(loginName.trim())){ mMainView.showErrorMessage("请输入合法登录名"); return; } if (mModel!=null){ mModel.getUser(new Subscriber<User>() { @Override public void onStart() { //先显示对话框 mMainView.showProgressDialog(); } @Override public void onCompleted() { //请求结束,对话框消失 mMainView.hideProgressDialog(); } @Override public void onError(Throwable e) { //error时 mMainView.showErrorMessage("搜索失败"); } @Override public void onNext(User user) { mMainView.showText(user); } },loginName); } } }
public class MainModel implements BaseModel{ @Override public void getUser(Subscriber<User> subscriber ,String loginName) { HttpMethods.getInstance().getUser(subscriber,loginName); } }
这里的Model实现类较为简单,直接使用了封装好的HttpMethods的方法。(Model可以理解为一个仓库管理员,我们的网络也能理解为一个大的仓库)。
在MainPresenter中我们其实是使用了Model的方法,即P——>M
然后用Rxjava的观察者观察结果,再去调用View的方法刷新界面,即P——>V
这时候回过来头来看我们的MVP图,是不是一模一样?(如何想要进阶mvp,可以试试契约类)