如何更高效的使用MVP以及官方MVP架構解析

轉載請標明出處: 
http://blog.csdn.net/dantestones/article/details/51445208

Android mvp 架構的自述中我簡單的介紹了mvp,以及怎麼寫mvp。我自己也將mvp運用到了項目中,其實mvp並沒有固定的寫法,正確的去理解架構的思想,都可以有自己獨特的mvp寫法。git上也有很多例子,比如google的android-architecture,simple哥的Android 源碼設計模式解析與實戰中也有mvp的討論。這裏參考了simple哥做了一個通用版的mvp,並對google的MVP做了一點自己的解析。

關於presenter一直持有Activity對象導致的內存泄漏問題

只要用過mvp這個問題可能很多人都知道。寫mvp的時候,presenter會持有view,如果presenter有後臺異步的長時間的動作,比如網絡請求,這時如果返回退出了Activity,後臺異步的動作不會立即停止,這裏就會有內存泄漏的隱患,所以會在presenter中加入一個銷燬view的方法。現在就在之前的項目中做一下修改

//presenter中添加mvpView 置爲null的方法
public void onDestroy(){    
    mvpView = null;
}

//退出時銷燬持有Activity
@Override
protected void onDestroy() {    
    mvpPresenter.onDestroy();    
    super.onDestroy();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

presenter中增加了類似的生命週期的方法,用來在退出Activity的時候取消持有Activity。

但是在銷燬後需要思考一點,後臺的延時操作返回時,這個時候view被銷燬了,如果接着去調用view的方法就 會拋出空指針異常。所以在後臺的延時操作中需要考慮到這種可能產生空指針的情況,尤其是網絡請求。

BasePresenter

如果每一個Activity都需要做綁定和解綁操作就太麻煩了,現在我希望可以有一個通用的presenter來爲我們添加view的綁定與銷燬。

public abstract class BasePresenter<T> {   

     public T mView;    

     public void attach(T mView) {       
         this.mView = mView;    
     }    

     public void dettach() {        
         mView = null;    
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

因爲不能限定死傳入的View,所以使用泛型來代替傳入的對象。通過這個通用的presenter我就可以把原來的MvpPresenter簡化成下面的樣子

public class NewMvpPresenter extends BasePresenter<NewMvpView> {   

    private RequestBiz requestBiz;    
    private Handler mHandler;    

    public NewMvpPresenter() {        
        requestBiz = new RequestBiziml();       
        mHandler = new Handler(Looper.getMainLooper());    
   }    

    public void onResume(){        
        requestBiz.requestForData(new OnRequestListener() {                        
             @Override            
             public void onSuccess(final List<String> data) {                
                 mHandler.post(new Runnable() {                    
                    @Override                   
                    public void run() {                       
                        mView.hideLoading();                        
                        mView.setListItem(data);                    
                   }               
               });            
            }            

             @Override            
             public void onFailed() {                
                 mView.showMessage("請求失敗");            
             }        
        });    
      }    

    public void onItemClick(int position){        
        mView.showMessage("點擊了item"+position);    
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

BaseView

界面需要提供的UI方法中會有很多類似的UI方法,可以把它們提取到一個公共的父類接口中。比如提取顯示loading界面和隱藏loading界面的方法,其他的view層接口就可以直接繼承BaseView接口,不必重複的寫顯示和隱藏loading界面方法。

public interface BaseView {    
    void showLoading();   
    void hideLoading();
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

BaseMvpActivity

presenter綁定到activity和View的綁定和解綁操作是每個Activity都會去做的,同樣這裏我也希望能有一個父類來完成這個統一的操作。

public abstract class BaseMvpActivity<V,T extends BasePresenter<V>> extends AppCompatActivity {   

    public T presenter;    

     @Override   
     protected void onCreate(Bundle savedInstanceState) {        
         super.onCreate(savedInstanceState);        
         presenter = initPresenter();   
     }    

     @Override    
     protected void onResume() {        
          super.onResume();        
          presenter.attach((V)this);    
     }    

     @Override    
     protected void onDestroy() {       
        presenter.dettach();        
        super.onDestroy();    
     }   

     // 實例化presenter
     public abstract T initPresenter();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

同樣使用泛型來提取通用的邏輯,presenter的初始化,以及view的綁定和解綁操作都提取到父類Activity中。向外部提供了一個 initPresenter(); 方法用來初始化presenter,如果想創建不同參數的構造函數都可以隨意去創建。

更加通用的例子

通過上面的base父類,對之前的例子進行優化,寫一個更加好用的例子。

  • NewMvpView 繼承BaseView接口,添加自己的初始化ListView和Toast信息方法
public interface NewMvpView extends BaseView {    
        void setListItem(List<String> data);    
        void showMessage(String message);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • NewMvpPresenter 繼承BasePresenter類,增加網絡請求和處理點擊事件的方法
public class NewMvpPresenter extends BasePresenter<NewMvpView> {    
        private RequestBiz requestBiz;    
        private Handler mHandler;    

        public NewMvpPresenter() {        
            requestBiz = new RequestBiziml();       
           mHandler = new Handler(Looper.getMainLooper());    
        }    

        public void onResume(){        
            requestBiz.requestForData(new OnRequestListener() {            
                @Override            
                public void onSuccess(final List<String> data) {                
                   mHandler.post(new Runnable() {                    
                      @Override                    
                       public void run() {                        
                           mView.hideLoading();                        
                           mView.setListItem(data);                    
                        }                
                    });            
                 }            

                 @Override            
                 public void onFailed() {                
                     mView.showMessage("請求失敗");            
                 }        
            });    
       }   

        public void onItemClick(int position){        
              mView.showMessage("點擊了item"+position);     
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • NewMvpActivity
public class NewMvpActivity extends BaseMvpActivity<NewMvpView,NewMvpPresenter> implements NewMvpView,AdapterView.OnItemClickListener{    
        ListView mvpListView;    
        ProgressBar pb;    

        @Override    
        protected void onCreate(Bundle savedInstanceState) {        
              super.onCreate(savedInstanceState);        
              setContentView(R.layout.activity_mvp);        
              mvpListView = (ListView)findViewById(R.id.mvp_listview);                 
              mvpListView.setOnItemClickListener(this);       
              pb = (ProgressBar) findViewById(R.id.mvp_loading);    
       }    

       @Override    
        protected void onResume() {        
             super.onResume();        
             presenter.onResume();    
        }    

      @Override    
       public NewMvpPresenter initPresenter() {        
             return new NewMvpPresenter();    
       }    

      @Override    
       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {         
                presenter.onItemClick(position);   
      }    

      @Override    
       public void setListItem(List<String> data) {        
           ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,data);        
           mvpListView.setAdapter(adapter);    
       }    

      @Override    
       public void showMessage(String message) {        
           Toast.makeText(this,message,Toast.LENGTH_SHORT).show();    
       }    

      @Override    
       public void showLoading() {        
           pb.setVisibility(View.VISIBLE);    
       }    

       @Override    
       public void hideLoading() {        
          pb.setVisibility(View.GONE);    
      }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

最終的成果,我們只需要在Acitivity中傳入泛型對象,並寫好initPresenter() Presenter的初始化的方法就可以直接去使用presenter,當然View的接口還是要自己去實現。 
以上的方法只是一些比較簡單的封裝,下面來看看官方的MVP架構是怎麼寫的。

官方的MVP架構

先來看看官方的代碼目錄(這裏只是功能模塊的目錄,不包括測試模塊,畢竟這裏分析的是官方的實現代碼)

這裏寫圖片描述 
谷歌的todomvp工程實現了一個類似記事本的功能,整體的目錄結構:

  • tasks 包可以顯示任務列表
  • taskdetail包顯示任務詳情
  • addedittask包添加和編輯任務
  • statistics包用來顯示任務的完成情況
  • data包數據模塊對應mvp的M
  • Util包就是通用的方法。

這裏todomvp也有BasePresenter和BaseView2個基類,不過看的出來這2個都是接口。先來看看這2個接口

public interface BasePresenter {    
    void start();
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
public interface BaseView<T> {    
    void setPresenter(T presenter);
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

各自簡單的聲明瞭一個方法,start()方法用來給Presenter做一些初始化的操作不用特別在意,BaseView聲明的方法就很有意思了,setPresenter(T presenter) 很明顯是給View綁定Presenter,而前文我們使用的方式給Presenter傳入View的方式來完成View和Presenter綁定的操作,這裏谷歌採用了相反的方式來操作,爲什麼呢?

先把這個問題留下,看看單個模塊的具體的文件結構。

這裏寫圖片描述

有Activity,Fragment,Presenter,View哪裏去了? 其實谷歌的mvp是將Fragment作爲View層來實現的,這一點在官方的Readme中也有說明,爲什麼要用Fragment?

官方認爲Fragement和Activity相比更像是MVP中的的View層,剛好可以滿足MVP的View層的要求,Activity則作爲最高指揮官用來創建和聯繫View和Presenter。

Fragment在平板或者屏幕上有多個View時更有優勢

Activity只作爲創建和聯繫View和PresenterView而存在,將Fragment作爲顯示UI而存在。Activity主指揮,Fragment主顯示。這也是谷歌的sample中的一貫做法。

View的問題解釋完了,再看看TaskDetailContract 這種**Contract 接口,這也是官方獨有的管理方法

Contract

google的todomvp 工程中每個模塊都會有一個 **Contract 接口,來看看他的代碼

public interface TaskDetailContract {    
    interface View extends BaseView<Presenter> {        
        void setLoadingIndicator(boolean active);        
        void showMissingTask();        
        void hideTitle();        
        void showTitle(String title);        
        void hideDescription();        
        void showDescription(String description);        
        void showCompletionStatus(boolean complete);        
        void showEditTask(String taskId);        
        void showTaskDeleted();        
        void showTaskMarkedComplete();        
        void showTaskMarkedActive();    
        boolean isActive();
}     
    interface Presenter extends BasePresenter {        
        void editTask();        
        void deleteTask();        
        void completeTask();        
        void activateTask();    
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Contract其實就是一個包涵了Presenter和View的接口,Presenter實現的邏輯層方法,View實現的UI層的方法都能在Contract接口中一目瞭然的看明白,具體的Presenter和View的實現類都是通過實現Contract接口來完成。這種方式既方便了管理和維護,也給開發點了一個導航燈。

下面來看看Presenter如何引用到Fragment中以及View如何與Presenter建立聯繫

TaskDetailActivity

/**
 * Displays task details screen.
 */
public class TaskDetailActivity extends AppCompatActivity {
    public static final String EXTRA_TASK_ID = "TASK_ID";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.taskdetail_act);

        // Set up the toolbar.
        //.... 設置toolbar 代碼省略
        // Get the requested task id
        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);

        // 實例化taskDetailFragment 
        TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                                                     .findFragmentById(R.id.contentFrame);
        if (taskDetailFragment == null) {
            //taskDetailFragment 添加到Activity
            taskDetailFragment = TaskDetailFragment.newInstance(taskId);
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
            taskDetailFragment, R.id.contentFrame);
        }

        // Create the presenter  初始化Presenter
       new TaskDetailPresenter(taskId,Injection.provideTasksRepository(getApplicationContext()),
                               taskDetailFragment);
}
..... 省去不重要代碼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

TaskDetailActivity主要做了taskDetailFragment 初始化和TaskDetailPresenter的初始化操作,在做TaskDetailPresenter初始化時直接將taskDetailFragment作爲參數傳入,因爲taskDetailFragment實現了View層的接口,看到這裏有一個疑問,new TaskDetailPresenter()可以實例化一個Presenter,但是怎麼傳入到taskDetailFragment中呢?爲了解開疑惑我查看了TaskDetailPresenterde 構造方法

public class TaskDetailPresenter implements TaskDetailContract.Presenter {    
     //....省去不重要的變量聲明

    public TaskDetailPresenter(@Nullable String taskId,                               
                               @NonNull TasksRepository tasksRepository,                               
                               @NonNull TaskDetailContract.View taskDetailView) {       
        this.mTaskId = taskId;        
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");        
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");        
        //實現綁定Presenter的關鍵方法
        mTaskDetailView.setPresenter(this);    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

關鍵方法就是在BaseView中聲明的void setPresenter(T presenter); 方法實現的綁定,當然這裏只是調用View的方法,具體的綁定還要追蹤到實體類中,來看TaskDetailFragment

TaskDetailFragment

public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
     ......省去不重要代碼

    //聲明瞭一個mPresenter
    private TaskDetailContract.Presenter mPresenter;

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View root = inflater.inflate(R.layout.taskdetail_frag, container, false);
         ....省去初始化代碼
         return root;
    }

    @Override
    public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {   
         //完成mPresenter的綁定
         mPresenter = checkNotNull(presenter);
    }

.....
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

TaskDetailFragment 實現了TaskDetailContract接口中的View接口,這樣在TaskDetailPresenter 構造方法中調用mTaskDetailView.setPresenter(this)方法後,TaskDetailFragment 的setPresenter也會調用,TaskDetailPresenter 就成功綁定到了TaskDetailFragment 中。

這種綁定方式也解釋了上文提到的問題,爲什麼谷歌採用了相反的方式操作,就是爲了這後面的綁定操作。

Acitivity對象內存泄漏的問題

谷歌的項目同樣會有當有後臺異步任務時可能導致Acitivity內存泄漏的問題。谷歌也有自己的處理方法,上面提到的TaskDetailContract 接口有聲明一個方法 isActive()

public interface TaskDetailContract { 
    interface View extends BaseView<Presenter> {
        ....省去其他的方法
        boolean isActive();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

看看這方法的具體實現類

public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
     .....
     @Override
     public boolean isActive() {    
          return isAdded();
     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

isAdded()方法如果返回true代表Fragment添加到了Activity,false代表沒有添加,通過調用isActive()方法就可以給後臺異步任務添加判斷避免內存泄漏。

UML結構圖

到這裏整體的架構就已經清楚了配上這張UML圖可以更好的理解。

這裏寫圖片描述

  1. Activity最外層負責Presenter和View的創建和聯繫。
  2. Fragment實現了View的接口並與Presenter綁定
  3. Presenter從數據層中獲取數據與View進行交互

google的todomvp不光給了我們一個mvp的架構演示,還附帶了一個測試模塊,整體的架構非常適合用來開發。強烈建議大家去github上下載源碼來看看,效果更佳

相關代碼 
https://github.com/haibuzou/MVPSample 
https://github.com/googlesamples/android-architecture/tree/todo-mvp/

發佈了11 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章