Android MVP框架模式
结合前一篇MVC框架模式
为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP(Model-View-Presenter)模式应运而生。
在MVP模式里通常包含4个要素:
(1)View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity);
(2)View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;
(3)Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);
(4)Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
MVP模式步骤:
1、创建一个IView让Presenter去依赖接口,不管数据时从哪里来,只要提供接口。
2、让Activity或者Fragment去实现接口,实现所有View的操作。
3、创建主持人,注入IView的接口,数据都从接口中获取。
4、创建Model,实现数据加载(网络、数据路、假数据)
5、使用主持人访问Model,获取数据后,调用IView去刷新布局。
还是使用藏头诗接口的例子
布局文件
1 xmlns:tools="http://schemas.android.com/tools" 2 android:id="@+id/activity_main" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 tools:context="com.example.lesson_mvc_cangtoushi.ui.MainActivity"> 7 8 <RadioGroup 9 android:id="@+id/rg_57" 10 android:layout_width="match_parent" 11 android:layout_height="wrap_content" 12 android:orientation="horizontal"> 13 14 <RadioButton 15 android:id="@+id/rb_5" 16 android:layout_width="0dp" 17 android:layout_height="wrap_content" 18 android:layout_weight="1" 19 android:gravity="center" 20 android:text="五言诗" /> 21 22 <RadioButton 23 android:id="@+id/rb_7" 24 android:layout_width="0dp" 25 android:layout_height="wrap_content" 26 android:layout_weight="1" 27 android:gravity="center" 28 android:text="七言诗" /> 29 </RadioGroup> 30 31 <RadioGroup 32 android:id="@+id/rg_ct" 33 android:layout_width="match_parent" 34 android:layout_height="wrap_content" 35 android:orientation="horizontal"> 36 37 <RadioButton 38 android:id="@+id/rb_ct" 39 android:layout_width="0dp" 40 android:layout_height="wrap_content" 41 android:layout_weight="1" 42 android:gravity="center" 43 android:text="藏头" /> 44 45 <RadioButton 46 android:id="@+id/rb_cw" 47 android:layout_width="0dp" 48 android:layout_height="wrap_content" 49 android:layout_weight="1" 50 android:gravity="center" 51 android:text="藏尾" /> 52 53 <RadioButton 54 android:id="@+id/rb_cz" 55 android:layout_width="0dp" 56 android:layout_height="wrap_content" 57 android:layout_weight="1" 58 android:gravity="center" 59 android:text="藏中" /> 60 61 <RadioButton 62 android:id="@+id/rb_dz" 63 android:layout_width="0dp" 64 android:layout_height="wrap_content" 65 android:layout_weight="1" 66 android:gravity="center" 67 android:text="递增" /> 68 69 <RadioButton 70 android:id="@+id/rb_dj" 71 android:layout_width="0dp" 72 android:layout_height="wrap_content" 73 android:layout_weight="1" 74 android:gravity="center" 75 android:text="递减"/> 76 77 </RadioGroup> 78 79 <RadioGroup 80 android:id="@+id/rg_yy" 81 android:layout_width="match_parent" 82 android:layout_height="wrap_content" 83 android:orientation="horizontal"> 84 85 <RadioButton 86 android:id="@+id/rb_1y" 87 android:layout_width="0dp" 88 android:layout_height="wrap_content" 89 android:layout_weight="1" 90 android:gravity="center" 91 android:text="双句一押" /> 92 93 <RadioButton 94 android:id="@+id/rb_2y" 95 android:layout_width="0dp" 96 android:layout_height="wrap_content" 97 android:layout_weight="1" 98 android:gravity="center" 99 android:text="双句押韵" /> 100 101 <RadioButton 102 android:id="@+id/rb_3y" 103 android:layout_width="0dp" 104 android:layout_height="wrap_content" 105 android:layout_weight="1" 106 android:gravity="center" 107 android:text="一三四押" /> 108 </RadioGroup> 109 <EditText 110 android:id="@+id/et_key" 111 android:layout_width="match_parent" 112 android:layout_height="wrap_content" 113 android:hint="请输入藏头诗"/> 114 <Button 115 android:id="@+id/btn_submit" 116 android:layout_width="match_parent" 117 android:layout_height="wrap_content" 118 android:text="提交"/> 119 120 <ScrollView 121 android:layout_width="match_parent" 122 android:layout_height="match_parent"> 123 <TextView 124 android:id="@+id/tv_show" 125 android:layout_width="match_parent" 126 android:layout_height="match_parent" /> 127 </ScrollView> 128 129 130 131 </LinearLayout>
java代码目录结构。可以看出MVP多了一个Presenter,主持人,还有一个View接口,极大的实现了解耦
藏头诗对象原型bean
1 public class CangTouShiBean { 2 3 4 /** 5 * showapi_res_code : 0 6 * showapi_res_error : 7 * showapi_res_body : {"ret_code":0,"list":["北风勇士马,晚水独芙蓉。吾将宝非宝,英雄徒自强。","朝骑五花马,太华三芙蓉。吾将宝非宝,天子贵文强。","请歌牵白马,菡萏金芙蓉。大位天下宝,自从冒顿强。","青丝系五马,秀出九芙蓉。迈德惟家宝,日来知自强。","北买党项马,美女夸芙蓉。河宗来献宝,十年思自强。","青丝系五马,大嫂采芙蓉。药妙灵仙宝,不独有文强。"]} 8 */ 9 10 private int showapi_res_code; 11 private String showapi_res_error; 12 private ShowapiResBodyBean showapi_res_body; 13 14 15 @Override 16 public String toString() { 17 return "CangTouShiBean{" + 18 "showapi_res_code=" + showapi_res_code + 19 ", showapi_res_error='" + showapi_res_error + '\'' + 20 ", showapi_res_body=" + showapi_res_body + 21 '}'; 22 } 23 24 public int getShowapi_res_code() { 25 return showapi_res_code; 26 } 27 28 public void setShowapi_res_code(int showapi_res_code) { 29 this.showapi_res_code = showapi_res_code; 30 } 31 32 public String getShowapi_res_error() { 33 return showapi_res_error; 34 } 35 36 public void setShowapi_res_error(String showapi_res_error) { 37 this.showapi_res_error = showapi_res_error; 38 } 39 40 public ShowapiResBodyBean getShowapi_res_body() { 41 return showapi_res_body; 42 } 43 44 public void setShowapi_res_body(ShowapiResBodyBean showapi_res_body) { 45 this.showapi_res_body = showapi_res_body; 46 } 47 48 public static class ShowapiResBodyBean { 49 /** 50 * ret_code : 0 51 * list : ["北风勇士马,晚水独芙蓉。吾将宝非宝,英雄徒自强。","朝骑五花马,太华三芙蓉。吾将宝非宝,天子贵文强。","请歌牵白马,菡萏金芙蓉。大位天下宝,自从冒顿强。","青丝系五马,秀出九芙蓉。迈德惟家宝,日来知自强。","北买党项马,美女夸芙蓉。河宗来献宝,十年思自强。","青丝系五马,大嫂采芙蓉。药妙灵仙宝,不独有文强。"] 52 */ 53 54 private int ret_code; 55 private List<String> list; 56 57 58 @Override 59 public String toString() { 60 return "ShowapiResBodyBean{" + 61 "ret_code=" + ret_code + 62 ", list=" + list + 63 '}'; 64 } 65 66 public int getRet_code() { 67 return ret_code; 68 } 69 70 public void setRet_code(int ret_code) { 71 this.ret_code = ret_code; 72 } 73 74 public List<String> getList() { 75 return list; 76 } 77 78 public void setList(List<String> list) { 79 this.list = list; 80 } 81 } 82 }
藏头诗Model实现藏头诗接口,请求数据,将结果回调出去
1 public interface BeanCallback<T> { 2 3 void onError(String msg); 4 void onSuccess(T t); 5 }
1 public interface ICangTouShi { 2 //请求数据,需要有变化的参数 3 void doRequest(String num, String type, String yayuntype, String key, BeanCallback<CangTouShiBean> callback); 4 5 }
1 public class CangTouShiModel implements ICangTouShi { 2 @Override 3 public void doRequest(String num, String type, String yayuntype, String key, final BeanCallback<CangTouShiBean> callback) { 4 5 //请求数据 6 //使用OkHttp 7 8 OkHttpClient client = new OkHttpClient(); 9 10 RequestBody body = new FormBody.Builder() 11 .add("showapi_appid","27306") 12 .add("showapi_sign","150e9206e7f542bab4affe49d73cb920") 13 .add("num",num) 14 .add("type",type) 15 .add("yayuntype",yayuntype) 16 .add("key",key).build(); 17 18 Request request = new Request.Builder() 19 .post(body) 20 .url("http://route.showapi.com/950-1").build(); 21 okhttp3.Call call = client.newCall(request); 22 //异步请求,子线程 23 call.enqueue(new Callback() { 24 @Override 25 public void onFailure(okhttp3.Call call, IOException e) { 26 Log.e("TAG","-----------"+e.getMessage()); 27 callback.onError(e.getMessage()); 28 } 29 30 @Override 31 public void onResponse(okhttp3.Call call, Response response) throws IOException { 32 String json = response.body().string(); 33 Gson gson = new Gson(); 34 CangTouShiBean bean = gson.fromJson(json, CangTouShiBean.class); 35 callback.onSuccess(bean); 36 } 37 38 }); 39 40 } 41 42 }
定义一个View的接口,声明一个些方法
1 public interface IMvpView { 2 3 String getNum(); 4 5 String getType(); 6 7 String getYY(); 8 9 String getKey(); 10 11 void showDialog(); 12 13 void dismissDialog(); 14 15 void setText(String text); 16 17 void showToast(String msg); 18 19 }
定义了主持人Presenter,构造方法传入以个IMvpView的对象,声明了一个CangTouModel的对象,并在构造的时候创建。然后就可以用着两个引用的方法,请求数据
1 public class MvpPresenter { 2 IMvpView iMvpView; 3 ICangTouShi cangTouShi; 4 5 public MvpPresenter(IMvpView iMvpView){ 6 this.iMvpView = iMvpView; 7 cangTouShi = new CangTouShiModel(); 8 } 9 10 public void getData(){ 11 //判断key不能为null 12 if(iMvpView.getKey().equals("")){ 13 iMvpView.showToast("Key不能为空"); 14 return; 15 } 16 iMvpView.showDialog(); 17 18 cangTouShi.doRequest(iMvpView.getNum(), iMvpView.getType(), iMvpView.getYY(), iMvpView.getKey(), new BeanCallback<CangTouShiBean>() { 19 @Override 20 public void onError(String msg) { 21 iMvpView.showToast(msg); 22 iMvpView.dismissDialog(); 23 } 24 25 @Override 26 public void onSuccess(CangTouShiBean cangTouShiBean) { 27 String msg = ""; 28 for (String s : cangTouShiBean.getShowapi_res_body().getList()) { 29 msg += s+"\n"; 30 } 31 iMvpView.setText(msg); 32 iMvpView.dismissDialog(); 33 } 34 }); 35 36 } 37 }
MainActivity加载视图,实现IMvpView的接口,实现里面的方法,拿到Presenter的对象,调用getData获取到数据。
1 public class MainActivity extends AppCompatActivity implements IMvpView, View.OnClickListener { 2 3 RadioGroup rg_57, rg_ct, rg_yy; 4 EditText et_key; 5 Button btn_submit; 6 TextView tv_show; 7 8 MvpPresenter presenter; 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_main); 14 15 initView(); 16 presenter = new MvpPresenter(this); 17 } 18 19 20 private void initView() { 21 rg_57 = (RadioGroup) findViewById(R.id.rg_57); 22 rg_57.check(R.id.rb_5); 23 rg_ct = (RadioGroup) findViewById(R.id.rg_ct); 24 rg_ct.check(R.id.rb_ct); 25 rg_yy = (RadioGroup) findViewById(R.id.rg_yy); 26 rg_yy.check(R.id.rb_1y); 27 et_key = (EditText) findViewById(R.id.et_key); 28 btn_submit = (Button) findViewById(R.id.btn_submit); 29 tv_show = (TextView) findViewById(R.id.tv_show); 30 btn_submit.setOnClickListener(this); 31 32 33 } 34 35 @Override 36 public String getNum() { 37 return rg_57.getCheckedRadioButtonId() == R.id.rb_5 ? "5" : "7"; 38 } 39 40 @Override 41 public String getType() { 42 String type = null; 43 switch (rg_ct.getCheckedRadioButtonId()){ 44 case R.id.rb_ct: 45 type = "1"; 46 break; 47 case R.id.rb_cw: 48 type = "2"; 49 break; 50 case R.id.rb_cz: 51 type = "3"; 52 break; 53 case R.id.rb_dz: 54 type = "4"; 55 break; 56 case R.id.rb_dj: 57 type = "5"; 58 break; 59 } 60 return type; 61 } 62 63 @Override 64 public String getYY() { 65 String yy= null; 66 switch (rg_yy.getCheckedRadioButtonId()){ 67 case R.id.rb_1y: 68 yy="1"; 69 break; 70 case R.id.rb_2y: 71 yy="2"; 72 break; 73 case R.id.rb_3y: 74 yy="3"; 75 break; 76 } 77 return yy; 78 } 79 80 @Override 81 public String getKey() { 82 return et_key.getText().toString(); 83 } 84 85 ProgressDialog dialog; 86 @Override 87 public void showDialog() { 88 dialog = new ProgressDialog(this); 89 dialog.setTitle("提示"); 90 dialog.setMessage("开始请求"); 91 dialog.show(); 92 } 93 94 @Override 95 public void dismissDialog() { 96 dialog.dismiss(); 97 } 98 99 @Override 100 public void setText(final String text) { 101 runOnUiThread(new Runnable() { 102 @Override 103 public void run() { 104 tv_show.setText(text); 105 } 106 }); 107 } 108 109 @Override 110 public void showToast(final String msg) { 111 runOnUiThread(new Runnable() { 112 @Override 113 public void run() { 114 showToast(msg); 115 } 116 }); 117 } 118 119 @Override 120 public void onClick(View v) { 121 //通过主持人请求 122 presenter.getData(); 123 } 124 }
对于MVP与MVC这两种模式,它们之间也有很大的差异。有一些程序员选择不使用任何一种模式,有一部分原因也许就是不能区分这两种模式差异。以下是这两种模式之间最关键的差异:
(参考文章:http://www.infragistics.com/community/blogs/todd_snyder/archive/2007/10/17/mvc-or-mvp-pattern-whats-the-difference.aspx)
MVP模式:
- View不直接与Model交互,而是通过与Presenter交互来与Model间接交互
- Presenter与View的交互是通过接口来进行的,更有利于添加单元测试
- 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑
MVC模式:
- View可以与Model直接交互
- Controller是基于行为的,并且可以被多个View共享
- 可以负责决定显示哪个View