安卓中的MVP模式



1.  MVP簡介:

隨着UI創建技術的功能日益增強,UI層也履行着越來越多的職責。爲了更好地細分視圖(View)與模型(Model)的功能,讓View專注於處理數據的可視化以及與用戶的交互,同時讓Model只關係數據的處理,基於MVC概念的MVP(Model-View-Presenter)模式應運而生。

MVP模式裏通常包含4個要素:

(1)View:負責繪製UI元素、與用戶進行交互(Android中體現爲Activity);

(2)ViewInterface:需要View實現的接口,View通過View interfacePresenter進行交互,降低耦合,方便進行單元測試;

(3)Model:負責存儲、檢索、操縱數據(有時也實現一個Modelinterface用來降低耦合);

(4)Presenter:作爲ViewModel交互的中間紐帶,處理與用戶交互的負責邏輯。


2.  爲什麼使用MVP模式


Android開發中,Activity並不是一個標準的MVC模式中的Controller的首要職責是加載應用的佈局和初始化用戶界面,並接受並處理來自用戶的操作請求,進而作出響應。隨着界面及其邏輯的複雜度不斷提升,Activity類的職責不斷增加,以致變得龐大臃腫。當我們將其中複雜的邏輯處理移至另外的一個類(Presneter)中時,Activity其實就是MVP模式中 View,它負責UI元素的初始化,建立UI元素與Presenter的關聯(Listener之類),同時自己也會處理一些簡單的邏輯(複雜的邏輯交由 Presenter處理).

 另外,回想一下你在開發Android應用時是如何對代碼邏輯進行單元測試的?是否每次都要將應用部署到Android模擬器或真機上,然後通過模擬用戶操作進行測試?然而由於Android平臺的特性,每次部署都耗費了大量的時間,這直接導致開發效率的降低。而在MVP模式中,處理複雜邏輯的 Presenter是通過interfaceView(Activity)進行交互的,這說明了什麼?說明我們可以通過自定義類實現這個 interface來模擬Activity的行爲對Presenter進行單元測試,省去了大量的部署及測試的時間。


3.  MVP模式實例

好了,大致瞭解了MVP模式的基本概念之後,我們就使用MVP模式來寫一個小例子。

包的結構如下圖所示:                    效果展示:

                       

下面開始講解mvp模式的步驟:

1) 創建view的接口類,根據業務定義抽象方法

[java] view plain copy
  1. <span style="font-size:18px;">public interface IUserView {  
  2.     //顯示進度條  
  3.     void showLoading();  
  4.     //展示用戶數據  
  5.     void showUser(List<User> users);  
  6.       
  7. }</span>  
2) 創建model的接口類,根據業務定義抽象方法

其中定一個加載數據的方法,同時設置一個加載完成的監聽,監聽內設置抽象方法complete,用於加載完成後進行回調

[java] view plain copy
  1. public interface IUserModel {  
  2.     //加載用戶信息的方法  
  3.     void loadUser(UserLoadListenner listener);  
  4.     //加載完成的回調  
  5.     interface UserLoadListenner{  
  6.         void complete(List<User> users);  
  7.     }  
  8. }  

3)創建model的實現類,實現其中抽象方法,其中的user類是在bean包根據需求自行創建的

[java] view plain copy
  1. public class UserModelImpl implements IUserModel{  
  2.   
  3.     @Override  
  4.     public void loadUser(UserLoadListenner listener) {  
  5.         //模擬加載本地數據  
  6.         List<User> users = new ArrayList<User>();  
  7.         users.add(new User("姚明""我很高", R.drawable.ic_launcher));  
  8.         users.add(new User("科比""怒砍81分", R.drawable.ic_launcher));  
  9.         users.add(new User("詹姆斯""我是宇宙第一", R.drawable.ic_launcher));  
  10.         users.add(new User("庫裏""三分我最強", R.drawable.ic_launcher));  
  11.         users.add(new User("杜蘭特""千年老二", R.drawable.ic_launcher));  
  12.         if(listener != null){  
  13.             listener.complete(users);  
  14.         }  
  15.     }  
  16.       
  17. }  

加載完數據,回調listener中的complete方法。

4) 創建present,在構造函數傳入view的實現類,同時在其中new出model的實現類,創建一個方法load,實現view與model間通信的橋樑。

[java] view plain copy
  1. public class Presenter1 {  
  2.     //view  
  3.     IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl();  
  6.     //ͨ通過構造函數傳入view  
  7.     public Presenter1(IUserView mUserView) {  
  8.         super();  
  9.         this.mUserView = mUserView;  
  10.     }  
[java] view plain copy
  1. //加載數據  
  2.     public void load() {  
  3.         //加載進度條  
  4.         mUserView.showLoading();  
  5.         //model進行數據獲取  
  6.         if(mUserModel != null){  
  7.             mUserModel.loadUser(new UserLoadListenner() {  
  8.                   
  9.                 @Override  
  10.                 public void complete(List<User> users) {  
  11.                     // 數據加載完後進行回調,交給view進行展示  
  12.                     mUserView.showUser(users);  
  13.                       
  14.                 }  
  15.             });  
  16.         }  
  17.           
  18.     }  
Load中,先調用mUserView.showLoading() 顯示加載進度,然後是調用mUserModel.loadUser加載數據,其中要實現Listenner的complete方法,其中的邏輯就是用view將數據顯示到界面,model的最後會回調listener中的complete方法,數據就顯示在界面上了。

5) MainActivity顯然是用來顯示數據的,其中有一個listview,創建與其相關的兩個佈局文件activity_main.xml與item_user.xml,令MainActivity實現IUserView接口,並實現兩個抽象方法,創建listview的適配器,重寫構造函數,並利用viewHolder,複用convertView對其進行優化,最後創建Presenter,並調用其load方法,完成加載所有邏輯。

[java] view plain copy
  1. <pre name="code" class="java">public class MainActivity extends ActionBarActivity implements IUserView  {  
  2.   
  3.     private ListView mListView;  
  4.       
  5.       
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         mListView = (ListView) findViewById(R.id.lv);        
  12.         new Presenter1(this).load();  
  13.           
  14.     }  
  15.       
  16.       
  17.     public void showUser(List<User> users) {  
  18.         //顯示所有用戶列表  
  19.         mListView.setAdapter(new UserAdapter(this,users));  
  20.     }  
  21.   
  22.     @Override  
  23.     public void showLoading() {  
  24.           
  25.         Toast.makeText(this"正在拼命加載中", Toast.LENGTH_SHORT).show();  
  26.     }  
  27. }  



適配器:

[java] view plain copy
  1. public class UserAdapter extends BaseAdapter {  
  2.   
  3.     private Context context;  
  4.     private List<User> users;  
  5.   
  6.     public UserAdapter(Context context, List<User> users) {  
  7.         this.context = context;  
  8.         this.users = users;  
  9.     }  
  10.   
  11.     @Override  
  12.     public int getCount() {  
  13.         // TODO Auto-generated method stub  
  14.         return users.size();  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object getItem(int position) {  
  19.         // TODO Auto-generated method stub  
  20.         return users.get(position);  
  21.     }  
  22.   
  23.     @Override  
  24.     public long getItemId(int position) {  
  25.         // TODO Auto-generated method stub  
  26.         return position;  
  27.     }  
  28.   
  29.     @Override  
  30.     public View getView(int position, View convertView, ViewGroup parent) {  
  31.         LayoutInflater inflater = LayoutInflater.from(context);  
  32.         ViewHolder viewHolder = null;  
  33.         //convertView  
  34.         if (convertView == null) {  
  35.             convertView = inflater.inflate(R.layout.item_user, null);  
  36.             viewHolder = new ViewHolder();  
  37.             viewHolder.image = (ImageView) convertView  
  38.                     .findViewById(R.id.iv_user);  
  39.             viewHolder.name = (TextView) convertView.findViewById(R.id.tv_name);  
  40.             viewHolder.content = (TextView) convertView  
  41.                     .findViewById(R.id.tv_content);  
  42.             convertView.setTag(viewHolder);  
  43.         }else{  
  44.             viewHolder = (ViewHolder) convertView.getTag();  
  45.         }  
  46.           
  47.         viewHolder.image.setImageResource(users.get(position).getPicid());  
  48.         viewHolder.name.setText(users.get(position).getName());  
  49.         viewHolder.content.setText(users.get(position).getContent());  
  50.         return convertView;  
  51.     }  
  52.   
  53.     private static class ViewHolder {  
  54.         ImageView image;  
  55.         TextView name;  
  56.         TextView content;  
  57.     }  
  58.   
  59. }  

這樣,我們的小例子就寫完了,效果如下:

體會MVP模式的優越性:

a) 假設我們不從本地獲取用戶數據了,改成從網絡獲取,只需要從新寫一個model的實現類,並new 一個present,並在MainActivity中進行替換,就可以解決,我們模擬一下這種情況,發現修改十分方便,主界面建議使用MVP模式,它很好遵守了開閉原則。

b) 假設我不想用listview顯示數據,想換成gridview,無需修改原來代碼,只需要新建一個新的Activity來實現view,實現接口方法,同時使用gridview與新建一個與其對應的adapter即可,符合了開閉原則,不修改源碼,而是進行擴展性修改。View與model解耦,可以發現我們寫的Activity裏面都是沒有model的影子的,只有presenter.

[java] view plain copy
  1. public class GridActivity extends MvpBaseActivity<IUserView, GridPresenter> implements IUserView{  
  2.      private GridView mGridView;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_grid);  
  8.         mGridView = (GridView) findViewById(R.id.gv);  
  9.         mPresenter.load();  
  10.     }  
  11.   
  12.     @Override  
  13.     public void showLoading() {  
  14.         // TODO Auto-generated method stub  
  15.         Toast.makeText(this"正在拼命加載中", Toast.LENGTH_SHORT).show();  
  16.     }  
  17.   
  18.     @Override  
  19.     public void showUser(List<User> users) {  
  20.         // TODO Auto-generated method stub  
  21.         mGridView.setAdapter(new UserAdapter(this,users));  
  22.     }  
  23.   
  24.     @Override  
  25.     protected GridPresenter createPresenter() {  
  26.         // TODO Auto-generated method stub  
  27.           
  28.         return new GridPresenter();  
  29.     }  
  30.   
  31. }  

[java] view plain copy
  1. public class Presenter2 {  
  2.     //view  
  3.     IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl2();  
  6.     //ͨ通過構造函數傳入view  
  7.     public Presenter2(IUserView mUserView) {  
  8.         super();  
  9.         this.mUserView = mUserView;  
  10.     }  
  11.     //加載數據  
  12.     public void load() {  
  13.         //加載進度條  
  14.         mUserView.showLoading();  
  15.         //model進行數據獲取  
  16.         if(mUserModel != null){  
  17.             mUserModel.loadUser(new UserLoadListenner() {  
  18.                   
  19.                 @Override  
  20.                 public void complete(List<User> users) {  
  21.                     // 數據加載完後進行回調,交給view進行展示  
  22.                     mUserView.showUser(users);  
  23.                       
  24.                 }  
  25.             });  
  26.         }  
  27.           
  28.     }  
  29.       
  30. }  


4)MVP中的內存泄露問題

發現我們之前寫的兩個Acitivty有共性的地方,就是都new 了present,我們對代碼進行抽取,提高代碼的複用性。

在各個Activitty中Presenter有很多類型,所以在BaseActivitty中,也需要對Presenter進行抽取成BasePresenter,MVP中Presenter是持有view的引用的,所以BasePresenter中使用泛型

[java] view plain copy
  1. public abstract class BasePresenter<T> {  
  2.       
  3. }  

在BaseActivitty中,Presenter的具體類型交給子類去確定,我們只提供一個生成Presenter的方法,這裏多次用到了泛型,需要注意

