RxJava+Retrofit2+MVP實現網路請求
上一遍部落格介紹了RxJava+Retrofit2的使用。在前段時間,刷招聘簡歷的時候,發現有一部分的公司會要求MVP模式的理解和具體使用。在現在越來越複雜的業務,我們的Activity的負擔也是越來越大,因此接著這篇我結合MVP模式來介紹一下自己對RxJava+Retrofit2+MVP的使用。
MVP的理解
RxJava+Retrofit2+MVP三者結合使用
MVP的理解
因為我相信大家在開發App時,肯定會發現,Activity的負擔非常重,既要初始化控制元件,又要寫一些邏輯操作的展示等等,有時候很多Activity中的程式碼都充當了Controller和Model的角色,所以你會發現Activity違背單一職責原則,負擔過重。所以,就出現了這麼一種架構模式,叫MVP。
而當將架構改為MVP以後,Presenter的出現,將Actvity視為View層,Presenter負責完成View層與Model層的互動。現在是這樣的:
- 檢視(View)對應於Activity,負責View的繪製以及與使用者互動
- 模型(Model)依然是業務邏輯和實體模型
- 主持人(Presenter)相當於協調者,是模型與檢視之間的橋樑,負責完成View於Model間的互動,將模型與檢視分離開來。
借下圖一用,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還有其他的一些優點,這裡不再贅述。
但是你可以發現這裡超級多累簡直就是類數量爆炸,程式碼複雜度和學習成本高,還有在某些場景下presenter的複用會產生介面冗餘。所以MVP模式是不是適合你使用?在使用之前需要思考一下是否有必要對自己的專案動刀?
RxJava+Retrofit2+MVP三者結合使用
首先看看我們用到的庫
dependencies {
compile fileTree(dir : 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.squareup.okhttp3:okhttp:3.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
}
再看看包目錄結構,相信有部分的同學曾糾結應該如何怎麼佈置自己的專案目錄結構,可以參考一下我的方式;
View:使用者在登入介面互動觸發的事件,我們需要處理到的方法。這裡需要我們想象一下在我們的Activity中一共會出現的互動有哪些。
public interface IUserLoginView {
void loginSuccess();
void loginFail(int toast, String reason);
void showLoading();
void hideLoading();
void saveCommonUserInfo(UserInfo info);
}
負責登入的Activity
public class LoginActivity extends MvpActivity<UserLoginPresenter> implements IUserLoginView {
private Context mContext;
@Bind(R.id.editText_name)
EditText mEtGwNumber;
@Bind(R.id.editText_pwd)
EditText mEtPassword;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mContext = this;
}
@Override
protected UserLoginPresenter createPresenter() {
return new UserLoginPresenter(this);
}
@OnClick(R.id.button_login)
public void onClickLogin() {
Map<String, String> maps = new HashMap<>();
maps.put("name", mEtGwNumber.getText().toString());
maps.put("pwd", mEtPassword.getText().toString());
mvpPresenter.login(maps);
}
@Override
public void loginSuccess() {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
}
@Override
public void loginFail(int toast, String reason) {
if (reason.isEmpty()) {
Toast.makeText(LoginActivity.this, getString(toast), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(LoginActivity.this, reason, Toast.LENGTH_LONG).show();
}
}
@Override
public void showLoading() {
DialogUtils.getInstance().popRemindDialog(mContext, "正在登入");
}
@Override
public void hideLoading() {
DialogUtils.getInstance().disMissRemind();
}
@Override
public void saveCommonUserInfo(UserInfo info) {
//這裡將登入介面返回的資料,根據實際情況儲存在SP或者SQLite
}
}
這裡會繼承一個MvpActivity,管理Presenter對Activity的生命週期,防止Presenter持有Activity物件可能導致的記憶體洩漏問題。
public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity {
protected P mvpPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
mvpPresenter = createPresenter();
super.onCreate(savedInstanceState);
}
protected abstract P createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
if (mvpPresenter != null) {
mvpPresenter.detachView();
}
}
}
使用者點選登入按鈕,實現IUserLoginModel介面,傳入一個Map集合物件,返回Subscription型別
public interface IUserLoginModel {
Subscription login(Map<String, String> maps, OnLoginListener listener);
}
對IUserLoginModel介面進行結果監聽
public interface OnLoginListener {
void loginSuccess(UserInfo userInfo);
void loginFailed(int toast, String reason);
void requestCompleted();
}
通過UserLoginModel去實現IUserLoginModel介面,結合RxJava+Retrofit2實現。這裡的具體內容可以引數我上篇部落格。
public class UserLoginModel implements IUserLoginModel {
@Override
public Subscription login(Map<String, String> maps, final OnLoginListener listener) {
Observable<JSONObject> observable = RetrofitManage.getInstance().getHttpServiceConnection().login(maps);
return observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io()) //在io執行緒中處理網路請求
.observeOn(AndroidSchedulers.mainThread()).subscribe(new SubscriberCallBack<>(new ApiCallback<JSONObject>() {
@Override
public void onSuccess(JSONObject model) {
UserInfo userInfo = JsonHandleAdapter.getUserLoginInfo(model.toString());
listener.loginSuccess(userInfo);
}
@Override
public void onFailure(int msg, String reason) {
listener.loginFailed(msg, reason);
}
@Override
public void onCompleted() {
listener.requestCompleted();
}
}));
}
}
到View和Model的中轉站Presenter,這裡將Model返回的結果進行判斷處理。
首先Presenter的基類BasePresenter
public class BasePresenter<V> implements Presenter<V> {
public V mvpView;
private CompositeSubscription mCompositeSubscription;
@Override
public void attachView(V view) {
this.mvpView = view;
}
@Override
public void detachView() {
this.mvpView = null;
onUnSubscribe();
}
//RxJava取消註冊,以避免記憶體洩露
public void onUnSubscribe() {
if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
mCompositeSubscription.unsubscribe();
}
}
public void addSubscription(Subscription subscription) {
if (mCompositeSubscription == null) {
mCompositeSubscription = new CompositeSubscription();
}
mCompositeSubscription.add(subscription);
}
}
泛型Presenter:
public interface Presenter<V> {
void attachView(V view);
void detachView();
}
最終我們的UserLoginPresenter登入Presenter,我們這裡登入Presenter,簡化了不少,但是具體思路是這樣。
public class UserLoginPresenter extends BasePresenter<IUserLoginView> {
private IUserLoginModel iUserLoginModel;
public UserLoginPresenter(IUserLoginView view) {
attachView(view);
iUserLoginModel = new UserLoginModel();
}
public void login(Map<String, String> maps) {
mvpView.showLoading();
addSubscription(iUserLoginModel.login(maps, new OnLoginListener() {
@Override
public void loginSuccess(UserInfo userInfo) {
mvpView.loginSuccess();
if (null != userInfo)
mvpView.saveCommonUserInfo(userInfo);
}
@Override
public void loginFailed(int toast, String reason) {
mvpView.loginFail(toast, reason);
}
@Override
public void requestCompleted() {
mvpView.hideLoading();
}
}));
}
}
注意上述程式碼,我們的Presenter完成View和Model的互動。首先我們在View接收到使用者的操作,我們通過實現Presenter的登入方法,在Model的登入方法中完成耗時操作網路請求,請求後的結果回撥給Presenter,然後Presenter在呼叫View的物件,執行View對應的方法。
總結:
這裡小小的登入介面,就需要到很多實現介面類,所以在我們的實際情況中,到底要不要全部採用MVP模式呢?需要大傢俱體情況具體分析。不要盲目認為MVP模式好厲害,好整潔就使用,畢竟我們需要保證我們的程式正常執行,程式碼如期迭代,其他的同學看你的程式碼不會暈掉。
另外我的封裝方式不一定適用你,但是這個方式是在我線上的專案中已經實行了。不過我感覺還是需要繼續迭代、繼續優化、繼續學習。謝謝大家!
原始碼點選下載: