Retrofit2.0+RxJava2.0
大家都知道Okhttp3用著已經很順手了,Retrofit則是在OkHttp3的基礎上,對網絡請求做了更加系統、簡潔的封裝,使用面向接口的方式進行網絡請求,它使用動態生成的代理類封裝了接口,而且使用很多注解提供功能。RxJava就是一種用Java語言實現的響應式編程,是一個基于事件訂閱的異步執行的一個類庫,核心思想是觀察者模式。
其它文章
Android MVP模式簡單使用和封裝使用
OkHttp3簡單使用和封裝使用
Android開發 多語言、指紋登錄、手勢登錄
Android使用IconFont阿里矢量圖標
Android Studio 使用SVN 主干和分支合并代碼
Retrofit、RxJava基礎
如果你還不了解Retrofit、RxJava的話,請先查看一些基礎文章,我也是這么過來的。
效果圖
項目地址:https://github.com/pengjunshan/Retrofit2RxJava2
配置
- gradle中引入相關庫 (相關庫請到github上查看最新版本)
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
-
自動下載Okhttp3和okio資源
Libraries資源.png - 權限 (這里只是demo所用到的權限,實際根據項目所用配置)
<!--聯網權限-->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--檢測網絡狀態權限-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 在SDCard中創建與刪除文件權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions"/>
<!-- 往SDCard寫入數據權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 從SDCard讀取數據權限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
先看下代碼調用和日志
案例中分別寫了封裝之前和封裝之后調的使用,封裝后比封裝前代碼少了不止一點兩點,但是該有的流程一步也不少只不過封裝起來沒有看到而已。
封裝之前簡單使用分為4步完成
- 創建Retrofit
- 構建接口
- 構建被觀察者對象
- 訂閱
/**
* 封裝之前
* 簡單使用
*/
public void RetrofitRxJavaRequet(View view) {
//第一步:創建Retrofit
Retrofit mRetrofit = new Builder()
.baseUrl(Constants.BASEURL)//添加BaseUrl
.client(MyApplication.mOkHttpClient)//添加OkhttpClient
.addConverterFactory(GsonConverterFactory.create())//添加Gson解析
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())// 添加 rxjava 支持
.build();
//第二步:構建接口
IApiService iApiService = mRetrofit.create(IApiService.class);
//第三步:構建被觀察者對象
Observable<BaseBean<List<BannerBean>>> observable = iApiService.getBanner();
//第四步:訂閱
observable.subscribeOn(Schedulers.io())//指定Observable自身在io線程中執行
.observeOn(AndroidSchedulers.mainThread())//指定一個觀察者在主線程中國觀察這個Observable
.subscribe(new Observer<BaseBean<List<BannerBean>>>() {
@Override
public void onSubscribe(Disposable d) {
Log.e("TAG", "開始之前");
}
@Override
public void onNext(BaseBean<List<BannerBean>> listBaseBean) {
Toast.makeText(MainActivity.this, listBaseBean.getData().toString(), Toast.LENGTH_SHORT).show();
Log.e("TAG", "成功");
}
@Override
public void onError(Throwable e) {
Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
Log.e("TAG", "失敗");
}
@Override
public void onComplete() {
Log.e("TAG", "結束");
}
});
}
/**
* 封裝后使用
* GET請求
*/
public void GetRequet(View view) {
RetrofitRequest.getBannerApi(context, new IResponseCallBack<BaseBean<List<BannerBean>>>() {
@Override
public void onSuccess(BaseBean<List<BannerBean>> data) {
Toast.makeText(MainActivity.this, "banner成功="+data.getData().toString(), Toast.LENGTH_SHORT).show();
Log.e("TAG", "banner內容="+data.getData().toString());
}
@Override
public void onFail(OkHttpException failuer) {
Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
Log.e("TAG", "失敗="+failuer.getEmsg());
}
});
}
/**
* 封裝后使用
* POST請求
*/
public void PostKeyValueRequet(View view) {
Map<String,String> map = new ArrayMap<>();
map.put("username", "15294792877");
map.put("password", "15294792877pp");
RetrofitRequest.postLoginApi(context, map, new IResponseCallBack<BaseBean<LoginBean>>() {
@Override
public void onSuccess(BaseBean<LoginBean> data) {
Toast.makeText(MainActivity.this, "登錄成功="+data.getData().toString(), Toast.LENGTH_SHORT).show();
Log.e("TAG", "登錄成功="+data.getData().toString());
}
@Override
public void onFail(OkHttpException failuer) {
Toast.makeText(MainActivity.this, "失敗", Toast.LENGTH_SHORT).show();
Log.e("TAG", "失敗="+failuer.getEmsg());
}
});
}
/**
* 封裝后使用
* 下載圖片
*/
public void GetImgRequet(View view) {
RetrofitRequest.downImgApi(String.valueOf(System.currentTimeMillis()) + ".png",
new IResponseByteCallBack() {
@Override
public void onSuccess(File file) {
Toast.makeText(MainActivity.this, "圖片下載成功="+file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
Log.e("TAG", "圖片下載成功="+file.getAbsolutePath());
}
@Override
public void onFailure(String failureMsg) {
Toast.makeText(MainActivity.this, "圖片下載失敗", Toast.LENGTH_SHORT).show();
Log.e("TAG", "圖片下載失敗="+failureMsg);
}
});
}
封裝
創建RetrofitClient
為我們的Retrofit配置參數,使用靜態語句塊來配置,只執行一次,運行一開始就開辟了內存,內存放在全局。設置baseUrl、添加OkHttpClient、添加Gson解析、添加 rxjava 支持
request方法是一個公共請求方法接收一個被觀察者(Observable)
subscribeOn(Schedulers.io())指定Observable自身在io線程中執行,
observeOn(AndroidSchedulers.mainThread())指定一個觀察者在主線程中國觀察這個Observable,
subscribe(new CallBackObserver<T>(listener, context));訂閱
/**
* @author:PengJunShan.
* 時間:On 2019-05-06.
* 描述:Retrofit對象
*/
public class RetrofitClient {
public static Retrofit mRetrofit;
/**
* 為我們的Client配置參數,使用靜態語句塊來配置
* 只執行一次,運行一開始就開辟了內存,內存放在全局
*/
static {
mRetrofit = new Retrofit.Builder()
.baseUrl(Constants.BASEURL)//添加BaseUrl
.client(MyApplication.mOkHttpClient)//添加OkhttpClient
.addConverterFactory(GsonConverterFactory.create())//添加Gson解析
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())// 添加 rxjava 支持
.build();
}
/**
* 所有請求都放在一個接口中
*/
public static IApiService createApi() {
return mRetrofit.create(IApiService.class);
}
/**
* 不同請求放不同接口中
* @param service 指定接口
*/
public static <T> T createApi(Class<T> service) {
return mRetrofit.create(service);
}
/**
* @param context 上下文
* @param observable 被觀察者
* @param listener 回調接口
* @param requstName 接口名稱
* @param <T> 實體類
*/
public static <T> void request(Context context, Observable<T> observable,
final IResponseCallBack<T> listener, String requstName) {
Constants.requestName = requstName;
if (!Utils.isConnected(MyApplication.context)) {
if (listener != null) {
listener.onFail(new OkHttpException(-1, "網絡不可用,請檢查網絡"));
}
return;
}
observable.subscribeOn(Schedulers.io())//指定Observable自身在io線程中執行
.observeOn(AndroidSchedulers.mainThread())//指定一個觀察者在主線程中國觀察這個Observable
.subscribe(new CallBackObserver<T>(listener, context));//訂閱
}
/**
* 統一下載圖片共用
* @param observable 被觀察者
* @param callback 回調接口
* @param imgPath 存儲地址
* @param requstName 功能名稱
*/
public static void downImg(Observable<ResponseBody> observable,
final IResponseByteCallBack callback, final String imgPath, String requstName) {
Constants.requestName = requstName;
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ResponseBody responseBody) {
File file = null;
try {
InputStream is = responseBody.byteStream();
int len = 0;
// 文件夾路徑
String pathUrl =
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
+ "/PJS/";
File filepath = new File(pathUrl);
if (!filepath.exists()) {
filepath.mkdirs();// 創建文件夾
}
file = new File(pathUrl, imgPath);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[2048];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
is.close();
callback.onSuccess(file);
} catch (final Exception e) {
callback.onFailure(e.getMessage());
}
}
@Override
public void onError(Throwable e) {
callback.onFailure(e.getMessage());
}
@Override
public void onComplete() {
}
});
}
}
初始化OkHttpClient
前面介紹已經說過了Retrofit是對OkHttp3進行的封裝,所以網絡請求核心還是OkHttp3,需要對OkHttp3進行初始化配置。
/**
* 作者:PengJunShan.
* 時間:On 2019-05-06.
* 描述:初始化OkHttpClient 當然也可以在RetrofitCLient類中初始化
*/
public class MyApplication extends Application {
public static Context context;
public static OkHttpClient mOkHttpClient;
/**
* 超時時間
*/
private static final int TIME_OUT = 30;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
initOkHttp();
}
private void initOkHttp() {
//獲取緩存路徑
File cacheDir = MyApplication.context.getExternalCacheDir();
//設置緩存的大小
int cacheSize = 10 * 1024 * 1024;
//創建我們Client對象的構建者
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder
//為構建者設置超時時間
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
////websocket輪訓間隔(單位:秒)
.pingInterval(20, TimeUnit.SECONDS)
//設置緩存
.cache(new Cache(cacheDir.getAbsoluteFile(), cacheSize))
//允許重定向
.followRedirects(true)
//設置攔截器
.addInterceptor(new RequetInterceptor())
//添加https支持
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
})
.sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());
mOkHttpClient = okHttpBuilder.build();
}
}
公共Observer 觀察者
創建一個公用的Observer來處理請求結果后進行回調,在回調數據之前先判斷當前Activity是否還存在。在onSubscribe()方法里可以進行網絡加載圈,onComplete()方法里可以取消網絡加載圈,把錯誤交給ExceptionHandle類處理。
/**
* @author:PengJunShan.
*
* 時間:On 2019-05-06.
*
* 描述:公共Observer 觀察者
*/
public class CallBackObserver<T> implements Observer<T> {
private IResponseCallBack<T> mListener;
private Activity activity;
public CallBackObserver(IResponseCallBack<T> listener, Context context) {
this.mListener = listener;
this.activity = (Activity) context;
}
@Override
public void onSubscribe(Disposable d) {
/**
* 這里可以 顯示加載圈等
*/
}
/**
* 成功
* @param data
*/
@Override
public void onNext(T data) {
if (mListener != null && !activity.isFinishing()) {
BaseBean baseBean = (BaseBean) data;
/**
* 是否成功
*/
if (baseBean.isSuccess()) {
mListener.onSuccess(data);
}else {
mListener.onFail(new OkHttpException(baseBean.getErrorCode(), baseBean.getErrorMsg()));
}
}
}
/**
* 失敗
* @param e
*/
@Override
public void onError(Throwable e) {
onComplete();
if (mListener != null && !activity.isFinishing()) {
/**
* 處理失敗原因
*/
OkHttpException okHttpException = ExceptionHandle.handleException(e,activity);
if(okHttpException !=null) {
mListener.onFail(okHttpException);
}
}
}
@Override
public void onComplete() {
/**
* 這里可以 關閉加載圈等
*/
}
}
ExceptionHandle(自定義錯誤解析類)
除了網絡錯誤之外,我們還要解析前端和后端定義的錯誤碼,比如常見的token失效錯誤,我們抓取錯誤碼來進行重新登錄獲取新的token值。
/**
* @author:PengJunShan.
* 時間:On 2019-05-06.
* 描述:自定義錯誤解析類
*/
public class ExceptionHandle {
private static final int TIMEOUT_ERROR = -1;
private static final int JSON_ERROR = -2;
private static final int NETWORK_ERROR = -3;
private static final int OTHER_ERROR = -4;
private static final String TIMEOUTMSG = "請求超時";
private static final String JSONMSG = "解析錯誤";
private static final String NETWORKMSG = "連接失敗";
private static final String OTHERMSG = "未知錯誤";
/**
* 根據接口定義 錯誤碼等于999 為token失效 需要重新登錄獲取新的token
*/
private static final int TOKENLOGIN = 999;
public static OkHttpException handleException(Throwable e, Activity activity) {
OkHttpException ex = null;
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Gson gson = new GsonBuilder().serializeNulls().create();
String jsonStr = body.string();
BaseBean baseBean = gson.fromJson(jsonStr, BaseBean.class);
/**
* token失效 重新登錄
*/
if (baseBean.getErrorCode() == TOKENLOGIN ) {
// activity.startActivity(LoginActivity.class);
// activity.finish();
} else {
ex = new OkHttpException(baseBean.getErrorCode(), baseBean.getErrorMsg());
}
} catch (IOException e1) {
e1.printStackTrace();
}
return ex;
} else if (e instanceof java.net.SocketTimeoutException) {
ex = new OkHttpException(TIMEOUT_ERROR, TIMEOUTMSG);
return ex;
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
ex = new OkHttpException(JSON_ERROR, JSONMSG);
return ex;
} else if (e instanceof ConnectException) {
ex = new OkHttpException(NETWORK_ERROR, NETWORKMSG);
return ex;
} else {
ex = new OkHttpException(OTHER_ERROR, OTHERMSG);
return ex;
}
}
}
/**
* @author:PengJunShan.
* 時間:On 2019-05-06.
* 描述:自定義異常類,返回ecode,emsg到業務層
*/
public class OkHttpException extends Exception {
private static final long serialVersionUID = 1L;
private int ecode;
private String emsg;
public OkHttpException(int ecode, String emsg) {
this.ecode = ecode;
this.emsg = emsg;
}
public int getEcode() {
return ecode;
}
public String getEmsg() {
return emsg;
}
}
IResponseCallBack<T>(回調接口)
Retrofit支持Gson解析實體類,請求成功后把實體類我們自定義的接口回調。失敗的話就回調護理過的錯誤實體類。
/**
* @author:PengJunShan.
* 時間:On 2019-05-06.
* 描述:自定義回調接口
*/
public interface IResponseCallBack<T> {
void onSuccess(T data);
void onFail(OkHttpException failuer);
}
RequetInterceptor(攔截器)
攔截器的作用還是很大的,一般我們工作中請求頭部都會傳入token、用戶id標識認證參數。連接器中可以攔截到Request對象,然后添加頭部公共參數。通過獲取FormBody可以打印出入參參數,通過獲取Response可以打印出出參參數。不能直接使用response.body().string()的方式輸出日志,因為response.body().string()之后,response中的流會被關閉,我們需要創建出一個新的response給應用層處理。
/**
* @author:PengJunShan.
* 時間:On 2019-05-05.
* 描述:日志攔截器
*/
public class RequetInterceptor implements Interceptor {
/**
* 這個chain里面包含了request和response,所以你要什么都可以從這里拿
*/
@Override
public Response intercept(Chain chain) throws IOException {
/**
* 可以添加公共頭部參數如token
*/
Request request = chain.request()
.newBuilder()
// .header("TOKEN", token)
// .header("ID", id)
.build();
/**
* 開始時間
*/
long startTime = System.currentTimeMillis();
Log.e("TAG","\n"+"requestUrl=" + request.url());
String method = request.method();
if ("POST".equals(method)) {
try {
JSONObject jsonObject = new JSONObject();
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
for (int i = 0; i < body.size(); i++) {
jsonObject.put(body.encodedName(i), body.encodedValue(i));
}
Log.e("TAG","入參JSON= " + jsonObject.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
Response response = chain.proceed(request);
/**
* 這里不能直接使用response.body().string()的方式輸出日志
* 因為response.body().string()之后,response中的流會被關閉,程序會報錯,我們需要創建出一個新的response給應用層處理
*/
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.e("TAG","出參JSON=" + responseBody.string());
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
Log.e("TAG","----------" + Constants.requestName + "耗時:" + duration + "毫秒----------");
return response;
}
}
結束
網上有很多別人封裝好的庫,但是符不符合自己的項目使用就是另外一回事了。還不如自己花點時間封裝一個既簡單又符合自己項目使用的代碼,文章中貼出來的代碼是核心代碼并不是所有的代碼,下載代碼根據自己項目需求修改一下就可以用的。下篇我要寫一個以MVP+retrofit2+rxjava2進行網絡請求封裝,主要是講MVP模式。
項目地址:https://github.com/pengjunshan/Retrofit2RxJava2
拿到代碼后移到自己項目中根據自己項目需求修改即可使用。