文章背景
在剛接觸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();
}
可以看出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基類代碼如下:
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();
}
}
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>
佈局裏面僅僅放了一個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) {
}
}
在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 獲取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));
}
}
}
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作爲具體的新聞列表數據處理層。
e、網絡接口
/**網絡接口
* Created by Administrator on 2017/7/6.
*/
public interface INewService {
@GET
Observable<NewsRequestModel> getNewList(@Url String url,
@QueryMap Map<String, String> params);
}
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類代碼如下:
/**
* @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.");
}
}
}
可以看出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、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();
}
}
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接口開發人員協商好再定義。
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;
}
}
作爲所以接口返回實體映射基類,這個也需要和後臺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;
}
}
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
Flutter電商實戰項目:https://github.com/dechengyang/ydc_flutter_app
如果對你有所幫助的話,賞我1元奶粉錢吧,多謝!
微信:
支付寶: