MVP+Retrofit+Rxjava在項目中實戰解析

文章目標

MVP在android中的原理解析 MVP+Retrofit+Rxjava在項目中實戰解析 架構經驗分享html

MVP簡單介紹

先說說MVC分層:
  • View:對應於佈局文件
  • Model:業務邏輯和實體模型
  • Controllor:對應於Activity 看起來的確像那麼回事,可是細細的想一想這個View對應於佈局文件,其實能作的事情特別少,實際上關於該佈局文件中的數據綁定的操做,事件處理的代碼都在Activity中,形成了Activity既像View又像Controller(固然了Data-Binder的出現,可能會讓View更像View吧)。
  • 當將架構改成MVP之後,Presenter的出現,將Actvity視爲View層,Presenter負責完成View層與Model層的交互。如今是這樣的:
    • View 對應於Activity,負責View的繪製以及與用戶交互 Model 依然是業務邏輯和實體模型 Presenter 負責完成View於Model間的交互

MVP模式的核心思想

MVP是模型(Model)、視圖(View)、主持人(Presenter)的縮寫,分別表明項目中3個不一樣的模塊。java

  • 模型(Model):負責處理數據的加載或者存儲,好比從網絡或本地[數據庫]
    (lib.csdn.net/base/mysql)…mysql

  • 視圖(View):負責界面數據的展現,與用戶進行交互;android

  • 主持人(Presenter):至關於協調者,是模型與視圖之間的橋樑,將模型與視圖分離開來。

  以下圖所示,sql

  • View與Model並不直接交互,而是使用Presenter做爲View與Model之間的橋樑。
  • 其中Presenter中同時持有Viwe層以及Model層的Interface的引用,
  • View層持有Presenter層Interface的引用。當View層某個界面須要展現某些數據的時候,首先會調用Presenter層的某個接口,而後Presenter層會調用Model層請求數據
  • 當Model層數據加載成功以後會調用Presenter層的回調方法通知Presenter層數據加載完畢
  • 最後Presenter層再調用View層的接口將加載後的數據展現給用戶。
  • 這就是MVP模式的整個核心過程。
    數據庫

    mvp0
    mvp0

    這樣分層的好處就是大大減小了Model與View層之間的耦合度。一方面可使得View層和Model層單獨開發與測試,互不依賴。另外一方面Model層能夠封裝複用,能夠極大的減小代碼量。固然,MVP還有其餘的一些優勢,這裏再也不贅述

    MVP在真實項目中的實戰

    上面已經介紹過MVP的核心思想以及基本架構,固然咱們在實際項目中不只僅要把建構劃分出來,還要加以延伸,這樣纔可以使項目的總體架構具有可擴展行、可複用性、可維護性、靈活性。下面我用我在實際項目中的角度來解析我所理解的MVP。編程

    整體架構圖:

    Paste_Image.png
    Paste_Image.png

    ####項目目錄結構:
    mvp2
    mvp2

    一、View層
    a、Iview接口代碼以下:

    /**
    * @Description MVP之V層 是全部VIEW的基類,其餘類能夠繼承該類
    * @Author ydc
    * @CreateDate 2016/10/10
    * @Version 1.0
    */
    public interface Iview<T> {
    
      /**
       * @description 全局的顯示加載框
       * @author ydc
       * @createDate
       * @version 1.0
       */
      void showLoading();
    
      /**
       * @description 全局的顯示加載框
       * @author ydc
       * @createDate
       * @version 1.0
       */
    
      ...
      /**
       * @description 當前fragment是否有效
       * @author ydc
       * @createDate
       * @version 1.0
       */
      boolean isActive();
      }複製代碼

    能夠看出Iview 接口是因此activity 和fragment最基本且共有的方法定義。api

b、NewsView接口代碼以下:bash

/**ydc 新聞列表所特有的方法定義
 * Created by Administrator on 2017/7/6.
 */

public interface NewsView extends Iview {
    void addNews(List<NewsBean> newsList);
    void showLoadFailMsg(String msg);
}複製代碼

