寫在前面:昨天晚上,公司請來專家講解了下MVP,並要求今后各自負責的模塊都要慢慢的轉到MVP模式上來。以前由於能力有限,沒有認真關注過設計模式、框架什么的,昨晚突然興趣大發,故這兩天空閑時間一直在學習MVP框架,公司不能上外網,不讓帶手機 ,只能吃飯坐班車時看看去公眾號里搜點相關文章。想在此做個記錄,希望原創者不要介意,再次感謝原創者
出自公眾號文章:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236921&idx=1&sn=4b2826b600a26b1cd3349ac91593b361&scene=1&srcid=0910LAe1TGDp0wpwPVoDx2Yo#wechat_redirect
即博客地址:http://blog.csdn.net/study_zhxu/article/details/52152895
本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
前言
隨着MVP概念的興起和發展,MVP使用越來越廣泛,當然MVP的優勢也越來越被認同,在合作開發功能模塊細分中MVP有着得天獨厚的優勢。本篇文章就來簡單的說說如何使用MVP。
什么是MVP
MVP是MVC的變種,其實是一種升級。要說MVP就要說說MVC,在MVC中Activity其實是View層級,但是通常在使用中Activity即使View也是Controller,並沒有將View層和Controller層進行分離,
耦合度大大提高,非常不利於項目的管理。這時候MVP就應運而生了。
1、MVP分為三層
Model View Presenter
2、MVP模式的核心思想
MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類還是原來的Model。
在MVP模式中Activity的功能就是響應生命周期和顯示界面,具體其他的工作都丟到了Presenter層中進行完成,Presenter其實是Model層和View層的橋梁。
具體的MVP的好處還有很多,就不詳細介紹了,下面看看如何使用MVP。
上面一張簡單的MVP模式的UML圖,從圖中可以看出,使用MVP,至少需要經歷以下步驟:
1、創建 IPresenter 接口,把所有業務邏輯的接口都放在這里,並創建它的實現 PresenterCompl(在這里可以方便地查看業務功能,由於接口可以有多種實現所以也方便寫單元測試)。
2、創建 IView 接口,把所有視圖邏輯的接口都放在這里,其實現類是當前的Activity/Fragment。
3、由UML圖可以看出,Activity里包含了一個IPresenter,而PresenterCompl里又包含了一個IView並且依賴了Model。Activity里只保留對IPresenter的調用,其它工作全部留到PresenterCompl中實現。
4、Model並不是必須有的,但是一定會有View和Presenter。
通過上面的介紹,MVP的主要特點就是把Activity里的許多邏輯都抽離到View和Presenter接口中去,並由具體的實現類來完成。這種寫法多了許多IView和IPresenter的接口,
在某種程度上加大了開發的工作量,剛開始使用MVP的小伙伴可能會覺得這種寫法比較別扭,而且難以記住。其實一開始想太多也沒有什么卵用,只要在具體項目中多寫幾次,
就能熟悉MVP模式的寫法,理解意圖,以及享受其帶來的好處。
MVP的使用
理論說了這么多其實沒有什么卵用,下面就來在代碼中看看如何使用吧。
先來看看項目中代碼結構,結構圖如下
通過代碼結構圖可以看到看出MVP結構層級分明(在項目中使用MVP最好通過模塊進行分層這樣便於管理且結構清晰),
先來看看View層代碼
ILoginView接口代碼如下
1 public interface ILoginView { 2 public void onClearText(); 3 public void onLoginResult(Boolean result, int code); 4 }
可以看出ILoginView只是一個接口,簡單的定義了兩個方法。
看看ILoginView的實現類也就是我們的LoginActivity
LoginActivity代碼如下
1 public class LoginActivity extends AppCompatActivity implements ILoginView,View.OnClickListener{ 2 3 private Button mLogin ; 4 private Button mClear ; 5 private EditText mName ; 6 private EditText mPassWord ; 7 8 ILoginPresenter loginPresenter ; 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_main); 14 15 mLogin = (Button) findViewById(R.id.btn_login); 16 mClear = (Button) findViewById(R.id.btn_clear); 17 mName = (EditText) findViewById(R.id.et_name); 18 mPassWord = (EditText) findViewById(R.id.et_password); 19 20 mLogin.setOnClickListener(this); 21 mClear.setOnClickListener(this); 22 23 loginPresenter = new LoginPresenterCompl(this) ; 24 25 } 26 27 @Override 28 public void onClearText() { 29 mName.setText(""); 30 mPassWord.setText(""); 31 Toast.makeText(this,"clear",Toast.LENGTH_SHORT).show(); 32 } 33 34 @Override 35 public void onLoginResult(Boolean result, int code) { 36 mLogin.setEnabled(true); 37 mClear.setEnabled(true); 38 39 if(result){ 40 Toast.makeText(this,"登陸成功: "+code,Toast.LENGTH_SHORT).show(); 41 }else{ 42 Toast.makeText(this,"登陸失敗:"+code,Toast.LENGTH_SHORT).show(); 43 } 44 } 45 46 @Override 47 public void onClick(View v) { 48 int id = v.getId() ; 49 String name = mName.getText().toString() ; 50 String password = mPassWord.getText().toString() ; 51 52 switch (id){ 53 case R.id.btn_login : 54 loginPresenter.doLogin(name,password); 55 break ; 56 case R.id.btn_clear : 57 loginPresenter.clear(); 58 break; 59 } 60 } 61 }
布局文件如下
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".view.LoginActivity"> 11 12 <EditText 13 android:layout_width="wrap_content" 14 android:layout_height="wrap_content" 15 android:inputType="textPersonName" 16 android:hint="name" 17 android:ems="10" 18 android:id="@+id/et_name" 19 android:layout_alignParentTop="true" 20 android:layout_alignParentStart="true" 21 android:layout_marginTop="94dp" /> 22 23 <EditText 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:inputType="textPassword" 27 android:hint="password" 28 android:ems="10" 29 android:id="@+id/et_password" 30 android:layout_below="@+id/et_name" 31 android:layout_alignParentStart="true" /> 32 33 <Button 34 android:layout_width="wrap_content" 35 android:layout_height="wrap_content" 36 android:text="LOGIN" 37 android:id="@+id/btn_login" 38 android:layout_centerVertical="true" 39 android:layout_alignParentStart="true" /> 40 41 <Button 42 android:layout_width="wrap_content" 43 android:layout_height="wrap_content" 44 android:text="CLEAR" 45 android:id="@+id/btn_clear" 46 android:layout_alignBottom="@+id/btn_login" 47 android:layout_alignParentEnd="true" /> 48 </RelativeLayout>
在LoginActivity中我們可以看到,LoginActivity實現了ILoginView接口,實現了未實現的方法,在代碼中可以看出LoginActivity並沒有做一些邏輯處理工作,數據處理的工作都是調用ILoginPresenter
完成的。下面就來看看ILoginPresenter
1 public interface ILoginPresenter { 2 public void clear() ; 3 public void doLogin(String name, String password) ; 4 }
也是簡單的接口定義兩個未實現的方法。
看看其實現類LoginPresenterCompl
1 public class LoginPresenterCompl implements ILoginPresenter { 2 3 private ILoginView loginView ; 4 private User user ; 5 6 @Inject 7 public LoginPresenterCompl(ILoginView view){ 8 loginView = view ; 9 user = new User("張三","123456") ; 10 } 11 12 @Override 13 public void clear() { 14 loginView.onClearText(); 15 } 16 17 @Override 18 public void doLogin(String name, String password) { 19 boolean result = false ; 20 int code = 0 ; 21 if(name.equals(user.getName()) && password.equals(user.getPassword())){ 22 result = true ; 23 code = 1 ; 24 }else{ 25 result = false ; 26 code = 0 ; 27 } 28 29 loginView.onLoginResult(result,code); 30 } 31 }
該實現類也比較簡單,定義了用戶名是張三,密碼是123456的一個登陸用戶,然后進行登陸和清除的操作。
OK這就完成了最簡單的MVP模式了。
看看model層的代碼
User的代碼如下
1 public class User { 2 3 private String name ; 4 private String password ; 5 6 public User(String name,String password){ 7 this.name = name ; 8 this.password = password ; 9 } 10 11 public String getName() { 12 return name; 13 } 14 15 public void setName(String name) { 16 this.name = name; 17 } 18 19 public String getPassword() { 20 return password; 21 } 22 23 public void setPassword(String password) { 24 this.password = password; 25 } 26 }
看完上述MVP的代碼后可能有的同學會說一個簡單的登陸就是簡單的驗證用戶名和密碼為什么要設計的這么復雜,可能有人會覺得還沒有以前的寫法簡單呢。但是這只是一個簡單的實現MVP的例子。
當項目越來越大,代碼越來越多的時候就能夠真正的體現出MVP模式的優勢了。
知道了什么是MVP也知道了如何在項目中使用MVP,那MVP的優勢到底有哪些呢?
MVP的優勢
1、使Activity中的代碼更加簡潔
在傳統的項目中Activity兼顧着Controller和View,這使得其代碼分分鍾上千行(本人深受其害),這使得代碼難以理解難以維護,看到這樣的Activity就想吐。
使用MVP之后,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,
而且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,以后要調整業務、刪減功能也就變得簡單許多。
2、方便進行單元測試
一般單元測試都是用來測試某些新加的業務邏輯有沒有問題,如果采用傳統的代碼風格,我們可能要先在Activity里寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,
這時如果發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧重新寫吧……
MVP中,由於業務邏輯都在Presenter里,我們完全可以寫一個PresenterTest的實現類繼承Presenter的接口,現在只要在Activity里把 Presenter的創建換成PresenterTest,
就能進行單元測試了,測試完再換回來即可。萬一發現還得進行測試,那就再換成PresenterTest吧。
3、避免Activity的內存泄露
APP發生OOM的最大原因就是出現內存泄露造成APP的內存不夠用,而造成內存泄露的兩大原因之一就是Activity泄露(Activity Leak)(另一個原因是Bitmap泄露(Bitmap Leak))。
Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像 C++ 用戶那樣考慮對象的回收問題。然而,Java用戶總是喜歡隨便寫一大堆對象,
然后幻想着虛擬機能幫他們處理好內存的回收工作。可是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用着的對象因為還可能會被調用,所以不能回收。
Activity 是有生命周期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於后台的Activity的資源以避免OOM。
采用傳統的模式,一大堆異步任務和對UI的操作都放在Activity里面,比如你可能從網絡下載一張圖片,在下載成功的回調里把圖片加載到Activity的ImageView里面,
所以異步任務保留着對Activity的引用。這樣一來,即使Activity已經被切換到后台(onDestroy 已經執行),這些異步任務仍然保留着對Activity實例的引用,
所以系統就無法回收這個Activity實例了,結果就是Activity Leak。Android 的組件中,Activity 對象往往是在堆(Java Heap)里占最多內存的,所以系統會優先回收Activity對象,
如果有Activity Leak,APP很容易因為內存不夠而 OOM。
采用MVP模式,只要在當前的Activity的onDestroy里,分離異步任務對Activity的引用,就能避免Activity Leak。
總結
以上就是MVP的簡單實現,可能示例代碼太簡單無法體現MVP的優勢,但是理解了MVP的思路在項目中使用MVP就才能夠真正體驗到MVP帶來的好處優勢。
01:59:11
