1. 程式人生 > >Android MVP Pattern 學習總結

Android MVP Pattern 學習總結

導讀:MVP模式是MVC模式在Android上的一種變體,要介紹MVP就得先介紹MVC。在MVC模式中,Activity應該是屬於View這一層。而實質上,它既承擔了View,同時也包含一些Controller的東西在裡面。這對於開發與維護來說不太友好,耦合度大高了。把Activity的View和Controller抽離出來就變成了View和Presenter,這就是MVP模式。

MVC模式
MVC模式的結構分為三部分,實體層的Model,檢視層的View,以及控制層的Controller。
mvc模式

• 其中View層其實就是程式的UI介面,用於向用戶展示資料以及接收使用者的輸入
• 而Model層就是JavaBean實體類,用於儲存例項資料
• Controller控制器用於更新UI介面和資料例項

例如,View層接受使用者的輸入,然後通過Controller修改對應的Model例項;同時,當Model例項的資料發生變化的時候,需要修改UI介面,可以通過Controller更新介面。(View層也可以直接更新Model例項的資料,而不用每次都通過Controller,這樣對於一些簡單的資料更新工作會變得方便許多。)

MVP模式

按照MVC的分層,Activity和Fragment(後面只說Activity)應該屬於View層,用於展示UI介面,以及接收使用者的輸入,此外還要承擔一些生命週期的工作。Activity是在Android開發中充當非常重要的角色,特別是它的生命週期的功能,所以開發的時候我們經常把一些業務邏輯直接寫在Activity裡面,這非常直觀方便,代價就是Activity會越來越臃腫。因此,Activity不僅承擔了View的角色,還承擔了一部分的Controller角色,這樣一來V和C就耦合在一起了,雖然這樣寫方便,但是如果業務調整的話,要維護起來就難了,而且在一個臃腫的Activity類查詢業務邏輯的程式碼也會非常麻煩,所以看起來有必要在Activity中,把View和Controller抽離開來,而這就是MVP模式的工作了。
mvp模式

MVP模式的核心思想:

MVP把Activity中的UI邏輯抽象成View介面,把業務邏輯抽象成Presenter介面,Model類還是原來的Model。
這就是MVP模式,現在這樣的話,Activity的工作的簡單了,只用來響應生命週期,其他工作都丟到Presenter中去完成。從上圖可以看出,Presenter是Model和View之間的橋樑,為了讓結構變得更加簡單,View並不能直接對Model進行操作,這也是MVP與MVC最大的不同之處。

MVP模式的作用

• 分離了檢視邏輯和業務邏輯,降低了耦合 • Activity只處理生命週期的任務,程式碼變得更加簡潔
• 檢視邏輯和業務邏輯分別抽象到了View和Presenter的介面中去,提高程式碼的可閱讀性
• Presenter被抽象成介面,可以有多種具體的實現,所以方便進行單元測試。需要的時候可以隨意切換presenter改變實
現方式。當presenter程式碼過於龐大時,還可以整理出多個presenter 互相呼叫。
• 把業務邏輯抽到Presenter中去,避免後臺執行緒引用著Activity導致Activity的資源無法被系統回收從而引起記憶體洩露和OOM

MVP模式程式碼例項結構分析
工程目錄

工程結構
從上面圖可以看到,MVP package下包含domian,model,presenter,ui 三個package.
Domian下放各個模組的Contract 功能實現的函式宣告。方便呼叫檢視。
Model 下放各個模組的Model 層。Model層的主要功能為 獲取資料。
Presenter 下放各個模組的 Presenter層。Presenter 層的主要功能 為 使用者的一些邏輯操作。
Ui 下放各個模組的Ui層。Ui層的主要功能為使用者的主activity。主執行緒使用者介面顯示。

這裡以Music 功能模組為例分析以上三個結構模組之間的呼叫關係。

MusicActivity 

public class MusicActivity extends BaseActivity implements SpeechInputContract.IMusicView {

    @BindView(R.id.recyclerview)
    RecyclerView recyclerview;

    View headView1;
    TextView tvSongName;
    TextView tvSingerAndAlbum;
    ImageView imgCover;
    ImageView btnPlay;

   /* TextView btnLyricsControl;
    TextView tvLyrics;*/

    private PlayListAdapter playListAdapter;

