1. 程式人生 > >Android之淺談Android中的MVP

Android之淺談Android中的MVP

個人開發的微信小程式,目前功能是書籍推薦,後續會完善一些新功能,希望大家多多支援!
在這裡插入圖片描述

前言

為什麼使用MVP,網上有很多說法,最主要就是減輕了Activity的責任,相比於MVC中的Activity承擔的責任太多,因此有必要講講MVP。

MVP入門

在MVC框架中,View是可以直接讀取Model模型中的資料的,Model模型資料發生改變是會通知View資料顯示發生相應的改變。而在MVP中Model和View之間的沒有任何聯絡,是兩個完全獨立的模組,當Model模型發生資料改變時,通過Presenter通知View檢視發生相應的UI改變。因此,個人覺得:MVP才是正真的檢視和模型完全分離,也就是Model模型進行業務資料處理和View檢視顯示沒有任何關聯。可以通過下圖看出:

以下是MVC的框架圖:

這裡寫圖片描述

以下是MVP的框架圖:

這裡寫圖片描述

有關MVP說明的文章現在網上一大堆,最近自己也在嘗試使用MVP去構建應用,關於MVP三層的定義,可以看看下面相關資料:

model

資料加工處理廠

model是整個應用或介面的資料加工處理廠,所謂資料加工廠就是對資料的獲取,資料的解析,資料的儲存,資料的分發,資料的增刪改查等操作。意思就是凡是涉及到資料操作都是在model進行的,所以model不僅僅只是實體類的集合,同時還包含關於資料的各種處理操作。

三種資料來源

資料的資料來源有三種:記憶體,磁碟(檔案或資料庫等),網路。為了提升app的效能,有必要把經常訪問的資料臨時存入記憶體中;同時也為了提升app效能和為使用者省流量省電,有必要把資料存入磁碟中;還有的資料是有必要從網路讀取的。三個資料來源不一定同時存在,比如不與網路互動的app,不存在網路資料來源。所以凡是涉及到關於資料發生於三個數據源加工處理的操作的程式碼都要放在model中。

model為上層提供的服務:

model從黑盒的角度來看為上層(指依賴於model的層比如present)提供的服務無非就2種:model為上層提供資料,model處理上層傳遞的資料

model為上層提供資料:

上層會從model中去資料,那model會從三資料來源中取資料,取的順序是

  • 先記憶體,記憶體取到資料返回
  • 其次磁碟,磁碟取到資料,如有必要把資料儲存在記憶體中,則需要進行儲存,返回資料
  • 最後網路,網路取到資料,如有必要在磁碟或記憶體中儲存,則進行儲存,返回資料

上面的取資料過程是最簡單的情況,複雜些還會涉及到從記憶體或磁碟中取到的資料是否過期,過期的話就應該從網路獲取。從網路取得資料後需要把記憶體或磁碟的資料更新。

model處理上層傳遞的資料:

model接收到上層傳遞的資料後,model會依次把資料扔給三個資料來源去處理,有可能三個資料來源都會處理資料,有可能只是其中一個處理,model會把處理的結果返回。

所以model會把解析好的資料提供給上層,上層對於資料的來源完全是透明的,上層完全不需要關心資料到底是來自記憶體,還是磁碟甚至是網路。同理上層只需要的把資料扔給model,上層唯一做的事情就是愉快的等待處理結果。

presenter

presenter翻譯成漢語的意思是主持人,提出者。從它的意思可以看出它有控制全場的作用。首先presenter是處於mvp的中間層,在view和model中起一個承上啟下的作用。presenter會把view交給自己的命令進行一定的校驗等操作交給model處理,會把model處理的結果交給view。

presenter封裝業務:
presenter不僅起一個橋樑的作用,它還會把業務邏輯程式碼給包攬下來。這樣就可以減輕Activity的負擔了,讓Activity全心全意做它的view工作。那估計就有朋友犯迷糊了,哪些程式碼屬於業務邏輯呢?比如一些校驗程式碼。或者可以這樣想只要是不屬於view和model的程式碼基本都可以放在presenter中。

presenter負責重新整理view:
mvc或以前的關於view的寫法一般都是這樣,view在接收到資料後,自己來進行view的重新整理或其他操作。但是mvp中presenter負責對view進行重新整理,比如從model獲取的資料,presenter會根據獲取的資料成功與否來通知view應該是顯示成功介面還是失敗介面。這樣就讓Activity變的更輕了,變成了聽別人指揮的傻白甜了。這時候的presenter就有點主持人,掌控者的味道了。

presenter持有的執行緒:
Android中view的操作需要在ui執行緒裡執行,其他耗時操作需要在普通執行緒執行。presenter會持有這2種執行緒:ui執行緒,普通執行緒。重新整理view時,它切換為ui執行緒進行重新整理,從model取資料切換為普通執行緒。假如使用rxjava的話,就特別簡單了關於執行緒切換的事情。