[java] view plain copy
  1. public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> extends ActionBarActivity {  
  2.     protected T mPresenter;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         //創建presenter  
  8.         mPresenter = createPresenter();  
  9.         //內存泄露  
  10.         //關聯View  
  11.         mPresenter.attachView((V) this);  
  12.     }  
  13.   
  14.     protected abstract  T createPresenter();  
  15.       
  16. }  

內存泄露分析:加入Model在請求網絡加載數據,此時假設Activity由於內存不足,被GC回收,但是網絡加載還未完成,則Presenter還存在,並持有Activity的引用,當網絡加載數據完成,Presenter會使用Activity進行數據展現,而此時Activity已被回收,就發生了內存泄露,會報錯,所以解決方法是:當view被回收,Presenter要解除與其的關聯。

既然是Presenter解除與view的關聯,那關聯與解除的邏輯肯定是在Presenter中,使用弱引用包裹view,理由是,使用弱引用,當GC掃描到的時候,就會立即回收。所以對BasePresenter進行如下的修改:

[java] view plain copy
  1. public abstract class BasePresenter<T> {  
  2.     //當內存不足,釋放內存  
  3.     protected WeakReference<T> mViewReference;  

創建關聯和解除關聯的方法:

進行關聯的邏輯:創建弱引用,幷包裹view

解除關聯的邏輯:判斷,如果弱引用不爲空,清空弱引用,並設置爲空,徹底釋放

[java] view plain copy
  1. //進行關聯  
  2.     public void attachView(T view) {  
  3.         mViewReference = new WeakReference<T>(view);  
  4.     }  
  5.     //解除關聯  
  6.     public void detachView() {  
  7.         if(mViewReference != null){  
  8.             mViewReference.clear();  
  9.             mViewReference = null;  
  10.         }  
  11.     }  

暴露一個方法,用於其他類從弱引用中取出view

[java] view plain copy
  1. protected T getView() {  
  2.           
  3.         return mViewReference.get();  
  4.           
  5.     }  
GridPresenter繼承BasePresenter,進行對象抽象方法的實現

[java] view plain copy
  1. public class GridPresenter extends BasePresenter<IUserView>{  
  2.     //view  
  3.     //IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl();  
  6.   
  7.     /*public GridPresenter(IUserView mUserView) { 
  8.         super(); 
  9.         this.mUserView = mUserView; 
  10.     }*/  
  11.     //加載數據  
  12.     public void load() {  
  13.         //加載進度條  
  14.         //mUserView.showLoading();  
  15.         getView().showLoading();  
  16.         //model進行數據獲取  
  17.         if(mUserModel != null){  
  18.             mUserModel.loadUser(new UserLoadListenner() {  
  19.                   
  20.                 @Override  
  21.                 public void complete(List<User> users) {  
  22.                     // 數據加載完後進行回調,交給view進行展示  
  23.                     //mUserView.showUser(users);  
  24.                     getView().showUser(users);  
  25.                       
  26.                 }  
  27.             });  
  28.         }  
  29.           
  30.     }  
  31.       
  32. }  

然後對BaseActivity進行修改:

[cpp] view plain copy
  1. public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> extends ActionBarActivity {  
  2.     protected T mPresenter;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         //創建presenter  
  8.         mPresenter = createPresenter();  
  9.         //內存泄露  
  10.         //關聯View  
  11.         mPresenter.attachView((V) this);  
  12.     }  
  13.   
  14.     protected abstract  T createPresenter();  
  15.     @Override  
  16.     protected void onDestroy() {  
  17.         // TODO Auto-generated method stub  
  18.         super.onDestroy();  
  19.         mPresenter.detachView();  
  20.     }  
  21. }  

oncreate方法中關聯view,onDestroy方法中對關聯進行清除,所有關於內存泄露的邏輯就完成了,好了,對MVP模式的分析到此就結束了,更多的應用得大家自己在項目中對該模式進行運用,並不斷進行總結。

1.  MVP簡介:

隨着UI創建技術的功能日益增強,UI層也履行着越來越多的職責。爲了更好地細分視圖(View)與模型(Model)的功能,讓View專注於處理數據的可視化以及與用戶的交互,同時讓Model只關係數據的處理,基於MVC概念的MVP(Model-View-Presenter)模式應運而生。

MVP模式裏通常包含4個要素:

(1)View:負責繪製UI元素、與用戶進行交互(Android中體現爲Activity);

(2)ViewInterface:需要View實現的接口,View通過View interfacePresenter進行交互,降低耦合,方便進行單元測試;

(3)Model:負責存儲、檢索、操縱數據(有時也實現一個Modelinterface用來降低耦合);

(4)Presenter:作爲ViewModel交互的中間紐帶,處理與用戶交互的負責邏輯。

2.  爲什麼使用MVP模式

Android開發中,Activity並不是一個標準的MVC模式中的Controller的首要職責是加載應用的佈局和初始化用戶界面,並接受並處理來自用戶的操作請求,進而作出響應。隨着界面及其邏輯的複雜度不斷提升,Activity類的職責不斷增加,以致變得龐大臃腫。當我們將其中複雜的邏輯處理移至另外的一個類(Presneter)中時,Activity其實就是MVP模式中 View,它負責UI元素的初始化,建立UI元素與Presenter的關聯(Listener之類),同時自己也會處理一些簡單的邏輯(複雜的邏輯交由 Presenter處理).

 另外,回想一下你在開發Android應用時是如何對代碼邏輯進行單元測試的?是否每次都要將應用部署到Android模擬器或真機上,然後通過模擬用戶操作進行測試?然而由於Android平臺的特性,每次部署都耗費了大量的時間,這直接導致開發效率的降低。而在MVP模式中,處理複雜邏輯的 Presenter是通過interfaceView(Activity)進行交互的,這說明了什麼?說明我們可以通過自定義類實現這個 interface來模擬Activity的行爲對Presenter進行單元測試,省去了大量的部署及測試的時間。

3.  MVP模式實例

好了,大致瞭解了MVP模式的基本概念之後,我們就使用MVP模式來寫一個小例子。

包的結構如下圖所示:                    效果展示:

                       

下面開始講解mvp模式的步驟:

1) 創建view的接口類,根據業務定義抽象方法

[java] view plain copy
  1. <span style="font-size:18px;">public interface IUserView {  
  2.     //顯示進度條  
  3.     void showLoading();  
  4.     //展示用戶數據  
  5.     void showUser(List<User> users);  
  6.       
  7. }</span>  
2) 創建model的接口類,根據業務定義抽象方法

其中定一個加載數據的方法,同時設置一個加載完成的監聽,監聽內設置抽象方法complete,用於加載完成後進行回調

[java] view plain copy
  1. public interface IUserModel {  
  2.     //加載用戶信息的方法  
  3.     void loadUser(UserLoadListenner listener);  
  4.     //加載完成的回調  
  5.     interface UserLoadListenner{  
  6.         void complete(List<User> users);  
  7.     }  
  8. }  

3)創建model的實現類,實現其中抽象方法,其中的user類是在bean包根據需求自行創建的

[java] view plain copy
  1. public class UserModelImpl implements IUserModel{  
  2.   
  3.     @Override  
  4.     public void loadUser(UserLoadListenner listener) {  
  5.         //模擬加載本地數據  
  6.         List<User> users = new ArrayList<User>();  
  7.         users.add(new User("姚明""我很高", R.drawable.ic_launcher));  
  8.         users.add(new User("科比""怒砍81分", R.drawable.ic_launcher));  
  9.         users.add(new User("詹姆斯""我是宇宙第一", R.drawable.ic_launcher));  
  10.         users.add(new User("庫裏""三分我最強", R.drawable.ic_launcher));  
  11.         users.add(new User("杜蘭特""千年老二", R.drawable.ic_launcher));  
  12.         if(listener != null){  
  13.             listener.complete(users);  
  14.         }  
  15.     }  
  16.       
  17. }  

