上一篇,算是完成了準(zhǔn)備工作爆哑,那么這篇就來說說MVP和RxJava的封裝了面哼。首先看看接口返回數(shù)據(jù)的格式:
{
"code" : 1,
"message" : "請求成功!" ,
"data" : {
"name": "張三",
"age": 3
}
}
code乔妈、message、data標(biāo)準(zhǔn)的三大門神氓皱。一般是以這種格式返回數(shù)據(jù)路召。數(shù)據(jù)格式的統(tǒng)一利于封裝,以此數(shù)據(jù)格式為準(zhǔn)的實體基類如下
在dataframework內(nèi)新建包model和BaseResponseBean類波材。
package com.example.burro.demo.dataframework.model;
/**基類 泛型T為實體數(shù)據(jù)
* Created by ex.zhong on 2017/9/23.
*/
public class BaseResponseBean<T> {
private int code;
private String message;
private T data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
由于后面的demo用到豆瓣的API股淡,很遺憾它的格式并非上述標(biāo)準(zhǔn)的格式。在項目中返回的數(shù)據(jù)用了繼承父類方式廷区,而非上面的泛型方式揣非,為了區(qū)分。新的父類名字我改為BaseResultBean
躲因,上面的標(biāo)準(zhǔn)格式基類我仍舊保存到demo中早敬,如果更換的話,那也是分分鐘的事情大脉。后面案例和講解也將使用BaseResultBean搞监,
其內(nèi)容如下:
package com.example.burro.demo.dataframework.model;
/**返回數(shù)據(jù)父類。子類可繼承
* Created by ex.zhong on 2017/9/23.
*/
public class BaseResultBean {
protected int code;
protected String msg;
public BaseResultBean() {
}
public BaseResultBean(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
當(dāng)然镰矿,項目中要根據(jù)實際數(shù)據(jù)為準(zhǔn)來調(diào)整調(diào)整字段琐驴、結(jié)構(gòu)等。沒有必要過于糾結(jié)數(shù)據(jù)格式問題秤标,換湯不換藥绝淡,道理是一樣的。
下面進行MVP相關(guān)內(nèi)容的講解苍姜!
依賴包引入:
項目的build.gradle增加如下appframework
/*rx-android-java*/
rxjava : 'io.reactivex:rxjava:1.1.0',
rxandroid : 'io.reactivex:rxandroid:1.1.0',
retrofit : 'com.squareup.retrofit2:retrofit:2.0.2',
converter_gson : 'com.squareup.retrofit2:converter-gson:2.0.2',
adapter_rxjava : 'com.squareup.retrofit2:adapter-rxjava:2.0.2',
//compile 'com.google.code.gson:gson:2.6.2'
logging_interceptor : 'com.squareup.okhttp3:logging-interceptor:3.3.0',
spots_dialog : 'com.github.d-max:spots-dialog:0.7@aar',
dataframework的build.gradle增加如下
compile deps.rxjava
compile deps.rxandroid
compile deps.retrofit
compile deps.converter_gson
compile deps.adapter_rxjava
compile deps.logging_interceptor
compile deps.spots_dialog
compile deps.annotation
BaseView
寫之前需要在
appframework
下新建包mvp
,mvp下新建三個包contract
,presenter
,view
在view下新建接口
BaseView
牢酵,Baseview接口內(nèi)的方法是頁面內(nèi)【Activity或者Fragment】需要執(zhí)行的通用方法。這里先定義一個 showError(BaseResultBean resultBean);
返回正確情況有很多種衙猪,在實現(xiàn)類中增加馍乙,若錯誤,我們要統(tǒng)一處理垫释。所以showError(BaseResultBean resultBean)方法是全局共有的丝格。
package com.example.burro.demo.appframework.mvp.view;
import com.example.burro.demo.dataframework.model.BaseResultBean;
/**View接口
* Created by ex.zhong on 2017/9/23.
*/
public interface BaseView {
void showError(BaseResultBean resultBean);
}
BasePresenter
Presenter和View創(chuàng)建類似,
在presenter下新建IPresenter
棵譬,IPresenter attachView(T view); void detachView();兩個方法是全局共有的
package com.example.burro.demo.appframework.mvp.presenter;
import com.example.burro.demo.appframework.mvp.view.BaseView;
/**Presenter接口
* 注:在創(chuàng)建presenter時綁定显蝌,在頁面destroy()時解綁。
* Created by ex.zhong on 2017/9/23.
*/
public interface IPresenter<T extends BaseView> {
void attachView(T view);
void detachView();
}
因為幾乎每個Presenter實現(xiàn)類里都要處理綁定和解綁事件订咸,所以我們要把這個處理過程提取出來曼尊,此處寫一個基類BasePresenter統(tǒng)一管理扭屁,在presenter下新建BasePresenter
實現(xiàn)IPresenter
package com.example.burro.demo.appframework.mvp.presenter;
import com.example.burro.demo.appframework.mvp.view.BaseView;
/**
* Presenter基類。目的是統(tǒng)一處理綁定和解綁
* Created by ex.zhong on 2017/9/23.
*/
public class BasePresenter<T extends BaseView> implements IPresenter<T> {
protected T mView;
@Override
public void attachView(T mView) {
mView = mView;
}
@Override
public void detachView() {
mView = null;
}
// public boolean isViewAttached() {
// return mView != null;
// }
// public void checkViewAttached() {
// if (!isViewAttached()) throw new
// MvpViewNotAttachedException();
// }
![Uploading 04_766476.png . . .]
// public static class //MvpViewNotAttachedException extends //RuntimeException {
// public MvpViewNotAttachedException() {
// super("Please call //Presenter.attachView(MvpView) before" +
// " requesting data to the //Presenter");
// }
// }
}
【備注:這里的checkViewAttached(),在rxJava未引入之前使用涩禀。目的是判斷頁面是否還存在料滥,若不存在則不執(zhí)行。rxJava中對此作了處理艾船。只需調(diào)用解綁方法即可葵腹。在此處稍作提及,后面我會直接刪掉此內(nèi)容】
稍后會寫一個測試類TestActivty結(jié)合豆瓣的API屿岂。來詳解mvp的使用践宴。在此之前先來封裝一下BaseActivity,因為一般都是在BaseActivity中進行Presenter和View的初始化綁定
BaseActivity
在appframework新建ui包,包內(nèi)新建BaseActivity抽象類
類中都是基本的要素爷怀,且注釋較為詳細(xì)阻肩,容易理解。其中有個別方法是為了和后面內(nèi)容對接运授,直接貼代碼:
package com.example.burro.demo.appframework.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import com.example.burro.demo.appframework.BaseApplication;
import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;
import com.example.burro.demo.appframework.mvp.view.BaseView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* BaseActivity Activity基類
* butterKnife的綁定 初始方法的設(shè)定 presentet和view的綁定
* Created by ex.zhong on 2017/9/23.
*/
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity implements BaseView,Toolbar.OnMenuItemClickListener {
protected T mPresenter;
protected Activity mContext;
private Unbinder mUnbinder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(initLayoutInflater());
mUnbinder = ButterKnife.bind(this);
mContext = this;
createPresenter();
if (mPresenter != null) mPresenter.attachView(this);
BaseApplication.getInstance().addActivity(this);
initParams();
initViews();
}
protected abstract int initLayoutInflater(); //初始化布局
protected abstract void initParams(); //初始化參數(shù)
protected abstract void initViews(); //初始化控件
protected abstract void createPresenter(); //創(chuàng)建presenter
/**
* @param toolbar toolbar 控件
* @param title 標(biāo)題
*/
protected void setToolBar(Toolbar toolbar, String title) {
if (toolbar != null) {
if (title != null) toolbar.setTitle(title);
setSupportActionBar(toolbar);
toolbar.setOnMenuItemClickListener(this);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onBackPressed();
}
});
}
}
//toolbar右側(cè)menu點擊事件
@Override
public boolean onMenuItemClick(MenuItem item) {
return false;
}
//統(tǒng)一處理錯誤信息
public void handleError(BaseResultBean errResult) {
if (errResult == null) return;
if (this == null) return;
//可以分門別類的處理 錯誤消息烤惊,如session過期,跳轉(zhuǎn)到登錄頁面吁朦。其他情況提示即可
ToastUtils.showToast(mContext, errResult.getMsg());
}
@Override
protected void onDestroy() {
if (mPresenter != null) mPresenter.detachView();
if (mUnbinder != null) mUnbinder.unbind();
super.onDestroy();
}
}
在biz新建測試類
新建內(nèi)容如下 biz/test/view/TestActivity柒室、biz/test/TestContract、biz/test/TestPresenterImpl
1.包內(nèi)新建TestActivity繼承BaseActivity.TestActivity
package com.example.burro.demo.appbiz.test.view;
import com.example.burro.demo.appbiz.R;
import com.example.burro.demo.appbiz.R2;
import com.example.burro.demo.appbiz.test.TestContract;
import com.example.burro.demo.appbiz.test.TestPresenterImpl;
import com.example.burro.demo.appframework.ui.BaseActivity;
import com.example.burro.demo.appframework.util.LogUtils;
import com.example.burro.demo.databiz.model.test.MovieListBean;
import com.example.burro.demo.dataframework.model.BaseResultBean;
import butterknife.OnClick;
/**測試頁面
* Created by ex.zhong on 2017/9/23.
*/
public class TestActivity extends BaseActivity<TestPresenterImpl> implements TestContract.View{
@Override
protected int initLayoutInflater() {
return R.layout.activity_test;
}
@Override
protected void initParams() {
}
@Override
protected void initViews() {
}
@Override
protected void createPresenter() {
mPresenter = new TestPresenterImpl();
}
@Override
public void showError(BaseResultBean resultBean) {
//錯誤處理
handleError(resultBean);
}
@Override
public void setMovieListData(MovieListBean bean) {
LogUtils.i("TAG",bean==null?"":bean.toString());
}
@OnClick(R2.id.btnTest)
public void getMovieListData(){
mPresenter.getMovieListData(1,15);
}
}
這里的showError(),是BasePresenter中的回調(diào)方法逗宜,用來統(tǒng)一處理錯誤情況雄右,若頁面有RecycalView并正在刷新的情況,也可在此處結(jié)束刷新纺讲。因為每個頁面都會showError()擂仍,所以我們需要在BaseActivity里增加統(tǒng)一處理的方法handleError(resultBean),內(nèi)容如下:
//統(tǒng)一處理錯誤信息
public void handleError(BaseResultBean errResult) {
if (errResult == null) return;
if (this == null) return;
//可以分門別類的處理 錯誤消息熬甚,如session過期逢渔,跳轉(zhuǎn)到登錄頁面。其他情況提示即可
ToastUtils.showToast(mContext, errResult.getMsg());
}
值得強調(diào)的是则涯,增加錯誤結(jié)果統(tǒng)一處理很有必要复局,也很少有人注意這點,我們后面網(wǎng)絡(luò)請求錯誤結(jié)果的返回也會與此對接粟判。此處默認(rèn)是給出Toast提示信息,當(dāng)然還有很多其他操作峦剔,正如注釋所說:如果errResult的code是session過期的標(biāo)識档礁,那么我們給出提示的同時也會跳轉(zhuǎn)至登錄頁面等等。
2.TestContract:
Contract:d單詞意思為契約吝沫、協(xié)議呻澜。TestContract即協(xié)議類递礼,定制mvp各層接口和實現(xiàn)方法。說白了羹幸,就是把v層和p層需要實現(xiàn)的方法統(tǒng)一在一塊脊髓,方便管理,也起到了解耦作用栅受。
package com.example.burro.demo.appbiz.test;
import com.example.burro.demo.appframework.mvp.presenter.IPresenter;
import com.example.burro.demo.appframework.mvp.view.BaseView;
import com.example.burro.demo.databiz.model.test.MovieListBean;
/**協(xié)議類将硝,定制mvp各層接口和實現(xiàn)方法
* Contract:d單詞意思為契約 協(xié)議
* 接口View內(nèi) 定義實現(xiàn)view內(nèi)所需方法
* 接口Presenter 定義實現(xiàn)presenter內(nèi)所需的方法
* Created by ex.zhong on 2017/9/23.
*/
public class TestContract {
public interface View extends BaseView {
void setMovieListData(MovieListBean bean);
}
public interface Presenter extends IPresenter<View> {
void getMovieListData(int start,int count);
}
}
3.TestPresenterImpl:
TestPresenterImpl繼承自BasePresenter,初級版本如下:
package com.example.burro.demo.appbiz.test;
import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;
import com.example.burro.demo.appbiz.test.TestContract.*;
import com.example.burro.demo.appframework.util.LogUtils;
import com.example.burro.demo.appframework.util.StringUtils;
import com.example.burro.demo.databiz.model.test.MovieListBean;
import com.example.burro.demo.databiz.service.ApiService;
import com.example.burro.demo.dataframework.http.HttpConfig;
import java.util.HashMap;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**測試presenter
* Created by ex.zhong on 2017/9/23.
*/
public class TestPresenterImpl extends BasePresenter<View> implements Presenter {
@Override
public void getMovieListData(int start, int count) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HttpConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
ApiService movieService = retrofit.create(ApiService.class);
HashMap<String,String> map=new HashMap<>();
map.put("start", StringUtils.getString(start));
map.put("count",StringUtils.getString(count));
movieService.getMovieListData(map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieListBean>() {
@Override
public void onStart() {
//請求開始
LogUtils.i("TestPresenterImpl","onStart()");
}
@Override
public void onCompleted() {
// //請求完成
LogUtils.i("TestPresenterImpl","onCompleted()");
}
@Override
public void onError(Throwable e) {
// //請求異常
LogUtils.i("TestPresenterImpl","onError()");
}
@Override
public void onNext(MovieListBean movieListBean) {
// //請求OK屏镊,執(zhí)行
LogUtils.i("TestPresenterImpl","onNext()");
mView.setMovieListData(movieListBean);
}
});
}
}
文中用到的豆瓣電影TOP250的URL為:http://api.douban.com/v2/movie/top250?start=1&count=15
其他幾個主要輔助的類或資源分別為如下:
布局文件activity_test.xml:
<RelativeLayout 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.burro.demo.appbiz.test.view.TestActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<Button
android:id="@+id/btnTest"
android:layout_below="@id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="請求數(shù)據(jù)"
/>
</RelativeLayout>
其中AppTheme.PopupOver,AppTheme.AppBarOverlay等是toolbar相關(guān)的樣式資源依疼,請到demo中查看,此處不一一列出
ApiService接口類 databiz/service/ApiService:
package com.example.burro.demo.databiz.service;
import com.example.burro.demo.databiz.model.test.MovieListBean;
import java.util.HashMap;
import java.util.Map;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
import rx.Observable;
/**
* 存放訪問網(wǎng)絡(luò)的方法
* Created by ex.zhong on 2017/9/24.
*/
public interface ApiService {
public static final String URL_MOVIELIST="/v2/movie/top250"; //豆瓣電影top250
@GET(URL_MOVIELIST)
Observable<MovieListBean> getMovieListData(@QueryMap HashMap<String,String> count);
}
網(wǎng)絡(luò)配置類 dataframework/http/HttpConfig :
package com.example.burro.demo.dataframework.http;
/**
* Created by ex.zhong on 2017/9/24.
*放置網(wǎng)絡(luò)相關(guān)配置數(shù)據(jù)而芥,如IP/端口等
*/
public class HttpConfig {
public final static String BASE_URL="http://api.douban.com";
}
電影列表實體類 databiz/model/test/MovieListBean :
package com.example.burro.demo.databiz.model.test;
import com.example.burro.demo.dataframework.model.BaseResultBean;
import java.util.List;
/**豆瓣電影列表
* Created by ex.zhong on 2017/9/24.
*/
public class MovieListBean extends BaseResultBean{
public List<SubjectsBean> subjects;
public static class SubjectsBean {
/**
* rating : {"max":10,"average":9.6,"stars":"50","min":0}
* genres : ["犯罪","劇情"]
* title : 肖申克的救贖
* casts : [{"alt":"https://movie.douban.com/celebrity/1054521/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/17525.jpg","large":"https://img3.doubanio.com/img/celebrity/large/17525.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/17525.jpg"},"name":"蒂姆·羅賓斯","id":"1054521"},{"alt":"https://movie.douban.com/celebrity/1054534/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/34642.jpg","large":"https://img3.doubanio.com/img/celebrity/large/34642.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/34642.jpg"},"name":"摩根·弗里曼","id":"1054534"},{"alt":"https://movie.douban.com/celebrity/1041179/","avatars":{"small":"https://img1.doubanio.com/img/celebrity/small/5837.jpg","large":"https://img1.doubanio.com/img/celebrity/large/5837.jpg","medium":"https://img1.doubanio.com/img/celebrity/medium/5837.jpg"},"name":"鮑勃·岡頓","id":"1041179"}]
* collect_count : 1107705
* original_title : The Shawshank Redemption
* subtype : movie
* directors : [{"alt":"https://movie.douban.com/celebrity/1047973/","avatars":{"small":"https://img3.doubanio.com/img/celebrity/small/230.jpg","large":"https://img3.doubanio.com/img/celebrity/large/230.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/230.jpg"},"name":"弗蘭克·德拉邦特","id":"1047973"}]
* year : 1994
* images : {"small":"https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.webp","large":"https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p480747492.webp","medium":"https://img3.doubanio.com/view/movie_poster_cover/spst/public/p480747492.webp"}
* alt : https://movie.douban.com/subject/1292052/
* id : 1292052
*/
public RatingBean rating;
public String title;
public int collect_count;
public String original_title;
public String subtype;
public String year;
public ImagesBean images;
public String alt;
public String id;
public List<String> genres;
public List<CastsBean> casts;
public List<DirectorsBean> directors;
public static class RatingBean {
/**
* max : 10
* average : 9.6
* stars : 50
* min : 0
*/
public int max;
public double average;
public String stars;
public int min;
}
public static class ImagesBean {
/**
* small : https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.webp
* large : https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p480747492.webp
* medium : https://img3.doubanio.com/view/movie_poster_cover/spst/public/p480747492.webp
*/
public String small;
public String large;
public String medium;
}
public static class CastsBean {
/**
* alt : https://movie.douban.com/celebrity/1054521/
* avatars : {"small":"https://img3.doubanio.com/img/celebrity/small/17525.jpg","large":"https://img3.doubanio.com/img/celebrity/large/17525.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/17525.jpg"}
* name : 蒂姆·羅賓斯
* id : 1054521
*/
public String alt;
public AvatarsBean avatars;
public String name;
public String id;
public static class AvatarsBean {
/**
* small : https://img3.doubanio.com/img/celebrity/small/17525.jpg
* large : https://img3.doubanio.com/img/celebrity/large/17525.jpg
* medium : https://img3.doubanio.com/img/celebrity/medium/17525.jpg
*/
public String small;
public String large;
public String medium;
}
}
public static class DirectorsBean {
/**
* alt : https://movie.douban.com/celebrity/1047973/
* avatars : {"small":"https://img3.doubanio.com/img/celebrity/small/230.jpg","large":"https://img3.doubanio.com/img/celebrity/large/230.jpg","medium":"https://img3.doubanio.com/img/celebrity/medium/230.jpg"}
* name : 弗蘭克·德拉邦特
* id : 1047973
*/
public String alt;
public AvatarsBeanX avatars;
public String name;
public String id;
public static class AvatarsBeanX {
/**
* small : https://img3.doubanio.com/img/celebrity/small/230.jpg
* large : https://img3.doubanio.com/img/celebrity/large/230.jpg
* medium : https://img3.doubanio.com/img/celebrity/medium/230.jpg
*/
public String small;
public String large;
public String medium;
}
}
}
}
點擊請求數(shù)據(jù)按鈕律罢。獲取到返回的數(shù)據(jù)如下:
當(dāng)然,TestPresenterImpl中的內(nèi)容是重點棍丐,其中g(shù)etMovieListData()方法里的內(nèi)容是retrofit和rxjava最基本的用法误辑!想必大家多少都見過。我們再來看下rxjava相關(guān)的代碼歌逢,其實它主要做了三個事情稀余。統(tǒng)一管理主線程、工作線程趋翻、請求返回后的回調(diào)處理睛琳!引入rxjava之前,三者都是自己管理踏烙。所以說师骗,它的引入極大的簡化了我們的工作。
下一篇將講述優(yōu)化封裝