view

view層就很好理解了,就是使用者直接看到的介面,mvp中的view是很省心的,比如更新view,接收資料。這些操作它都不需要操心,也不需要知道資料到底來自哪裡,給我啥我顯示啥就可以了。

一個view可以同時擁有多個presenter,也可以只有一個presenter。

Android中的Activity,Fragment在mvp中是作為view來使用的,這些Activity,Fragment的責任就小了,只關心介面相關的事情足矣。各種Adapter是放在view層的。

案例展示

既然清楚了MVP的一些概念,現在就可以建立一個專案,整個專案很簡單,通過點選按鈕獲取天氣資訊並顯示在介面上,這裡面我們使用MVP來搭建整個專案,先從Mode開始到Presenter最後View的建立。(天氣介面使用的是心知天氣提供的SDK http://www.thinkpage.cn/doc#info )

案例Model層

程式碼展示:

package weather.weatherproject.mode;

import com.thinkpage.lib.api.TPCity;
import com.thinkpage.lib.api.TPListeners;
import com.thinkpage.lib.api.TPWeatherManager;
import com.thinkpage.lib.api.TPWeatherNow;

import weather.weatherproject.WeatherApplication;

/**
 * 天氣管理類
 * Created by glh on 2016-06-23.
 */
public class WeatherManager {

    public static final WeatherManager instance = new WeatherManager();

    public interface WeatherListener{
        void onSuccess(TPWeatherNow response);

        void onFailed(String errString);
    }


    /**
     * 獲取指定城市的實況天氣。
     *
     * @param city
     * @param listener
     */
    public void getNowWeather(String city, final WeatherListener listener) {

        WeatherApplication.weatherManager.getWeatherNow(new TPCity(city), TPWeatherManager.TPWeatherReportLanguage.kSimplifiedChinese, TPWeatherManager.TPTemperatureUnit.kCelsius, new TPListeners.TPWeatherNowListener() {
            @Override
            public void onTPWeatherNowAvailable(TPWeatherNow tpWeatherNow, String s) {
                if (tpWeatherNow != null) {
                    listener.onSuccess(tpWeatherNow);
                } else {
                    listener.onFailed(s);
                }
            }
        });
    }

}

我們知道Model層主要用於資料的輸入和輸出,因此這裡建立了一個天氣管理類,通過獲取天氣資訊的方法獲取資料,最後將資料傳遞給Presenter,Presenter只需要將城市名傳遞給Model層,而它只需監聽獲取資訊的狀態,因此內部建立了一個天氣資訊獲取的監聽介面,通過它使得Presenter進行資訊獲取的監聽。

案例Presenter層

程式碼展示:

package weather.weatherproject.presenter;

/**
 * View的基礎介面
 * Created by glh on 2016-06-23.
 */
public interface IView {
    void initView();
}
package weather.weatherproject.presenter;

/**
 *
 * Created by glh on 2016-06-23.
 */
public interface IPresenter<V extends IView> {
    void onStop();

    void onResume();

    void onDestroy();

    void onPause();

    void onStart();

    void init(V view);
}

IView是一個基礎介面,內部定義了一個initView方法,用於View的初始化。
IPresenter也是一個基礎介面,內部定義了一些 Activity或Fragment常用的生命週期方法,用於View與 Activity或Fragment的聯動。

package weather.weatherproject.presenter.weather;

import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.IView;
import weather.weatherproject.view.bean.NowWeather;

/**
 * 獲取天氣的約定類,用於組合IWeatherView和IWeatherPresenter
 * Created by glh on 2016-06-23.
 */
public interface WeatherContract {
    interface IWeatherView extends IView {
        //獲取指定城市的實況天氣
        void showNowWeather(NowWeather result);

        void error(String error);
    }

    interface IWeatherPresenter extends IPresenter<IWeatherView> {
        void getWeather(String city);
    }
}

可以看到WeatherContract介面內部定義兩個介面:
1、IWeatherView介面,用於Presenter與View的資料傳遞。
2、IWeatherPresenter介面,是連線Model與View的中間層。

package weather.weatherproject.presenter.weather;

import com.thinkpage.lib.api.TPWeatherNow;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import weather.weatherproject.mode.WeatherManager;
import weather.weatherproject.presenter.bean.BeanUtil;

/**
 * 獲取天氣的Presenter
 * Created by glh on 2016-06-23.
 */
public class WeatherPresenter implements WeatherContract.IWeatherPresenter {

    private WeatherContract.IWeatherView mIWeatherView;
    private WeatherManager mWeatherManager = WeatherManager.instance;
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);

    @Override
    public void init(WeatherContract.IWeatherView view) {
        this.mIWeatherView = view;
        mIWeatherView.initView();
    }

    @Override
    public void getWeather(final String city) {
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                mWeatherManager.getNowWeather(city, new WeatherManager.WeatherListener() {
                    @Override
                    public void onSuccess(TPWeatherNow response) {
                        mIWeatherView.showNowWeather(BeanUtil.createNowWeather(response));
                    }

                    @Override
                    public void onFailed(String errString) {
                        mIWeatherView.error(errString);
                    }
                });
            }
        });
    }


    @Override
    public void onStop() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onDestroy() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStart() {

    }

}