加載完數據,回調listener中的complete方法。

4) 創建present,在構造函數傳入view的實現類,同時在其中new出model的實現類,創建一個方法load,實現view與model間通信的橋樑。

[java] view plain copy
  1. public class Presenter1 {  
  2.     //view  
  3.     IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl();  
  6.     //ͨ通過構造函數傳入view  
  7.     public Presenter1(IUserView mUserView) {  
  8.         super();  
  9.         this.mUserView = mUserView;  
  10.     }  
[java] view plain copy
  1. //加載數據  
  2.     public void load() {  
  3.         //加載進度條  
  4.         mUserView.showLoading();  
  5.         //model進行數據獲取  
  6.         if(mUserModel != null){  
  7.             mUserModel.loadUser(new UserLoadListenner() {  
  8.                   
  9.                 @Override  
  10.                 public void complete(List<User> users) {  
  11.                     // 數據加載完後進行回調,交給view進行展示  
  12.                     mUserView.showUser(users);  
  13.                       
  14.                 }  
  15.             });  
  16.         }  
  17.           
  18.     }  
Load中,先調用mUserView.showLoading() 顯示加載進度,然後是調用mUserModel.loadUser加載數據,其中要實現Listenner的complete方法,其中的邏輯就是用view將數據顯示到界面,model的最後會回調listener中的complete方法,數據就顯示在界面上了。

5) MainActivity顯然是用來顯示數據的,其中有一個listview,創建與其相關的兩個佈局文件activity_main.xml與item_user.xml,令MainActivity實現IUserView接口,並實現兩個抽象方法,創建listview的適配器,重寫構造函數,並利用viewHolder,複用convertView對其進行優化,最後創建Presenter,並調用其load方法,完成加載所有邏輯。

[java] view plain copy
  1. <pre name="code" class="java">public class MainActivity extends ActionBarActivity implements IUserView  {  
  2.   
  3.     private ListView mListView;  
  4.       
  5.       
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         mListView = (ListView) findViewById(R.id.lv);        
  12.         new Presenter1(this).load();  
  13.           
  14.     }  
  15.       
  16.       
  17.     public void showUser(List<User> users) {  
  18.         //顯示所有用戶列表  
  19.         mListView.setAdapter(new UserAdapter(this,users));  
  20.     }  
  21.   
  22.     @Override  
  23.     public void showLoading() {  
  24.           
  25.         Toast.makeText(this"正在拼命加載中", Toast.LENGTH_SHORT).show();  
  26.     }  
  27. }  



適配器:

[java] view plain copy
  1. public class UserAdapter extends BaseAdapter {  
  2.   
  3.     private Context context;  
  4.     private List<User> users;  
  5.   
  6.     public UserAdapter(Context context, List<User> users) {  
  7.         this.context = context;  
  8.         this.users = users;  
  9.     }  
  10.   
  11.     @Override  
  12.     public int getCount() {  
  13.         // TODO Auto-generated method stub  
  14.         return users.size();  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object getItem(int position) {  
  19.         // TODO Auto-generated method stub  
  20.         return users.get(position);  
  21.     }  
  22.   
  23.     @Override  
  24.     public long getItemId(int position) {  
  25.         // TODO Auto-generated method stub  
  26.         return position;  
  27.     }  
  28.   
  29.     @Override  
  30.     public View getView(int position, View convertView, ViewGroup parent) {  
  31.         LayoutInflater inflater = LayoutInflater.from(context);  
  32.         ViewHolder viewHolder = null;  
  33.         //convertView  
  34.         if (convertView == null) {  
  35.             convertView = inflater.inflate(R.layout.item_user, null);  
  36.             viewHolder = new ViewHolder();  
  37.             viewHolder.image = (ImageView) convertView  
  38.                     .findViewById(R.id.iv_user);  
  39.             viewHolder.name = (TextView) convertView.findViewById(R.id.tv_name);  
  40.             viewHolder.content = (TextView) convertView  
  41.                     .findViewById(R.id.tv_content);  
  42.             convertView.setTag(viewHolder);  
  43.         }else{  
  44.             viewHolder = (ViewHolder) convertView.getTag();  
  45.         }  
  46.           
  47.         viewHolder.image.setImageResource(users.get(position).getPicid());  
  48.         viewHolder.name.setText(users.get(position).getName());  
  49.         viewHolder.content.setText(users.get(position).getContent());  
  50.         return convertView;  
  51.     }  
  52.   
  53.     private static class ViewHolder {  
  54.         ImageView image;  
  55.         TextView name;  
  56.         TextView content;  
  57.     }  
  58.   
  59. }  

這樣,我們的小例子就寫完了,效果如下:

體會MVP模式的優越性:

a) 假設我們不從本地獲取用戶數據了,改成從網絡獲取,只需要從新寫一個model的實現類,並new 一個present,並在MainActivity中進行替換,就可以解決,我們模擬一下這種情況,發現修改十分方便,主界面建議使用MVP模式,它很好遵守了開閉原則。