    MusicPlayService musicPlayService;
private void initMusic(final MusicPlayBean musicPlayBean){…..}



@Override
protected int setRootViewId() {
    return R.layout.activity_music;
}

@Override
protected void initData(){
…資料的初始化..
    playListAdapter = new PlayListAdapter();
    initView();
…
}

private void initView(){
…
    tvSongName = (TextView) headView1.findViewById(R.id.tv_song_name);

    btnPlay.setOnClickListener
…

}
@Override
public void getMusicDataSuccess(List<MusicPlayBean> musicPlayBeanList) {
    // playListAdapter.addData(musicPlayBeanList);
}

@Override
public void controlMusic(String playState, int index) {
    if (null != musicPlayService) {
        musicPlayService.changePlayState(playState);
    }
}

@Override
protected void onResume() {…}

@Override
protected void onPause(){…}

@Override
protected void onDestroy() {
    super.onDestroy();
    unbindService(conn);
}

SpeechInputContract
在musicAcitivity 類繼承SpeechInputContract.IMusicView

interface IMusicView extends IBaseView {

    /**
     * 獲取音樂源成功
     *
     * @param musicPlayBeanList 獲取到的音樂源
     */
    void getMusicDataSuccess(List<MusicPlayBean> musicPlayBeanList);

    /**
     * 音樂控制
     *
     * @param playState 不用列舉是因為 1、離線識別返回就是STRING 方便 2、列舉消耗記憶體
     */
    void controlMusic(String playState, int index);

}

這些介面在Activity 中實現。在presenter 中呼叫,以實現在presenter中控制ui。

MusicPresenter

public class MusicPresenter {

    IMusicView musicView;

    MusicModel musicModel;

    public MusicPresenter(IMusicView musicView) {
        this.musicView = musicView;
        musicModel = new MusicModel();
    }

這裡傳入MusicView 就可以通過介面呼叫直接控制介面顯示,直接控制MusicView.
例項化 MusicModel ,獲取Music模組相關的資料

public void getMusicData(String requirement) {
    if (null != musicView) {
        musicModel.getMusicData(requirement, new  <KugouMusicBean>(musicView) {
        @Override
        public void onSuccess(KugouMusicBean kugouMusicBean, Call call, Response                      response) {
    …
        musicView.getMusicDataSuccess(dateList);
    …
}
}

public void setMusicState(String musicState) {
    if (null != musicView) {
        musicView.controlMusic(musicState, -1);
    }
}

public void release() {
    if (null != musicModel) {
        musicModel.release();
    }
    musicModel = null;
    musicView = null;
}

}

從以上可以看出 在MusicPresenter 中直接呼叫musicModel 和MusicView 中的相關介面,
MusicActivity 中只做了控制元件的初始化等簡單的操作。使Activity的程式碼結構簡單。

MusicModel

public class MusicModel extends BaseModel implements SpeechInputContract.IMusicModel {

    @Override
    public void getMusicData(String requirement, AbsCallback callback){…}
    @Override
public void release(){…}
}

再看看MusicPresenter 初始化和引用的地方
SpeechInputPresenter

public class SpeechInputPresenter extends SpeechInputContract.ISpeechInputPresenter {
    MusicPresenter musicPresenter;
    musicPresenter = new MusicPresenter((SpeechInputContract.IMusicView) getContext());
}

當錄音並識別完成時,NLI 返回值 recognizeBean 。對這個結果進行分析處理並進行相應的邏輯和ui顯示。

private String dealRecognizeBean(RecognizeBean recognizeBean)
{…
    if (recognizeBean.getApiType().equals(MUSIC.getName())) {
{
            switch (semantic.getGlobalModifiers()[0]) {
                case MUSIC_PLAY:
                    if (semantic.hasSlots()) {
                   musicPresenter.getMusicData(semantic.getSlots()[0].getValue());
                    } else {
                                                                                                                                     musicPresenter.getMusicData(getContext().getString(R.string.recommend_music));
                    }
                    tempShowResult = getContext().getString(R.string.go_to_play);
                    break;
                case MUSIC_PAUSE:
                    musicPresenter.setMusicState(PAUSE);
                    tempShowResult = getContext().getString(R.string.go_to_pause);
                    break;
                case MUSIC_PRE:
                    musicPresenter.setMusicState(PRE_SONG);
                    tempShowResult = getContext().getString(R.string.go_to_play);
                    break;
                case MUSIC_NEXT:
                    musicPresenter.setMusicState(NEXT_SONG);
                    tempShowResult = getContext().getString(R.string.go_to_play);
                    break;
            }

…}