查看原文
其他

MVP+Retrofit+Rxjava实战

2017-07-19 人与宇宙的关系 终端研发部


前言介绍

MVP在android中的原理解析 MVP+Retrofit+Rxjava在项目中实战解析


人与宇宙的关系的博客地址:

http://blog.csdn.net/xinanheishao/article/details/74732933

正文

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个不同的模块。

  • 模型(Model):负责处理数据的加载或者存储,比如从网络或本地[数据库] (http://lib.csdn.net/base/mysql)获取数据等;

  • 视图(View):负责界面数据的展示,与用户进行交互;

  • 主持人(Presenter):相当于协调者,是模型与视图之间的桥梁,将模型与视图分离开来。

如下图所示,

  • View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。

  • 其中Presenter中同时持有Viwe层以及Model层的Interface的引用,

  • View层持有Presenter层Interface的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据

  • 当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载完毕

  • 最后Presenter层再调用View层的接口将加载后的数据展示给用户。

  • 这就是MVP模式的整个核心过程。 这样分层的好处就是大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。另一方面Model层可以封装复用,可以极大的减少代码量。当然,MVP还有其他的一些优点,这里不再赘述

MVP在真实项目中的实战

上面已经介绍过MVP的核心思想以及基本架构,当然我们在实际项目中不仅仅要把建构划分出来,还要加以延伸,这样才能够使项目的整体架构具备可扩展行、可复用性、可维护性、灵活性。下面我用我在实际项目中的角度来解析我所理解的MVP。

总体架构图:

项目目录结构:

1、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最基本且共有的方法定义。

  • b、NewsView接口代码如下:

/**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 完成的。

2、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的接口或是抽象类,起到中转的作用。

3、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 的结合在本项目中的应用

  • 1、创建RxService类代:

    RxService类主要用来初始化Retrofit以及添加头部和系统参数,NewsModel初始化时,顺带完成了以上工作。

  • 2、INewService接口代码如下:

    /**网络接口   * Created by Administrator on 2017/7/6.   */    public interface INewService {        @GET      Observable<NewsRequestModel> getNewList(@Url String url,                                              
           @QueryMap Map<String, String> params);  }

    这个类是来定义新闻列表网络接口

  • 3、管理被观察者和观察者

    统一添加订阅关联被观察者和观察者

    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抽象类中。

  • 4、把请求添加到rx注册中心SubscriptionLis中

  • 5、NewsPresenter类 ApiCallBack是一个抽象类,处理网络数据处理完成后的回调响应(即RxJava的观察者),把它和被观察者作为参数一起传入到addSubscription方法中:

  • 6、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注册中心的当前请求清除。

 效果图: 

由于微信做了篇幅的限制,阅读原文,请参考

Demo现在地址:

http://download.csdn.net/download/xinanheishao/9892674



博客:

http://blog.csdn.net/xinanheishao/article/details/74732933

终端研发部提倡 没有做不到的,只有想不到的。

在这里获得的不仅仅是技术! 


让心,在阳光下学会舞蹈

让灵魂,在痛苦中学会微笑

—终端研发部—



如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809   

微信公众号:终端研发部


            

这里学到的不仅仅

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存