转自:https://blog.csdn.net/XiNanHeiShao/article/details/74732933
文章背景
在刚接触Android开始起,很长一段时间都在使用MVC模式开发项目,MVC模式很适合小项目的开发,简单方便。但当我们的项目越来越大时,MVC就显得力不从心,Activty或者Fragment中代码也会越来越多,导致项目的维护变的越来越复杂,业务剥离和复用难度大,一个新的框架或技术的出现必定是来解决前有的框架的缺陷,所以MVP的出现很优雅的解决了MVC中存在缺陷或不足。
文章目标
MVP在Android中的原理解析
MVP+Retrofit+Rxjava在项目中实战解析
架构经验分享
MVP简单介绍
MVC分层:
View:对应于布局文件
Model:业务逻辑和实体模型
Controllor:对应于Activity
看起来的确像那么回事,但是细细的想想这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller(当然了Data-Binder的出现,可能会让View更像View吧)。这可能也就是为何,在该文中有一句这样的话:
Most of the modern android applications just use View-Model architecture,everything is connected with Activity.
而当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:
View 对应于Activity,负责View的绘制以及与用户交互
Model 依然是业务逻辑和实体模型
Presenter 负责完成View于Model间的交互
MVP模式的核心思想
MVP是模型(Model)、视图(View)、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。
模型(Model):负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;
视图(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
*/
void showLoading(String msg);
/**
* @description 全局的显示加载框
* @author ydc
* @createDate
* @version 1.0
*/
void showLoading(String msg, int progress);
/**
* @description 全局的隐藏加载框
* @author ydc
* @createDate
* @version 1.0
*/
void hideLoading();
/**
* @description 全局消息展示
* @author ydc
* @createDate
* @version 1.0
*/
void showMsg(String msg);
/**
* @description 全局错误消息展示
* @author ydc
* @createDate
* @version 1.0
*/
void showErrorMsg(String msg, String content);
/**
* @description 关闭界面信息
* @author ydc
* @createDate
* @version 1.0
*/
void close();
/**
* @description 当前fragment是否有效
* @author ydc
* @createDate
* @version 1.0
*/
boolean isActive();
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
可以看出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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
NewsView接口继承自Iview接口,定义新闻列表特有的方法。
c、BaseActivity基类代码如下:
package com.example.ydcretrofitmvp.Base;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Window;
/**
* Created by Administrator on 2017/7/8.
*/
public class BaseActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
- 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
BaseActivity作为所以activity的基类,你可以把所以activity共有的方法和属性提取到该中。
d、activity_main.xml布局文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ydcretrofitmvp.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="8dp">
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
布局里面仅仅放了一个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 = (RecyclerView)findViewById(R.id.recycle_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new NewsAdapter(getApplicationContext());
mRecyclerView.setAdapter(mAdapter);
}
@Override
protected void onResume() {
super.onResume();
mPresenter.loadNews(0,0);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
mPresenter.unsubscribe();
}
}
@Override
public void showLoading() {
}
@Override
public void showLoading(String msg) {
}
@Override
public void showLoading(String msg, int progress) {
}
@Override
public void hideLoading() {
}
@Override
public void showMsg(String msg) {
}
@Override
public void showErrorMsg(String msg, String content) {
}
@Override
public void close() {
}
@Override
public boolean isActive() {
return false;
}
@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) {
}
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
在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();
}
- 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
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 获取V
* @author ydc
* @createDate
* @version 1.0
*/
public T getMvpView() {
return mMvpView;
}
/**
* @description view绑定P的时候初始化
* @author ydc
* @createDate
* @version 1.0
*/
@Override
public void attachView(T view) {
this.mMvpView = view;
this.mSubscriptions = new SubscriptionList();
this.mDataCenter = DataRepository.getInstance();
}
/**
* @description view失去绑定清除
* @author ydc
* @createDate
* @version 1.0
*/
@Override
public void detachView() {
unsubscribe();
this.mMvpView = null;
this.mSubscriptions = null;
this.mDataCenter = null;
}
@Override
public void unsubscribe(){
if(mSubscriptions!=null){
mSubscriptions.clear();
}
}
/**
* @description 当前的view(fragemnt&activity是否存在)
* @author ydc
* @createDate
* @version 1.0
*/
public boolean isViewAttached() {
return mMvpView != null;
}
/**
* @description 是否viewb绑定过P
* @author ydc
* @createDate
* @version 1.0
*/
public void checkViewAttached() {
if (!isViewAttached()) throw new MvpViewNotAttachedException();
}
/**
* @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));
}
}
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
可以看出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() {
}
}
- 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
可以看出NewsPresenter持有view和model的接口或是抽象类,起到中转的作用。
3、Model层
a、Imodel接口代码如下:
**
* @Description MVP的M层
* @Author ydc
* @CreateDate 2016/10/10
* @Version 1.0
*/
public interface Imodel {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
我这里其实并没有做什么,只是留了一个接口而已,你可以定义所以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.");
}
}
}
- 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
这个类我是用它来做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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
既然公共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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
这个类实现了Model作为具体的新闻列表数据处理层。
e、网络接口
/**网络接口
* Created by Administrator on 2017/7/6.
*/
public interface INewService {
@GET
Observable<NewsRequestModel> getNewList(@Url String url,
@QueryMap Map<String, String> params);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
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);
- 1
- 2
在程序的构建过程中, Retrofit 会把自动把方法实现并生成代码,然后开发者就可以利用下面的方法来获取特定用户并处理响应:
getUser(userId, new Callback<User>() {
@Override
public void success(User user) {
userView.setUser(user);
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
其实 Retrofit传统的API调用与okHTTP功能和使用上没有什么本质的区别,它的强大之处在于与RxJava结合使用。
而使用 RxJava 形式的 API,定义同样的请求是这样的:
@GET("/user")
public Observable<User> getUser(@Query("userId") String userId);
- 1
- 2
使用的时候是这样的:
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
...
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
看到区别了吗?
当 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
...
}
});
- 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
倒是没有什么性能问题,可是迷之缩进而且充满了无穷无尽的回调,这种后果你懂我也懂,做过大项目的人应该更懂。
而使用 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
...
}
});
- 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
用一个 flatMap() 就搞定了逻辑,整个请求都在一条链当中。读者看到这里应该明白我为什么选择RxJava 与 Retrofit 的结合来处理网络请求。其实RxJava有两个比较核心的功能就是数据转换和线程调度,当然它还有其它的强大之处,只是我们用的最多的是这两个而已。
RxJava 与 Retrofit 的结合在本项目中的应用
1、RxService类代码如下:
/**
* @Description RX&Retrofit
* @Author ydc
* @CreateDate 2016/10/31
* @Version 1.0
*/
public enum RxService {
RETROFIT;
private Retrofit mRetrofit;
private static final int READ_TIMEOUT = 60;//读取超时时间,单位秒
private static final int CONN_TIMEOUT = 50;//连接超时时间,单位秒
/**
* @description Head信息拦截
* @author ydc
* @createDate
* @version 1.0
*/
private Interceptor mHeadInterceptor = new Interceptor() {//头信息
@Override
public Response intercept(Chain chain) throws IOException {
//这个chain里面包含了request和response,所以你要什么都可以从这里拿
Request request = chain.request();
long t1 = System.nanoTime();//请求发起的时间
String method = request.method();
if ("POST".equals(method)) {
StringBuilder sb = new StringBuilder();
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
for (int i = 0; i < body.size(); i++) {
sb.append(body.encodedName(i) + "=" + body.encodedValue(i) + ",");
}
sb.delete(sb.length() - 1, sb.length());
Log.d("NET_RELATIVE",String.format("发送请求 %s on %s %n%s %nRequestParams:{%s}",
request.url(), chain.connection(), request.headers(), sb.toString()));
}
} else {
Log.d("NET_RELATIVE",String.format("发送请求 %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
}
Response response = chain.proceed(initHead(null,chain));
long t2 = System.nanoTime();//收到响应的时间
//这里不能直接使用response.body().string()的方式输出日志
//因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一
//个新的response给应用层处理
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.d("NET_RELATIVE",
String.format("接收响应: [%s] %n返回json:【%s】 %.1fms %n%s",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d,
response.headers()
));
return response;
}
};
/**
* @description 创建Retrofit对象
* @author ydc
* @createDate
* @version 1.0
*/
public Retrofit createRetrofit() {
if(mRetrofit == null){
OkHttpClient client = new OkHttpClient.Builder()//初始化一个client,不然retrofit会自己默认添加一个
.addInterceptor(mHeadInterceptor)
.connectTimeout(CONN_TIMEOUT, TimeUnit.MINUTES)//设置连接时间为50s
.readTimeout(READ_TIMEOUT, TimeUnit.MINUTES)//设置读取时间为一分钟
.build();
mRetrofit = new Retrofit.Builder()
.client(client)
.baseUrl(URLRoot.API_PATH)
.addConverterFactory(GsonConverterFactory.create())//返回值为Gson的支持(以实体类返回)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//返回值为Oservable<T>的支持
.build();
}
return mRetrofit;
}
/**
* @description 头信息初始化
* @author ydc
* @createDate
* @version 1.0
*/
public Request initHead(File file, Interceptor.Chain chain) {
Request.Builder mBuilder = chain.request().newBuilder();
Map<String, String> map = new HashMap<String, String>();
//HeaderUtil.setHeader(map);//ydc 移植注释
if (file != null) {
//String mSize = MD5Util.MD5(file.length() + "");
String mSize ="10";
//SharedUtil.setFileSize(mSize);//andy.fang 文件大小MD5保存头信息,方便后台校验
map.put("filesize", mSize);//上传下载文件的MD5值 不需要每个地方都加,在需要的地方加
}
Set keys = map.keySet();
if (keys != null) {
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
String value = (String) map.get(key);
/*if(StrUtil.isNotBlank(key)&& StrUtil.isNotBlank(value)){
mBuilder.addHeader(key, value);
}*/
}
}
//系统级请求参数
//mBuilder.addHeader("token", SharedUtil.getPreferStr("TOKEN"));//ydc 移植注释
// mBuilder.addHeader("v", "1.0");
// mBuilder.addHeader("format", "JSON");
// mBuilder.addHeader("appKey", "00001");
Request mRequest = mBuilder.build();
return mRequest;
}
/**
* @description 返回服务接口对象实例
* @author ydc
* @createDate
* @version 1.0
*/
public <T> T createService(final Class<T> service) {
validateServiceInterface(service);
return (T) RxService.RETROFIT.createRetrofit().create(service);
}
/**
* @description 校验接口合法性
* @author ydc
* @createDate
* @version 1.0
*/
public <T> void validateServiceInterface(Class<T> service) {
if (service == null) {
//Toast.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.");
}
}
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
可以看出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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这个类是来定义新闻列表网络接口
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));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
RX取消订阅代码如下:
@Override
public void unsubscribe(){
if(mSubscriptions!=null){
mSubscriptions.clear();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
以上两段代码是在BasePresenter抽象类中。
4、把请求添加到rx注册中心SubscriptionLis中
5、ApiCallBack代码如下:
/**
* @Description 处理网络数据处理完成后的回调响应(观察者)
* @Author ydc
* @CreateDate 2016/10/28
* @Version 1.0
*/
public abstract class ApiCallBack<M> extends Subscriber<M> {
/**
* @description 成功接口回调,提供给View处理页面问题
* @author ydc
* @createDate
* @version 1.0
*/
public abstract void onSuccess(M modelBean);
/**
* @description 失败接口回调,提供给View处理页面问题
* @author ydc
* @createDate
* @version 1.0
*/
public abstract void onFailure(String errorMsg);
/**
* @description 请求结束,提供给View处理页面问题
* @author ydc
* @createDate
* @version 1.0
*/
public abstract void onFinished();
@Override
public void onNext(M modelBean) {
if(modelBean!=null){
BaseFeed feed = (BaseFeed) modelBean;
String status = feed.getStatus();
status="S";//由于是演示所以手动赋值,正在开发中和服务器协商好
if("S".equalsIgnoreCase(status)){
onSuccess(modelBean);
}else {
onFailure(feed.getMessage());
}
}
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onCompleted() {
onFinished();
}
/**
* @description 統一处理异常的回调
* @author Andy.fang
* @createDate
* @version 1.0
*/
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (e instanceof HttpException) {
HttpException httpException = (HttpException) e;
int exceptionCode = httpException.code();
String msg = httpException.getMessage();
if (exceptionCode == 401) {
msg = "用户名密码错误,请重新登录!";
}
if (exceptionCode == 403 || exceptionCode == 404 || exceptionCode == 407 || exceptionCode == 408) {
msg = "网络链接超时,请稍后再试!";
}
if (exceptionCode == 501 || exceptionCode == 502 || exceptionCode == 504) {
msg = "服务器无响应,请稍后再试!";
}
onFailure(msg);
} else {
onFailure(e.getMessage());
}
onFinished();
}
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
该类继承自BaseFeed,作为新闻列表接口返回实体映射,这个需要和后台api接口开发人员协商好再定义。
7、BaseFeed代码如下:
/**
* @Description 返回接口的基类
* @Author ydc
* @CreateDate 2016/11/15
* @Version 1.0
*/
public class BaseFeed {
private String token;
private String status;
private String message;
private String solution;
private int totalSize;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public String getSolution() {
return solution;
}
public void setSolution(String solution) {
this.solution = solution;
}
}
- 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
- 51
- 52
- 53
- 54
- 55
作为所以接口返回实体映射基类,这个也需要和后台api开发人员协商好,至少我是这么做的。
8、NewsBean(新闻实体)代码如下:
public class NewsBean implements Serializable {
/**
* docid
*/
private String docid;
/**
* 标题
*/
private String title;
/**
* 小内容
*/
private String digest;
/**
* 图片地址
*/
private String imgsrc;
/**
* 来源
*/
private String source;
/**
* 时间
*/
private String ptime;
/**
* TAG
*/
private String tag;
public String getDocid() {
return docid;
}
public void setDocid(String docid) {
this.docid = docid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDigest() {
return digest;
}
public void setDigest(String digest) {
this.digest = digest;
}
public String getImgsrc() {
return imgsrc;
}
public void setImgsrc(String imgsrc) {
this.imgsrc = imgsrc;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getPtime() {
return ptime;
}
public void setPtime(String ptime) {
this.ptime = ptime;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
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
如果对你有所帮助的话,赏我1元奶粉钱吧,多谢!
微信:
支付宝: