前言
上一篇文章簡單的說了RxJava痛倚,Retrofit,MVP的簡單用法丰刊,這篇我們繼續(xù)上一篇結(jié)尾時說的幾個需要優(yōu)化的地方(封裝),先回顧下之前說的幾個需要優(yōu)化地方:
- 每次請求都寫一串代碼創(chuàng)建Retrofit
- 每次訂閱
Observable
時都有一大串重寫的方法增拥,然而我們只關(guān)心請求成功與否和請求成功后返回的數(shù)據(jù) - 每次都需要設(shè)置
Obserable
和Observer
在哪個線程里工作 - 如果請求未完成但是界面卻退出了就會存在內(nèi)存泄漏的風(fēng)險
- 每次請求添加公共參數(shù)(例如設(shè)備號啄巧,版本信息等等)
先來看下全部封裝完的項目結(jié)構(gòu):
-
base:
放一些基類寻歧,例如:BaseView,BaseModel秩仆,BaseObserver等等码泛。 -
bean:
放實體類。 -
callback:
回調(diào)接口澄耍。 -
model:
M層噪珊。 -
presenter:
P層。 -
ui:
放置Activity,Fragment等UI界面齐莲。 -
view:
V層接口痢站。
下面開始一步步進行封裝:
Retrofit2封裝
創(chuàng)建RetrofitHelper類,主要用于對Retrofit進行初始化选酗,以及每次實例化時都需要寫一串配置信息阵难,以及每次請求時的公共參數(shù)添加
下面直接貼上代碼,都寫上了注釋:
public class RetrofitHelper {
private static OkHttpClient mOkHttpClient;
private RetrofitHelper() {
throw new UnsupportedOperationException("u can't init me");
}
static {
initOkHttpClient();
}
//創(chuàng)建一個請求
public static Api getBoobApi() {
return createApi(Api.class, "https://api.douban.com/");
}
/**
* 根據(jù)傳入的baseUrl芒填,和api創(chuàng)建retrofit
*/
private static <T> T createApi(Class<T> clazz, String baseUrl) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl) //這個一般是接口地址(一定要以/結(jié)尾)
.client(mOkHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //集成rxjava
.addConverterFactory(GsonConverterFactory.create()) //gson解析
.build();
return retrofit.create(clazz);
}
/**
* 初始化OKHttpClient,設(shè)置超時時間,設(shè)置打印日志,設(shè)置Request攔截器
*/
private static void initOkHttpClient() {
//打印所有okhttp請求日志
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
if (mOkHttpClient == null) {
synchronized (RetrofitHelper.class) {
if (mOkHttpClient == null) {
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)//添加網(wǎng)絡(luò)log攔截器
.addInterceptor(new RequestInterceptor())//添加網(wǎng)絡(luò)請求攔截器
.retryOnConnectionFailure(true) //當(dāng)失敗時重復(fù)請求
.connectTimeout(30, TimeUnit.SECONDS)//連接超時時間
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
}
}
}
}
private static class RequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.method().equals("POST")) {
//這里分兩種post提交方式
// FormBoy:以表單方式提交 MultipartBody:是有多個參數(shù)(其中包括文件)
if (request.body() instanceof FormBody) {
request = addPostFormParams(request);
} else if (request.body() instanceof MultipartBody) {
request = addPostMultiParams(request);
}
} else if (request.method().equals("GET")) {
request = addGetParams(chain);
}
return chain.proceed(request);
}
}
//post附帶圖片時
private static Request addPostMultiParams(Request request) {
// 添加公共參數(shù)
MultipartBody.Builder builder = new MultipartBody.Builder().addFormDataPart("deviceId", "123456");
MultipartBody oldBody = (MultipartBody) request.body();
for (int i = 0; i < oldBody.size(); i++) {
builder.addPart(oldBody.part(i));
}
oldBody = builder.build();
request = request.newBuilder().post(oldBody).build();
return request;
}
//post參數(shù)時
private static Request addPostFormParams(Request request) {
FormBody.Builder bodyBuilder = new FormBody.Builder();
FormBody formBody = (FormBody) request.body();
//把原來的參數(shù)添加到新的構(gòu)造器呜叫,(因為沒找到直接添加,所以就new新的)
for (int i = 0; i < formBody.size(); i++) {
bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
}
//添加公共參數(shù)
formBody = bodyBuilder
.addEncoded("deviceId", "123456").build();
request = request.newBuilder().post(formBody).build();
return request;
}
private static Request addGetParams(Interceptor.Chain chain) {
Request request;
Request oldRequest = chain.request();
// 添加公共參數(shù)
HttpUrl authorizedUrlBuilder = oldRequest.url()
.newBuilder()
.addQueryParameter("deviceId", "123456").build();
request = oldRequest.newBuilder()
.url(authorizedUrlBuilder)
.build();
return request;
}
}
以上便是對Retrofit的封裝殿衰。
對Observer和實體類封裝
創(chuàng)建BaseObserver類朱庆,主要是用來對返回的實體類進行處理。
創(chuàng)建BaseBaseResult類闷祥,對返回的數(shù)據(jù)處理(這里的處理是對一些返回的公共部分做一個提取椎工,下面會有圖片說明)
以此Demo為例,請求https://api.douban.com/v2/book/search?q=西游記
時返回數(shù)據(jù)類型如下圖:
這里面公共的參數(shù)就是count蜀踏,start维蒙,total,books果覆,所以我們只需要對這些參數(shù)進行封裝(還有參數(shù)不全颅痊,參數(shù)錯誤等情況去請求服務(wù)器,服務(wù)器返回的錯誤code等等)局待,這里我們只對books感興趣斑响,那么我們只需要對count判斷就能得出是否搜索到書籍。
下面我們看看這個BaseResult代碼:
public class BaseResult<T> {
private int count;
private int start;
private int total;
private T books;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public T getBooks() {
return books;
}
public void setBooks(T books) {
this.books = books;
}
}
注(大坑):這里的books
是返回的json數(shù)組的名字钳榨,而不是固定死的(之前用別人的data
舰罚,然而發(fā)現(xiàn)一直是null,原來是gson解析時沒找到這個json字段的名字)
封裝完BaseResult那么就要來封裝Observer了薛耻,畢竟數(shù)據(jù)是在Observer里面回調(diào)的营罢,真正對數(shù)據(jù)處理的是Observer這個大佬,數(shù)據(jù)到了這想要走,還不得一頓判斷饲漾,以此為例蝙搔,當(dāng)count為0的時候說明沒有搜索到那么我們需要處理下返回提示信息,當(dāng)count>0時我們把搜索到的書籍返回考传,當(dāng)搜索出錯時走到onError
時我們也要返回個提示信息吃型,當(dāng)然這里還能做許多操作(加些提示框,加載框等等)這里就不一一實現(xiàn)了僚楞。
看下BaseObserver代碼:
public abstract class BaseObserver<T> implements Observer<BaseResult<T>> {
@Override
public void onSubscribe(@NonNull Disposable d) {
//訂閱剛開始的時候
}
@Override
public void onNext(@NonNull BaseResult<T> tBaseResult) {
//這里面就用BaseResult包裹著實體類
if (tBaseResult.getCount() > 0) {
handleData(tBaseResult.getBooks());
} else {
errorMsg("未搜索到");
}
}
@Override
public void onError(@NonNull Throwable e) {
if (e instanceof ConnectException)
errorMsg("無網(wǎng)絡(luò)");
errorMsg(e.getMessage());
}
@Override
public void onComplete() {
//完成的時候
}
//處理過的數(shù)據(jù)
public abstract void handleData(T t);
//錯誤信息
public abstract void errorMsg(String msg);
}
當(dāng)然請求的接口也改變了(其實也就在外面套了個BaseResult)
public interface Api {
@GET("v2/book/search")
Observable<BaseResult<List<Book>>> searchBook(@Query("q") String bookName);
}
線程調(diào)度
每次請求時候的時候都需要
.subscribeOn(Schedulers.io())
和.observeOn(AndroidSchedulers.mainThread())
這樣不是很麻煩勤晚?特別是在Android中只能在主線程更新UI這個規(guī)矩,那么數(shù)據(jù)返回要在主線程中咯泉褐,這樣我們就封裝下兩個就不用每次都寫了赐写。
代碼如下:
public class BaseRx {
private BaseRx() {
throw new RuntimeException("u con't instantiate me");
}
public static <T> ObservableTransformer<T, T> io4main() {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(@NonNull Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io());
}
};
}
}
這樣我們?nèi)绻怯玫皆贗O線程中操作,在主線程中取數(shù)據(jù)兴枯,就可以用到此工具類血淌,只需要RxJava的操作符compose
就可以了(下面有完整例子)。
下面我們把封裝好的全部來跑一遍
RetrofitHelper.getBoobApi().searchBook(mEtBook.getText().toString().trim())
.compose(BaseRx.<BaseResult<List<Book>>>io4main()) //使用compose轉(zhuǎn)換線程
.subscribe(new BaseObserver<List<Book>>() {
@Override
public void handleData(List<Book> books) {
for (int i = 0; i < books.size(); i++) {
if(!TextUtils.isEmpty(books.get(i).getPublisher())){
mStringBuilder.append(books.get(i).getPublisher() + "\n");
}
mBook.setText(mStringBuilder);
}
}
@Override
public void errorMsg(String msg) {
mBook.setText(msg);
}
});
運行下效果:
是不是感覺封裝完看著都很爽财剖,代碼少悠夯,邏輯清晰,當(dāng)然這還沒有結(jié)合MVP一起使用躺坟,結(jié)合起來也是一大殺器沦补,擼碼猶如神助,好啦咪橙,下次再說與MVP封裝夕膀,明天又是周末,美滋滋美侦。