NewsView接口繼承自Iview接口,定義新聞列表特有的方法。服務器

c、封裝BaseActivity基類

BaseActivity做爲因此activity的基類,你能夠把因此activity共有的方法和屬性提取到該中。複製代碼

d、activity_main.xml佈局文件代碼以下:

佈局裏面僅僅放了一個RecyclerView,用來展現數據列表。

e、NewListActivity代碼以下:

public class NewListActivity extends BaseActivity implements NewsView {

    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLayoutManager;
    private NewsAdapter mAdapter;
    private List<NewsBean> mData;
    private int pageIndex = 0;


    private NewsPresenter mPresenter;

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

        mPresenter = new NewsPresenter();
        mPresenter.attachView(this);
        mPresenter.subscribe();
........
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mAdapter = new NewsAdapter(getApplicationContext());
        mRecyclerView.setAdapter(mAdapter);



    }
........
    @Override
    public void addNews(List<NewsBean> newsList) {
        mAdapter.isShowFooter(true);
        if(mData == null) {
            mData = new ArrayList<NewsBean>();
        }
        mData.addAll(newsList);
        if(pageIndex == 0) {
            mAdapter.setmDate(mData);
        } else {
            //若是沒有更多數據了,則隱藏footer佈局
            if(newsList == null || newsList.size() == 0) {
                mAdapter.isShowFooter(false);
            }
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    public void showLoadFailMsg(String msg) {

    }
}複製代碼

在NewListActivity 中咱們能夠看到,NewListActivity 顯示實現了NewsView 接口,實現了NewsView和Iview 未實現的方法,在代碼中能夠看出NewListActivity並無作一些邏輯處理工做,僅僅作了添加數據和展現數據以及一些提示消息等工做,數據處理的工做都是調用 NewsPresenter 完成的。

二、Presenter層

a、Ipresenter 代碼以下:

/**
 * @Description MVP的P層
 * @Author ydc
 * @CreateDate 2016/10/10
 * @Version 1.0
 */
public interface Ipresenter<T extends Iview> {

    /**
     * @description 關聯P與V(綁定,VIEW銷燬適合解綁)
     * @author ydc
     * @createDate
     * @version 1.0
     */
    void attachView(T view);

    /**
     * @description 取消關聯P與V(防止內存泄漏)
     * @author ydc
     * @createDate
     * @version 1.0
     */
    void detachView();

    /**
     * @description RX訂閱
     * @author ydc
     * @createDate
     * @version 1.0
     */
    void subscribe();

    /**
     * @description RX取消訂閱
     * @author ydc
     * @createDate
     * @version 1.0
     */
    void unsubscribe();複製代碼

Ipresenter定義了全部presenter最基本且共有的方法。

b、BasePresenter代碼以下:

/**
 * @Description 抽象的公用Presenter
 * @Author ydc
 * @CreateDate 20170707
 * @Version 1.0
 */
public abstract class BasePresenter<T extends Iview> implements Ipresenter<T> {
    protected T mMvpView;//全部View
    protected SubscriptionList mSubscriptions;//rx註冊中心
    protected DataRepository mDataCenter;//數據中心
    //protected abstract SubscriptionList createSubscriptionList();//引入darger後取締
  ......
    /**
     * @description p&v沒有綁定的異常
     * @author ydc
     * @createDate
     * @version 1.0
     */
    public static class MvpViewNotAttachedException extends RuntimeException {
        public MvpViewNotAttachedException() {
            super("Please call Presenter.attachView(MvpView) before requesting data to the Presenter");
        }
    }

    /**
     * @description 統一添加訂閱關聯被觀察者和觀察者
     * @author ydc
     * @createDate
     * @version 1.0
     */
    public void addSubscription(Observable observable, Subscriber subscriber) {
        if( observable!=null && subscriber!=null ){
            if (mSubscriptions == null) {
                mSubscriptions = new SubscriptionList();
            }
            mSubscriptions.clear();
            mSubscriptions.add(observable
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(subscriber));
        }
    }

}複製代碼

BasePresenter是一個abstract類,在實現Ipresenter的未實現的方法以外,又擴展了幾個因此presenter共用的方法。

c、Presenter代碼以下:

/**ydc 新聞類的協議也能夠是接口
 * Created by Administrator on 2017/7/6.
 */
abstract class Presenter extends BasePresenter<NewsView> {
    public abstract void loadNews(int type, int page);
}複製代碼

能夠看出Presenter也是一個abstract類、繼承自BasePresenter抽象類,同時定義了新聞列表所特有的方法。

d、NewsPresenter代碼以下:

/**
 * Created by Administrator on 2017/7/6.
 */

public class NewsPresenter extends Presenter {

    private Model mModel;
    public  NewsPresenter(){
        mModel=new NewsModel();
    }

    @Override
    public void loadNews(int type, int page) {
        addSubscription(mModel.loadNews("nc/article/headline/T1348647909107/0-20.html",0), new ApiCallBack<NewsRequestModel>() {

            @Override
            public void onStart() {
                getMvpView().showLoading();
            }

            @Override
            public void onSuccess(NewsRequestModel modelBean) {
                if(modelBean!=null){
                    getMvpView().addNews(modelBean.getT1348647909107());
                }

            }

            @Override
            public void onFailure(String errorMsg) {
               getMvpView().showLoadFailMsg(errorMsg);
            }

            @Override
            public void onFinished() {
                getMvpView().hideLoading();
            }
        });
    }

    @Override
    public void subscribe() {

    }
}複製代碼

能夠看出NewsPresenter持有view和model的接口或是抽象類,起到中轉的做用。

三、Model層

a、Imodel接口代碼以下:

**
 * @Description MVP的M層
 * @Author ydc
 * @CreateDate 2016/10/10
 * @Version 1.0
 */
public interface Imodel {
}複製代碼

我這裏其實並無作什麼,只是留了一個接口而已,你能夠定義因此model的基本方法。

b、BaseModel代碼以下:

/**
 * @Description 數據模型基礎類
 * @Author ydc
 * @CreateDate 2016/11/2
 * @Version 1.0
 */
public abstract class BaseModel implements Imodel {

    /**
     * @description 返回服務接口對象實例
     * @author ydc
     * @createDate
     * @version 1.0
     */
    public <T> T createService(final Class<T> clazz) {
        validateServiceInterface(clazz);
        return (T) RxService.RETROFIT.createRetrofit().create(clazz);
    }

    /**
     * @description 校驗接口合法性
     * @author ydc
     * @createDate
     * @version 1.0
     */
    public <T> void validateServiceInterface(Class<T> service) {
        if (service == null) {
            //AppToast.ShowToast("服務接口不能爲空!");
        }
        if (!service.isInterface()) {
            throw new IllegalArgumentException("API declarations must be interfaces.");
        }
        if (service.getInterfaces().length > 0) {
            throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
        }
    }

}複製代碼

這個類我是用它來作Retrofit的初始化以及網絡請求的工做,固然我要進一步包裝Retrofit,因此這裏只看到一個方法調用。

c、Model代碼以下:

/**ydc 獲取數據的邏輯模塊協議,也能夠是接口,提供給P調用,在callback中更新V
 * Created by Administrator on 2017/7/6.
 */

public  abstract class Model extends BaseModel {
   public abstract Observable<NewsRequestModel> loadNews(String url, int type);
}複製代碼

既然公共BaseModel的職責任命爲整個網絡調用的工做,那麼我就要在抽象一個Model抽象類來定義新聞數據處理邏輯模塊協議,提供給P調用。

d、NewsModel代碼以下:

/**ydc 新聞數據處理協議
 * Created by Administrator on 2017/7/6.
 */

public class NewsModel extends Model {
    private INewService service=createService(INewService.class);

    @Override
    public Observable<NewsRequestModel> loadNews(String url, int type) {
        Map<String, String> map = new HashMap<>();
        //map.put("type", type+"");
        return service.getNewList(url,map);
    }
}複製代碼

這個類實現了Model做爲具體的新聞列表數據處理層。

MVP總結:

  • 當用戶進入到NewListActivity界面以後,界面須要展現新聞列表信息給用戶。
  • 首先NewListActivity會調用NewsPresenter的loadNews方法,NewsPresenter 的loadNews方法中又會調用NewsModel中的loadNews方法。
  • NewsModel中的loadNews方法中就是加載數據的核心,經過Retrofit請求服務器接口獲取數據,不管數據獲取成功與否,都會經過ApiCallBack回調給NewsPresenter 。
  • 若是獲取成功,NewsPresenter 會調用NewsView的addNews方法將獲取的新聞列表信息展現到RecyclerView。
  • 若是獲取失敗,則調用NewsView的showLoadFialMsg方法向用戶提示失敗信息。

RxJava 與 Retrofit 的結合簡單介紹

  • Retrofit 是 Square 的一個著名的網絡請求庫,是okHTTP的升級版,目前公認的最好的網絡請求框架。

  • 響應式編程RxJava就更不用說,它的強大之處只有用過的人才會體會獲得。

  • Retrofit 除了提供了傳統的 Callback 形式的 API,還有 RxJava 版本的 Observable 形式 API。下面我用對比的方式來介紹 Retrofit 的 RxJava 版 API 和傳統版本的區別。

  • 以獲取一個 User 對象的接口做爲例子。使用Retrofit 的傳統 API,你能夠用這樣的方式來定義請求:

    @GET("/user")
    public void getUser(@Query("userId") String userId, Callback<User> callback);複製代碼

    在程序的構建過程當中, Retrofit 會把自動把方法實現並生成代碼,而後開發者就能夠利用下面的方法來獲取特定用戶並處理響應:

    getUser(userId, new Callback<User>() {
      @Override
      public void success(User user) {
          userView.setUser(user);
      }
    
      @Override
      public void failure(RetrofitError error) {
          // Error handling
          ...
      }
    };複製代碼

    其實 Retrofit傳統的API調用與okHTTP功能和使用上沒有什麼本質的區別,它的強大之處在於與RxJava結合使用。

而使用 RxJava 形式的 API,定義一樣的請求是這樣的:

@GET("/user")
public Observable<User> getUser(@Query("userId") String userId);複製代碼

使用的時候是這樣的:

getUser(userId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });複製代碼

看到區別了嗎?

當 RxJava 形式的時候,Retrofit 把請求封裝進 Observable ,在請求結束後調用 onNext() 或在請求失敗後調用 onError()。

對比來看, Callback 形式和 Observable 形式長得不太同樣,但本質都差很少,並且在細節上 Observable 形式彷佛還比 Callback 形式要差點。那 Retrofit 爲何還要提供 RxJava 的支持呢?

單個請求體現不出它的優點所在,可是情景複雜起來, Callback 形式立刻就會開始讓人頭疼。

假設 /user 接口並不能直接訪問,而須要填入一個在線獲取的 token ,代碼應該怎麼寫?

Callback 方式,可使用嵌套的 Callback:

GET("/token")
public void getToken(Callback<String> callback);

@GET("/user")
public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback);

...

getToken(new Callback<String>() {
    @Override
    public void success(String token) {
        getUser(token, userId, new Callback<User>() {
            @Override
            public void success(User user) {
                userView.setUser(user);
            }

            @Override
            public void failure(RetrofitError error) {
                // Error handling
                ...
            }
        };
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
});複製代碼

卻是沒有什麼性能問題,但是迷之縮進並且充滿了無窮無盡的回調,這種後果你懂我也懂,作過大項目的人應該更懂。

而使用 RxJava 的話,代碼是這樣的:

@GET("/token")
public Observable<String> getToken();

@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);

...

getToken()
    .flatMap(new Func1<String, Observable<User>>() {
        @Override
        public Observable<User> onNext(String token) {
            return getUser(token, userId);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });複製代碼

用一個 flatMap() 就搞定了邏輯,整個請求都在一條鏈當中。讀者看到這裏應該明白我爲何選擇RxJava 與 Retrofit 的結合來處理網絡請求。其實RxJava有兩個比較核心的功能就是數據轉換和線程調度,固然它還有其它的強大之處,只是咱們用的最多的是這兩個而已。

RxJava 與 Retrofit 的結合在本項目中的應用

一、建立RxService類代:

RxService類主要用來初始化Retrofit以及添加頭部和系統參數,NewsModel初始化時,順帶完成了以上工做。

二、INewService接口代碼以下:

/**網絡接口
 * Created by Administrator on 2017/7/6.
 */

public interface INewService {

    @GET
    Observable<NewsRequestModel> getNewList(@Url String url,
                                            @QueryMap Map<String, String> params);
}複製代碼

這個類是來定義新聞列表網絡接口

三、管理被觀察者和觀察者

統一添加訂閱關聯被觀察者和觀察者

protected SubscriptionList mSubscriptions;//rx註冊中心


    /**
     * @description 統一添加訂閱關聯被觀察者和觀察者
     * @author ydc
     * @createDate
     * @version 1.0
     */
    public void addSubscription(Observable observable, Subscriber subscriber) {
        if( observable!=null && subscriber!=null ){
            if (mSubscriptions == null) {
                mSubscriptions = new SubscriptionList();
            }
            mSubscriptions.clear();
            mSubscriptions.add(observable
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(subscriber));
        }
    }複製代碼

RX取消訂閱代碼以下:

@Override
    public void unsubscribe(){
        if(mSubscriptions!=null){
            mSubscriptions.clear();
        }
    }複製代碼

以上兩段代碼是在BasePresenter抽象類中。

四、把請求添加到rx註冊中心SubscriptionLis中

Paste_Image.png
Paste_Image.png

五、NewsPresenter類
ApiCallBack是一個抽象類,處理網絡數據處理完成後的回調響應(即RxJava的觀察者),把它和被觀察者做爲參數一塊兒傳入到addSubscription方法中:

Paste_Image.png
Paste_Image.png

六、NewsRequestModel代碼以下

public class NewsRequestModel extends BaseFeed {
    public List<NewsBean> getT1348647909107() {
        return T1348647909107;
    }

    public void setT1348647909107(List<NewsBean> t1348647909107) {
        T1348647909107 = t1348647909107;
    }

    private List<NewsBean> T1348647909107;
}複製代碼

該類繼承自BaseFeed,做爲新聞列表接口返回實體映射,這個須要和後臺api接口開發人員協商好再定義。

做爲因此接口返回實體映射基類,這個也須要和後臺api開發人員協商好,至少我是這麼作的。複製代碼

####RxJava 與 Retrofit 的結合小結

  • 使用NewListActivity中onResume()方法調用 NewsPresenter中的loadNews(0,0),
  • 接着NewsPresenter再調用NewsModel()中的loadNews方法發起網絡請求,同時把請求中的被觀察者和觀察者添加到Rx註冊中心,註冊中心統一管理全部網絡請求。
  • 接下來NewsModel()初始化Retrofit以及各類基本參數添加,同時調用INewService網絡協議真正發起網絡請求。
  • 接下來網絡請求被咱們的ApiCallBack觀察者所接收,而後網絡數據又被觀察者回調到NewsPresenter類中的觀察者回調函數onSuccess中
  • 再把所得到的網絡數據使用getMvpView().addNews(modelBean.getT1348647909107())回發到NewListActivity中的addNews(List newsList)方法中,而後顯示到UI列表中
  • 最後退出時在onDestroy()中把Rx註冊中心的當前請求清除。
    效果圖:
    mvp11
    mvp11

Demo如今地址:

[download.csdn.net/download/xi…]

項目地址:

blog.csdn.net/xinanheisha…

若是你以爲此文對您有所幫助,歡迎入羣 QQ交流羣 :232203809
微信公衆號:終端研發部

技術+職場
技術+職場
相關文章
相關標籤/搜索