b) 假設我不想用listview顯示數據,想換成gridview,無需修改原來代碼,只需要新建一個新的Activity來實現view,實現接口方法,同時使用gridview與新建一個與其對應的adapter即可,符合了開閉原則,不修改源碼,而是進行擴展性修改。View與model解耦,可以發現我們寫的Activity裏面都是沒有model的影子的,只有presenter.

[java] view plain copy
  1. public class GridActivity extends MvpBaseActivity<IUserView, GridPresenter> implements IUserView{  
  2.      private GridView mGridView;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_grid);  
  8.         mGridView = (GridView) findViewById(R.id.gv);  
  9.         mPresenter.load();  
  10.     }  
  11.   
  12.     @Override  
  13.     public void showLoading() {  
  14.         // TODO Auto-generated method stub  
  15.         Toast.makeText(this"正在拼命加載中", Toast.LENGTH_SHORT).show();  
  16.     }  
  17.   
  18.     @Override  
  19.     public void showUser(List<User> users) {  
  20.         // TODO Auto-generated method stub  
  21.         mGridView.setAdapter(new UserAdapter(this,users));  
  22.     }  
  23.   
  24.     @Override  
  25.     protected GridPresenter createPresenter() {  
  26.         // TODO Auto-generated method stub  
  27.           
  28.         return new GridPresenter();  
  29.     }  
  30.   
  31. }  

[java] view plain copy
  1. public class Presenter2 {  
  2.     //view  
  3.     IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl2();  
  6.     //ͨ通過構造函數傳入view  
  7.     public Presenter2(IUserView mUserView) {  
  8.         super();  
  9.         this.mUserView = mUserView;  
  10.     }  
  11.     //加載數據  
  12.     public void load() {  
  13.         //加載進度條  
  14.         mUserView.showLoading();  
  15.         //model進行數據獲取  
  16.         if(mUserModel != null){  
  17.             mUserModel.loadUser(new UserLoadListenner() {  
  18.                   
  19.                 @Override  
  20.                 public void complete(List<User> users) {  
  21.                     // 數據加載完後進行回調,交給view進行展示  
  22.                     mUserView.showUser(users);  
  23.                       
  24.                 }  
  25.             });  
  26.         }  
  27.           
  28.     }  
  29.       
  30. }  


4)MVP中的內存泄露問題

發現我們之前寫的兩個Acitivty有共性的地方,就是都new 了present,我們對代碼進行抽取,提高代碼的複用性。

在各個Activitty中Presenter有很多類型,所以在BaseActivitty中,也需要對Presenter進行抽取成BasePresenter,MVP中Presenter是持有view的引用的,所以BasePresenter中使用泛型

[java] view plain copy
  1. public abstract class BasePresenter<T> {  
  2.       
  3. }  

在BaseActivitty中,Presenter的具體類型交給子類去確定,我們只提供一個生成Presenter的方法,這裏多次用到了泛型,需要注意