WeatherPresenter主要做了以下幾件事情:
1、初始化了View。
2、通過View傳遞過來的指令向Model層請求資料。
3、監聽Model層的狀態,並將結果重新整理到View上。
請求資料屬於耗時操作因此我們開闢了執行緒用與請求處理,最後通過UI執行緒重新整理View。BeanUtil用於封裝我們所需的資料。

案例View層

程式碼展示:

package weather.weatherproject.view.base;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;

import java.util.HashSet;
import java.util.Set;

import weather.weatherproject.presenter.IPresenter;

/**
 * 基類Activity,所有業務介面都繼承此BaseActivity。
 * Created by glh on 2016-06-23.
 */
public abstract class BaseActivity extends FragmentActivity {
    private Set<IPresenter> mAllPresenters = new HashSet<>(1);

    /**
     * 獲取layout的id,具體由子類實現
     *
     * @return
     */
    protected abstract int getLayoutResId();

    /**
     * 需要子類來實現,獲取子類的IPresenter,一個activity有可能有多個IPresenter
     */
    protected abstract IPresenter[] getPresenters();

    /**
     * 初始化presenters
     */
    protected abstract void onInitPresenters();

    /**
     * 事件監聽
     */
    protected abstract void initEvent();

    /**
     * 從intent中解析資料,具體子類來實現
     *
     * @param argIntent
     */
    protected void parseArgumentsFromIntent(Intent argIntent) {
    }

    private void addPresenters() {

        IPresenter[] presenters = getPresenters();
        if (presenters != null) {
            for (int i = 0; i < presenters.length; i++) {
                mAllPresenters.add(presenters[i]);
            }
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResId());
        if (getIntent() != null) {
            parseArgumentsFromIntent(getIntent());
        }
        addPresenters();
        onInitPresenters();
        initEvent();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //依次呼叫IPresenter的onResume方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onResume();
            }
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        //依次呼叫IPresenter的onStop方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onStop();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        //依次呼叫IPresenter的onPause方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onPause();
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        //依次呼叫IPresenter的onStart方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onStart();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //依次呼叫IPresenter的onDestroy方法
        for (IPresenter presenter : mAllPresenters) {
            if (presenter != null) {
                presenter.onDestroy();
            }
        }
    }
}

BaseActivity只是做了一下封裝,方便我們展示介面的使用,最後我們看看展示介面WeatherActivity:

package weather.weatherproject.view.weather;

import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import weather.weatherproject.R;
import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.weather.WeatherContract;
import weather.weatherproject.presenter.weather.WeatherPresenter;
import weather.weatherproject.view.base.BaseActivity;
import weather.weatherproject.view.bean.NowWeather;

/**
 * 天氣介面
 * Created by glh on 2016-06-23.
 */
public class WeatherActivity extends BaseActivity implements WeatherContract.IWeatherView {

    private WeatherPresenter mWeatherPresenter = new WeatherPresenter();

    private TextView tv_show;
    private Button btn_now_weather;

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_main;
    }

    @Override
    protected IPresenter[] getPresenters() {
        return new IPresenter[]{mWeatherPresenter};
    }

    @Override
    protected void onInitPresenters() {
        mWeatherPresenter.init(this);
    }

    @Override
    protected void initEvent() {
        btn_now_weather.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(WeatherActivity.this, "onclick", Toast.LENGTH_SHORT).show();
                mWeatherPresenter.getWeather("shanghai");
            }
        });
    }

    @Override
    public void showNowWeather(NowWeather result) {
        tv_show.setText(result.toString());
    }

    @Override
    public void error(String error) {
        tv_show.setText(error);
    }

    @Override
    public void initView() {
        tv_show = (TextView) findViewById(R.id.tv_show);
        btn_now_weather = (Button) findViewById(R.id.btn_now_weather);
    }
}

最後可以看到我們的Activity非常的簡潔,到了這裡我們的專案已經搭建完成,這樣做有以下好處:
1、學習過設計模式的人都知道,這樣做基本符合了單一職責原則。
2、符合單一職責原則後,導致類與類組織更清晰。
3、View層與Model層互動需要通過Presenter層進行,這樣v與m層級間的耦合性降低。
4、通過這種分層處理,每一層的測試也相對簡單,維護性更高。

專案地址

MVPDemo請點選這裡