[java] view plain copy
  1. public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> extends ActionBarActivity {  
  2.     protected T mPresenter;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         //創建presenter  
  8.         mPresenter = createPresenter();  
  9.         //內存泄露  
  10.         //關聯View  
  11.         mPresenter.attachView((V) this);  
  12.     }  
  13.   
  14.     protected abstract  T createPresenter();  
  15.       
  16. }  

內存泄露分析:加入Model在請求網絡加載數據,此時假設Activity由於內存不足,被GC回收,但是網絡加載還未完成,則Presenter還存在,並持有Activity的引用,當網絡加載數據完成,Presenter會使用Activity進行數據展現,而此時Activity已被回收,就發生了內存泄露,會報錯,所以解決方法是:當view被回收,Presenter要解除與其的關聯。

既然是Presenter解除與view的關聯,那關聯與解除的邏輯肯定是在Presenter中,使用弱引用包裹view,理由是,使用弱引用,當GC掃描到的時候,就會立即回收。所以對BasePresenter進行如下的修改:

[java] view plain copy
  1. public abstract class BasePresenter<T> {  
  2.     //當內存不足,釋放內存  
  3.     protected WeakReference<T> mViewReference;  

創建關聯和解除關聯的方法:

進行關聯的邏輯:創建弱引用,幷包裹view

解除關聯的邏輯:判斷,如果弱引用不爲空,清空弱引用,並設置爲空,徹底釋放

[java] view plain copy
  1. //進行關聯  
  2.     public void attachView(T view) {  
  3.         mViewReference = new WeakReference<T>(view);  
  4.     }  
  5.     //解除關聯  
  6.     public void detachView() {  
  7.         if(mViewReference != null){  
  8.             mViewReference.clear();  
  9.             mViewReference = null;  
  10.         }  
  11.     }  

暴露一個方法,用於其他類從弱引用中取出view

[java] view plain copy
  1. protected T getView() {  
  2.           
  3.         return mViewReference.get();  
  4.           
  5.     }  
GridPresenter繼承BasePresenter,進行對象抽象方法的實現

[java] view plain copy
  1. public class GridPresenter extends BasePresenter<IUserView>{  
  2.     //view  
  3.     //IUserView mUserView;  
  4.     //model  
  5.     IUserModel mUserModel = new UserModelImpl();  
  6.   
  7.     /*public GridPresenter(IUserView mUserView) { 
  8.         super(); 
  9.         this.mUserView = mUserView; 
  10.     }*/  
  11.     //加載數據  
  12.     public void load() {  
  13.         //加載進度條  
  14.         //mUserView.showLoading();  
  15.         getView().showLoading();  
  16.         //model進行數據獲取  
  17.         if(mUserModel != null){  
  18.             mUserModel.loadUser(new UserLoadListenner() {  
  19.                   
  20.                 @Override  
  21.                 public void complete(List<User> users) {  
  22.                     // 數據加載完後進行回調,交給view進行展示  
  23.                     //mUserView.showUser(users);  
  24.                     getView().showUser(users);  
  25.                       
  26.                 }  
  27.             });  
  28.         }  
  29.           
  30.     }  
  31.       
  32. }  

然後對BaseActivity進行修改:

[cpp] view plain copy
  1. public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> extends ActionBarActivity {  
  2.     protected T mPresenter;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.         //創建presenter  
  8.         mPresenter = createPresenter();  
  9.         //內存泄露  
  10.         //關聯View  
  11.         mPresenter.attachView((V) this);  
  12.     }  
  13.   
  14.     protected abstract  T createPresenter();  
  15.     @Override  
  16.     protected void onDestroy() {  
  17.         // TODO Auto-generated method stub  
  18.         super.onDestroy();  
  19.         mPresenter.detachView();  
  20.     }  
  21. }  

oncreate方法中關聯view,onDestroy方法中對關聯進行清除,所有關於內存泄露的邏輯就完成了,好了,對MVP模式的分析到此就結束了,更多的應用得大家自己在項目中對該模式進行運用,並不斷進行總結。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章