配置Retrofit網路框架及其使用
**
1.配置service介面
網路請求需要哪些資訊?
**
一般網路請求,會需要如下這些資訊:
請求的網址
請求方式;是GET請求,還是POST請求
請求引數
引數傳遞方式;是通過表單方式傳遞,還是通過JSON方式傳遞
請求頭
如何配置?
將這些資訊寫到一個介面中。
建立Model
/**
* 歌單詳情包裹物件
* <p>
* 只是用來測試
*/
public class SheetDetailWrapper {
/**
* 歌單詳情
*/
private Sheet data;
public Sheet getData() {
return data;
}
public void setData(Sheet data) {
this.data = data;
}
}
/**
* 歌單物件
*/
public class Sheet {
/**
* 歌單Id
*/
private String id;//這個用字串型別,防止以後id變為字串了,不好搞
/**
* 歌單標題
*/
private String title;
/**
* 歌單封面
*/
private String banner;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBanner() {
return banner;
}
public void setBanner(String banner) {
this.banner = banner;
}
}
{
"data": {
"id": 1,
"title": "這世上所有的歌zheshishangtest",
"banner": "assets/list1.jpg",
"description": "這是因為iOS9引入了新特性App Transport Security (ATS),他要求App內網路請求必須使用HTTPS協議。解決方法是要麼改為HTTPS,要麼宣告可以使用HTTP,可以宣告部分使用HTTP,也可以所有;但需要說明的是如果APP內所有請求都是HTTP,那麼如果要上架App Store的時候基本都會被拒。",
"clicks_count": 16773,
"collections_count": 28,
"comments_count": 172,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
},
"songs": [
{
"id": 11,
"title": "忘不了的溫柔",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"uri": "assets/wangbiliaodewenrou_andongyang.mp3",
"clicks_count": 0,
"comments_count": 0,
"created_at": "2019-09-17T05:52:50.000Z",
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg"
},
"singer": {
"id": 43,
"nickname": "安東陽",
"avatar": "assets/andongyang.jpg"
}
},
{
"id": 10,
"title": "傷心的站臺",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"uri": "assets/shangxingzhantai_andongyang.mp3",
"clicks_count": 0,
"comments_count": 0,
"created_at": "2019-09-17T05:50:50.000Z",
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg"
},
"singer": {
"id": 43,
"nickname": "安東陽",
"avatar": "assets/andongyang.jpg"
}
}
]
}
建立Service介面
我們希望將專案中,所有請求的資訊都放到Service介面中,名稱可以隨便寫,每個方法,就代表一個介面,定義是歌單詳情,只是我們這裡用這種命名方式。
/**
* 網路介面配置
* <p>
* 之所以呼叫介面還能返回資料
* 是因為Retrofit框架內部處理了
* 這裡不講解原理
* 在《詳解Retrofit》課程中講解
*/
public interface Service {
/**
* 歌單詳情
*
* @param id {id} 這樣表示id,表示的@Path("id")裡面的id,
* path裡面的id其實就是接收後面String id 的值
* <p>
* 一般情況下,三個名稱都寫成一樣,比如3個都是id
* <p>
* //Retrofit如何知道我們傳入的是id呢,其實通過Retrofit註解@Path("id")知道
* (應該是相等於限定了id,其他的應該會報錯)
* <p>
* Observable<SheetDetailWrapper>:相等於把json資料轉換成這個SheetDetailWrapper型別的物件
* Observable:rxjava裡面的類
*/
@GET("v1/sheets/{id}")
Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id);
}
如果需要更多介面,只需要在這裡新增就行了;這裡暫時只新增了一個介面,目的是後面封裝網路框架的時候會用到,其他的介面,用到了在新增。
2.如何使用Retrofit請求網路
//測試網路請求
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder();
//構建者模式
//初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
//讓Retrofit使用okhttp
.client(okhttpClientBuilder.build())
//api地址
.baseUrl(Constant.ENDPOINT)//這裡使用的是地址的公共字首
//適配Rxjava(就是所返回的物件以Rxjava這種方式來工作(比如我們使用了Observable這種方式,介面Service檢視))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//使用gson解析json
//包括請求引數和響應
// (比如使用Retrofit請求框架請求資料,傳送物件,也會轉換成json(使用gson轉換))
.addConverterFactory(GsonConverterFactory.create())
//建立Retrofit
.build();
//建立Service
Service service = retrofit.create(Service.class);
//請求歌單詳情
service.sheetDetail("1")
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread())//在主執行緒觀察(操作UI在主執行緒)
//介面方法裡面對應的物件:SheetDetailWrapper
.subscribe(new Observer<SheetDetailWrapper>() {//訂閱回來的資料
@Override
public void onSubscribe(Disposable d) {
}
/**
* 請求成功
*
* @param sheetDetailWrapper 解析回來的物件
*/
@Override
public void onNext(SheetDetailWrapper sheetDetailWrapper) {
LogUtil.d(TAG, "request sheet detail success:" + sheetDetailWrapper.getData().getTitle());
}
/**
* 請求失敗
*
* @param e Error
*/
@Override
public void onError(Throwable e) {
e.printStackTrace();
// LogUtil.d(TAG,"request sheet detail failed:" + e.getLocalizedMessage());
}
/**
* 請求結束
*/
@Override
public void onComplete() {
}
});
測試
執行專案,點選按鈕,就可以在日誌中檢視到歌單為1的JSON資料了。
2.2 網路請求錯誤 處理
程式碼演示:
在onError方法可以判斷,這樣太麻煩,後面會封裝
//判斷錯誤型別
if (e instanceof UnknownHostException) {
ToastUtil.errorShortToast(R.string.error_network_unknown_host);
} else if (e instanceof ConnectException) {
ToastUtil.errorShortToast(R.string.error_network_connect);
} else if (e instanceof SocketTimeoutException) {
ToastUtil.errorShortToast(R.string.error_network_timeout);
} else if (e instanceof HttpException) {
HttpException exception = (HttpException) e;
int code = exception.code();
if (code == 401) {
ToastUtil.errorShortToast(R.string.error_network_not_auth);
} else if (code == 403) {
ToastUtil.errorShortToast(R.string.error_network_not_permission);
} else if (code == 404) {
ToastUtil.errorShortToast(R.string.error_network_not_found);
} else if (code >= 500) {
ToastUtil.errorShortToast(R.string.error_network_server);
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
測試錯誤(404和500)
因為我們上面程式碼判斷了,所以手機上報錯404和500會爆出相應的提示。
為了方便,我們電腦上先檢視下網路狀態,然後再 執行到手機app上檢視網路狀態
如果要404錯誤,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就變成404 (把URL地址更改為一個不存在的地址;就會提示“你訪問內容不存在!”。)
如果要500錯誤,只要使用者名稱不存在就會變成500錯誤(使用者名稱不存在就會報500錯誤)
比如:
http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111這個地址就是500錯誤
開啟這個檢查介面,然後輸入網址就可以捕獲到網路狀態
測試無網路
我們這裡使用的是模擬器,所以關閉電腦WiFi就可以模擬;如果是真實手機,也可以關閉WiFi;測試會發現,關閉網路,會產生一個UnknownHostException異常,所以在這裡程式碼判斷就行了。
2.3 封裝
1. 封裝網路請求Api
可以把初始化okhttp,初始化retrofit,還要建立Service放到一個單獨的類中,然後把這個類,實現為單例,因為前面的這些初始化,只需要執行一次就行了。
Api 類
public class Api {
/**
* Api單例欄位
*/
private static Api instance;
/**
* Service單例
*/
private final Service service;
/**
* 返回當前物件的唯一例項
* <p>
* 單例設計模式
* 由於移動端很少有高併發
* 所以這個就是簡單判斷
*
* @return 本類單例
*/
public static Api getInstance() {
if (instance == null) {
instance = new Api();
}
return instance;
}
/**
* 私有構造方法
*/
private Api() {
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder();
//構建者模式
//初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
//讓Retrofit使用okhttp
.client(okhttpClientBuilder.build())
//api地址
.baseUrl(Constant.ENDPOINT)//這裡使用的是地址的公共字首
//適配Rxjava(就是所返回的物件以Rxjava這種方式來工作(比如我們使用了Observable這種方式,介面Service檢視))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//使用gson解析json
//包括請求引數和響應
// (比如使用Retrofit請求框架請求資料,傳送物件,也會轉換成json(使用gson轉換))
.addConverterFactory(GsonConverterFactory.create())
//建立Retrofit
.build();
//建立Service
service = retrofit.create(Service.class);
}
/**
* 歌單詳情
*
* @param id 傳入的第幾個歌曲Id
* @return 返回Retrofit介面例項 裡面的方法返回的物件
*/
public Observable<SheetDetailWrapper> sheetDetail(String id) {
return service.sheetDetail(id)
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread());//在主執行緒觀察(操作UI在主執行緒)
}
2. 使用
把之前的service去掉,然後用這個Api物件呼叫裡面的方法即可
//請求歌單詳情
// service.sheetDetail("1")
Api.getInstance().sheetDetail("1")
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread())//在主執行緒觀察(操作UI在主執行緒)
//介面方法裡面對應的物件:SheetDetailWrapper
.subscribe(new Observer<SheetDetailWrapper>() {//訂閱回來的資料
@Override
public void onSubscribe(Disposable d) {
}
/**
* 請求成功
*
* @param sheetDetailWrapper 解析回來的物件
*/
@Override
public void onNext(SheetDetailWrapper sheetDetailWrapper) {
LogUtil.d(TAG, "request sheet detail success:" + sheetDetailWrapper.getData().getTitle());
}
/**
* 請求失敗
*
* @param e Error
*/
@Override
public void onError(Throwable e) {
e.printStackTrace();
// LogUtil.d(TAG,"request sheet detail failed:" + e.getLocalizedMessage());
//判斷錯誤型別
if (e instanceof UnknownHostException) {
ToastUtil.errorShortToast(R.string.error_network_unknown_host);
} else if (e instanceof ConnectException) {
ToastUtil.errorShortToast(R.string.error_network_connect);
} else if (e instanceof SocketTimeoutException) {
ToastUtil.errorShortToast(R.string.error_network_timeout);
} else if (e instanceof HttpException) {
HttpException exception = (HttpException) e;
int code = exception.code();
if (code == 401) {
ToastUtil.errorShortToast(R.string.error_network_not_auth);
} else if (code == 403) {
ToastUtil.errorShortToast(R.string.error_network_not_permission);
} else if (code == 404) {
ToastUtil.errorShortToast(R.string.error_network_not_found);
} else if (code >= 500) {
ToastUtil.errorShortToast(R.string.error_network_server);
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
}
/**
* 請求結束
*/
@Override
public void onComplete() {
}
});
2.4 載入對話方塊
public class LoadingUtil {
private static ProgressDialog progressDialog;
/**
* 使用一個載入對話方塊,使用預設提示文字
*
* @param activity Activity
*/
public static void showLoading(Activity activity) {
showLoading(activity, activity.getString(R.string.loading));
}
/**
* 顯示一個載入對話方塊(可以輸入任何的message)
*
* @param activity Activity
* @param message Message
*/
private static void showLoading(Activity activity, String message) {
//判斷activity為空或者已經銷燬了
if (activity == null || activity.isFinishing()) {
return;
}
//判斷是否顯示了
if (progressDialog != null) {
//已經顯示了 不需要再次顯示
//就不再顯示了
return;
}
//建立一個進度對話方塊
progressDialog = new ProgressDialog(activity);
progressDialog.setTitle("提示");//提示標題
progressDialog.setMessage(message);//提示資訊
//點選外部彈窗不會自動隱藏
progressDialog.setCancelable(false);
progressDialog.show();
}
/**
* 隱藏載入提示對話方塊
*/
public static void hideLoading() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.hide();
progressDialog = null;
}
}
}
如何使用
//測試載入提示框
LoadingUtil.showLoading(getMainActivity());
//3秒中隱藏載入提示框
//因顯示後無法點選後面的按鈕(也就是當前頁面點選的3s後關閉,在另一個頁面關閉麻煩)
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
LoadingUtil.hideLoading();
}
}, 3000);
完成網路請求載入顯示
前面已經學習了RxJava的回撥方法,所以可以在onSubscribe方法顯示載入提示;
在onNext和onError方法中隱藏載入提示。
//請求歌單詳情
// service.sheetDetail("1")
Api.getInstance().sheetDetail("1")
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread())//在主執行緒觀察(操作UI在主執行緒)
//介面方法裡面對應的物件:SheetDetailWrapper
.subscribe(new Observer<SheetDetailWrapper>() {//訂閱回來的資料
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: ");
//顯示載入對話方塊
LoadingUtil.showLoading(getMainActivity());
}
/**
* 請求成功
*
* @param sheetDetailWrapper 解析回來的物件
*/
@Override
public void onNext(SheetDetailWrapper sheetDetailWrapper) {
LogUtil.d(TAG, "onNext:" + sheetDetailWrapper.getData().getTitle());
LoadingUtil.hideLoading();//隱藏載入提示框
}
/**
* 請求失敗
*
* @param e Error
*/
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError: ");
LoadingUtil.hideLoading();//隱藏載入提示框
}
}
測試
確認請求網路能顯示,並隱藏。
2.5 請求資料
2.5.1 json資料分析
{
"data": [
{
"id": 1,
"title": "這世上所有的歌zheshishangtest",
"banner": "assets/list1.jpg",
"clicks_count": 16795,
"collections_count": 28,
"comments_count": 172,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 2,
"title": "我向來做事十拿九不穩 不信可以試試woxianglaitest",
"banner": "assets/list2.jpg",
"clicks_count": 2099,
"collections_count": 11,
"comments_count": 2,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 3,
"title": "網路離別歌曲最後還是離開了wangllibietest",
"banner": "assets/list3.jpg",
"clicks_count": 4433,
"collections_count": 10,
"comments_count": 0,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 9,
"title": "傷心的人怎可願意聽慢歌",
"banner": "assets/yuanfengredehuo_andongyang.jpg",
"clicks_count": 620,
"collections_count": 7,
"comments_count": 4,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 10,
"title": "你開始懂得了歌詞",
"banner": "assets/shengburusi.jpg",
"clicks_count": 772,
"collections_count": 9,
"comments_count": 0,
"user": {
"id": 1,
"nickname": "愛學啊dev666",
"avatar": "67133b479b364e8c9bfceb58015cd71f.jpg",
"gender": 0
}
},
{
"id": 92,
"title": "2020",
"banner": null,
"clicks_count": 15,
"collections_count": 0,
"comments_count": 0,
"user": {
"id": 622,
"nickname": "凌鍇",
"avatar": null,
"gender": 0
}
},
{
"id": 91,
"title": "最好聽的歌單",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 120,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "樂天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 90,
"title": "2020年度金曲",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 41,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "樂天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 89,
"title": "最好聽的粵語歌",
"banner": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3489003925,2338759571&fm=26&gp=0.jpg",
"clicks_count": 29,
"collections_count": 1,
"comments_count": 0,
"user": {
"id": 516,
"nickname": "樂天gg",
"avatar": "7f71228986a340a8830cb778d2c9037b.jpg",
"gender": 10
}
},
{
"id": 88,
"title": "sfsfdx",
"banner": null,
"clicks_count": 20,
"collections_count": 0,
"comments_count": 0,
"user": {
"id": 118,
"nickname": "阿健",
"avatar": "95a189d10eb94da4b795585af183c774.jpg",
"gender": 10
}
}
]
}
分析:
可以當成一個大物件(也就是外圍是一個類),然後物件裡面的成員變數又是一個物件的話,按照物件的思維去解析;否則直接在大物件的類裡面直接新增一個成員變數即可。
我們先摺疊json資料
我們可以建立一個類
public class SheetListWrapper {
}
依次張開
這個data是個陣列(當做集合處理),這個大物件裡面的data成員變數是一個集合。
那集合裡面的item又是一個物件,所以我們又得定義一個item物件Sheet(這個之前定義的)
//public class Sheet extends BaseModel {
public class Sheet extends BaseMultiItemEntity {
//id可以刪除了,因為已經定義到父類了
// /**
// * 歌單Id
// */
// private String id;//這個用字串型別,防止以後id變為字串了,不好搞
/**
* 歌單標題
*/
private String title;
/**
* 歌單封面
*/
private String banner;
/**
* 描述
*/
private String description;
}
我們依次展開
可以看到
所以最總的
這裡記得把set get方法加上
public class SheetListWrapper {
/**
* 歌單列表
*/
private List<Sheet> data;
public List<Sheet> getData() {
return data;
}
public void setData(List<Sheet> data) {
this.data = data;
}
}
請求單個item資料的model 方法同上。
這裡順便附上程式碼
/**
* 歌單詳情包裹物件
* <p>
* 只是用來測試
*/
public class SheetDetailWrapper {
/**
* 歌單詳情
*/
private Sheet data;
//這裡返回的是單個歌單Sheet,//SheetListWrapper那邊返回的是多個歌單,也就是list,
public Sheet getData() {
return data;
}
public void setData(Sheet data) {
this.data = data;
}
}
2.5.2 請求資料並簡單使用
1.建立模型資料,前面已經分析了
/**
* 歌單詳情包裹物件
* <p>
* 只是用來測試
*/
//這裡類這裡還沒有用到,先寫上 這個是請求單個歌單詳情時候用到的model類
public class SheetDetailWrapper {
/**
* 歌單詳情
*/
private Sheet data;
//這裡返回的是單個歌單Sheet,//SheetListWrapper那邊返回的是多個歌單,也就是list,
public Sheet getData() {
return data;
}
public void setData(Sheet data) {
this.data = data;
}
}
/**
* 歌單列表模型
* <p>
* 只是用來測試
* <p>
* 地址:http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets
* 後面沒有數字,有數字的是具體某個歌單
*/
public class SheetListWrapper {
/**
* 歌單列表
*/
private List<Sheet> data;
public List<Sheet> getData() {
return data;
}
public void setData(List<Sheet> data) {
this.data = data;
}
}
item裡面的model類
//public class Sheet extends BaseModel {
public class Sheet extends BaseMultiItemEntity {
//id可以刪除了,因為已經定義到父類了
// /**
// * 歌單Id
// */
// private String id;//這個用字串型別,防止以後id變為字串了,不好搞
/**
* 歌單標題
*/
private String title;
/**
* 歌單封面
*/
private String banner;
/**
* 描述
*/
private String description;
}
2.介面配置
/**
* 網路介面配置
* <p>
* 之所以呼叫介面還能返回資料
* 是因為Retrofit框架內部處理了
* 這裡不講解原理
* 在《詳解Retrofit》課程中講解
*/
public interface Service {
/**
* 歌單列表
*/
@GET("v1/sheets")
Observable<SheetListWrapper> sheets();
}
Api類
public class Api {
....
/**
* 歌單列表
*
* @return 返回Observable<SheetListWrapper>
*/
public Observable<SheetListWrapper> sheets() {
return service.sheets()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
3.使用
//請求歌單列表
Api.getInstance()
.sheets()
.subscribe(new Observer<SheetListWrapper>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(SheetListWrapper sheetListWrapper) {
LogUtil.d(TAG, "onNext:" + sheetListWrapper.getData().size());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
2.5.3 封裝請求資料Model
前面我們也說了,專案中所有的網路,請求最外層都有一層包裝,真實的資料在data裡面,如果是詳情,data就是一個物件,如果是列表,data就是一個陣列;前面的解析歌單詳情的時候,還要給歌單詳情外面建立一個包裝類,那如果其他物件也按照這種方式實現的話,第一個是要建立很多的包裝類,同時外面的包裝類是重複的,所以說,可以建立一個通用的包裝類,通過泛型的方式指定裡面的內容
1.如何實現封裝
前面看到每個網路請求,最外層都有可能有,message,status兩個欄位,他們是必要的時候才有,還有一個data欄位,只是不同的介面,型別不一樣;所以我們可以建立一個BaseResponse。
建立BaseResponse
/**
* 通用網路請求響應模型
* <p>
* 前面看到每個網路請求,最外層都有可能有,message,status兩個欄位,他們是必要的時候才有,
* 還有一個data欄位,只是不同的介面,型別不一樣;所以我們可以建立一個BaseResponse。
*/
public class BaseResponse {
/**
* 狀態碼
* <p>
* 只有發生了錯誤才會有
* <p>
* 如果用int型別的話,全域性變數會預設初始化,這個值就預設為0了
* 而我們不想為0,讓發生了錯誤的時候才會值(不想有值,出錯了才有值)
* 所以我們這裡定義為引用型別Integer,就會預設初始化為null
*/
private Integer status;
/**
* 出錯的提示資訊
* <p>
* 發生了錯誤不一定有
*/
private String message;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
建立詳情物件
建立一個DetailResponse,它用來解析詳情這類網路請求。
/**
* 詳情網路請求解析類
* <p>
* 繼承BaseResponse
* 定義了一個泛型T
*/
public class DetailResponse<T> extends BaseResponse {
/**
* 真實資料
* 他的型別就是泛型
*/
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
前面建立了BaseResponse,也實現了詳情網路請求的包裝,那對應列表請求來說,其實也只有data不一樣,所以可以建立一個ListResponse。
/**
* 解析列表網路請求
*/
public class ListResponse<T> extends BaseResponse {
/**
* 定義一個列表
* <p>
* 裡面的物件使用了泛型
*/
private List<T> data;//名字要和伺服器端的一樣
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
}
2.使用封裝後請求
2.1單個物件詳情使用
統一將裡面的model類改成 DetailResponse
* <p>
* 之所以呼叫介面還能返回資料
* 是因為Retrofit框架內部處理了
* 這裡不講解原理
* 在《詳解Retrofit》課程中講解
*/
public interface Service {
/**
* 歌單詳情
*
* @param id {id} 這樣表示id,表示的@Path("id")裡面的id,
* path裡面的id其實就是接收後面String id 的值
* <p>
* 一般情況下,三個名稱都寫成一樣,比如3個都是id
* <p>
* //Retrofit如何知道我們傳入的是id呢,其實通過Retrofit註解@Path("id")知道
* (應該是相等於限定了id,其他的應該會報錯)
* <p>
* Observable<SheetDetailWrapper>:相等於把json資料轉換成這個SheetDetailWrapper型別的物件
* Observable:rxjava裡面的類
*/
@GET("v1/sheets/{id}")
// Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id);
Observable<DetailResponse<Sheet>> sheetDetail(@Path("id") String id);
}
/**
* 歌單詳情
*
* @param id 傳入的第幾個歌曲Id
* @return 返回Retrofit介面例項 裡面的方法返回的物件
*/
public Observable<DetailResponse<Sheet>> sheetDetail(String id) {
return service.sheetDetail(id)
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread());//在主執行緒觀察(操作UI在主執行緒)
}
//請求DetailResponse歌單詳情(封裝介面後使用)
Api.getInstance().sheetDetail("1")
.subscribe(new Observer<DetailResponse<Sheet>>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(DetailResponse<Sheet> sheetDetailResponse) {
LogUtil.d(TAG, "onNext:" + sheetDetailResponse.getData().getTitle());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
2.2 列表使用
類似的,使用也把Model類改成 ListResponse 即可。
最後還要測試能正常的執行。
2.5.4 封裝網路請求回撥
如果大家不知道從和下手,還是按照我們前面的方法,從使用的位置,也就是我們期望封裝完成後的效果開始。
請求網路的時候先通過Api的getInstance方法,獲取到Api類,然後呼叫相應的方法,他返回的是Observable物件,然後重寫onSucceeded只關注成功,通過onFailed關注失敗。
1.實現通用Observer回撥
可以看到現在使用Observer的時候,都需要實現全部方法,這樣每次使用的時候比較麻煩,所以可以借鑑Java中的設計,就是給介面建立一個預設實現類,這個類只是簡單的實現這些方法。
/**
* 通用實現Observer裡面的方法
* <p>
* 目的是避免要實現所有方法
*/
public class ObserverAdapter<T> implements Observer<T> {
/**
* 開始訂閱了執行(可以簡單理解為開始執行前)
*
* @param d Disposable物件
*/
@Override
public void onSubscribe(Disposable d) {
}
/**
* 下一個Observer(當前Observer執行完成了)
*
* @param t 具體的物件或者集合
*/
@Override
public void onNext(T t) {
}
/**
* 發生了錯誤(執行失敗了)
*
* @param e Throwable物件
*/
@Override
public void onError(Throwable e) {
}
/**
* 回撥了onNext方法後呼叫
*/
@Override
public void onComplete() {
}
}
//使用ObserverAdapter
Api.getInstance().sheetDetail("1")
.subscribe(new ObserverAdapter<DetailResponse<Sheet>>() {
@Override
public void onNext(DetailResponse<Sheet> sheetDetailResponse) {
super.onNext(sheetDetailResponse);
LogUtil.d(TAG, "onNext:" + sheetDetailResponse.getData().getTitle());
}
});
可以看到只需要重寫需要的方法就行了。
最後確保能正確的執行
2.實現HttpObserver回撥
後面我們要實現自動網路錯誤處理,而這部分邏輯放到ObserverAdapter不太好,所以我們建立HttpObserver。
/**
* 網路請求Observer
* <p>
* 有些錯誤不方便放在ObserverAdapter處理,所以定義了HttpObserver類這個是繼承ObserverAdapter<T>的
* <p>
* 本類有成功與失敗方法
*/
public abstract class HttpObserver<T> extends ObserverAdapter<T> {
private static final String TAG = "HttpObserver";
/**
* 請求成功
*
* @param data 資料(物件或者集合)
* Succeeded:success 後面的2個s改成ed
* 改成抽象類,讓子類實現
*/
public abstract void onSucceeded(T data);
/**
* 請求失敗
*
* @param data 資料(物件或者集合) 比如傳入進來的:DetailResponse<Sheet>模型物件類
* @param e Throwable
* @return boolean
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
//TODO 處理錯誤
//請求正常
onSucceeded(t);
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
//TODO 處理錯誤
}
}
使用:
//使用HttpObserver
Api.getInstance().sheetDetail("1")
.subscribe(new HttpObserver<DetailResponse<Sheet>>() {
@Override
public void onSucceeded(DetailResponse<Sheet> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle());
}
});
確保能執行成功
2.2.加強(新增錯誤處理)
HttpObserver類
package com.ixuea.courses.mymusicold.listener;
import android.text.TextUtils;
import com.ixuea.courses.mymusicold.R;
import com.ixuea.courses.mymusicold.domain.response.BaseResponse;
import com.ixuea.courses.mymusicold.util.LogUtil;
import com.ixuea.courses.mymusicold.util.ToastUtil;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import retrofit2.HttpException;
/**
* 網路請求Observer
* <p>
* 有些錯誤不方便放在ObserverAdapter處理,所以定義了HttpObserver類這個是繼承ObserverAdapter<T>的
* <p>
* 本類有成功與失敗方法
*/
public abstract class HttpObserver<T> extends ObserverAdapter<T> {
private static final String TAG = "HttpObserver";
/**
* 請求成功
*
* @param data 資料(物件或者集合)
* Succeeded:success 後面的2個s改成ed
* 改成抽象類,讓子類實現
*/
public abstract void onSucceeded(T data);
/**
* 請求失敗
*
* @param data 資料(物件或者集合)
* @param e Throwable
* @return boolean
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
/**
* 如果要404錯誤,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就變成404
* 如果要500錯誤,只要使用者名稱不存在就會變成500錯誤
* 比如:
* http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111這個地址就是500錯誤
* <p>
* 總結:發生錯誤都會發生在onError中
*
* @param t 具體的物件或者集合
*/
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
/**
* 已經請求成功,但是登入失敗了
* 但是如果使用者名稱 密碼錯誤會返回false
*
* 可以理解為200~299之間的值就會返回到這裡來
* 這裡面的錯誤,可以先看看,到時候遇到再說
*/
if (isSucceeded(t)) {
//請求正常
onSucceeded(t);
} else {
//請求出錯了(就是登入失敗了)
requestErrorHandler(t, null);
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
requestErrorHandler(null, e);//第一個引數為null,出錯了,沒有資料物件傳遞到這個方法裡面來
}
/**
* 判斷網路請求是否成功
*
* @param t T
* @return 是否成功
*/
private boolean isSucceeded(T t) {
//比如返回的code=200,(比如使用者名稱 密碼錯誤)
if (t instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse baseResponse = (BaseResponse) t;
//沒有狀態碼錶示成功
//這是我們和服務端的一個規定(一般情況下status等於0才是成功,我們這裡是null才成功)
//一般==null,則return true;否則return false
return baseResponse.getStatus() == null;
}
return false;
}
/**
* 處理錯誤網路請求
*
* @param data T 資料模型物件
* @param error Throwable錯誤物件
*/
private void requestErrorHandler(T data, Throwable error) {
if (error != null) {
//判斷錯誤型別
if (error instanceof UnknownHostException) {
ToastUtil.errorShortToast(R.string.error_network_unknown_host);
} else if (error instanceof ConnectException) {
ToastUtil.errorShortToast(R.string.error_network_connect);
} else if (error instanceof SocketTimeoutException) {
ToastUtil.errorShortToast(R.string.error_network_timeout);
} else if (error instanceof HttpException) {
HttpException exception = (HttpException) error;
int code = exception.code();
if (code == 401) {
ToastUtil.errorShortToast(R.string.error_network_not_auth);
} else if (code == 403) {
ToastUtil.errorShortToast(R.string.error_network_not_permission);
} else if (code == 404) {
ToastUtil.errorShortToast(R.string.error_network_not_found);
} else if (code >= 500) {
ToastUtil.errorShortToast(R.string.error_network_server);
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {//error為null(這個時候是走onNext-->else-->requestErrorHandler)
//(登入失敗的這種錯誤)
if (data instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse response = (BaseResponse) data;
if (TextUtils.isEmpty(response.getMessage())) {
//沒有錯誤提示資訊(message可能沒有錯誤提示資訊) (未知錯誤,請稍後再試!)
ToastUtil.errorShortToast(R.string.error_network_unknown);
} else {//message不為空
ToastUtil.errorShortToast(response.getMessage());
}
}
}
}
}
2.2.1使用(程式碼實現404和500錯誤,看看是否會彈出錯誤提示)
public interface Service {
/**
* 歌單詳情
*
* @param id {id} 這樣表示id,表示的@Path("id")裡面的id,
* path裡面的id其實就是接收後面String id 的值
* <p>
* 一般情況下,三個名稱都寫成一樣,比如3個都是id
* <p>
* //Retrofit如何知道我們傳入的是id呢,其實通過Retrofit註解@Path("id")知道
* (應該是相等於限定了id,其他的應該會報錯)
* <p>
* Observable<SheetDetailWrapper>:相等於把json資料轉換成這個SheetDetailWrapper型別的物件
* Observable:rxjava裡面的類
*/
// @GET("v1/sheets11111111/{id}")//404
@GET("v1/sheets/{id}")
// Observable<SheetDetailWrapper> sheetDetail(@Path("id") String id);
Observable<DetailResponse<Sheet>> sheetDetail(@Path("id") String id);
//模擬500(也就是)
/**
* 使用者詳情
* //後面的查詢引數會自動新增到後面的
* http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111
* 比如這裡嗎的 問號?和引數nickname=11111111會新增到後面
* 因為這裡有個引數 @QueryMap Map<String,String> data
*/
@GET("v1/users/{id}")
Observable<DetailResponse<User>> userDetail(@Path("id") String id, @QueryMap Map<String, String> data);
}
上面Service介面中用到的User模型類,目前還沒有內容,因為我們這裡是測試錯誤。‘’
/**
* 使用者詳情
*/
public class User {
}
API類中的
/**
* 歌單詳情
*
* @param id 傳入的第幾個歌曲Id
* @return 返回Retrofit介面例項 裡面的方法返回的物件
*/
public Observable<DetailResponse<Sheet>> sheetDetail(String id) {
return service.sheetDetail(id)
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread());//在主執行緒觀察(操作UI在主執行緒)
}
/**
* 歌單詳情
*
* @param id 傳入的第幾個歌曲Id
* @return 返回Retrofit介面例項 裡面的方法返回的物件
*/
public Observable<DetailResponse<User>> userDetail(String id, String nickname) {
//新增查詢引數
HashMap<String, String> data = new HashMap<>();
if (StringUtils.isNotBlank(nickname)) {
//如果暱稱不為空才新增
// nickname=11111111 鍵Constant.NICKNAME對應nickname; 值nickname對應11111111
data.put(Constant.NICKNAME, nickname);
}
return service.userDetail(id, data)
.subscribeOn(Schedulers.io())//在子執行緒執行
.observeOn(AndroidSchedulers.mainThread());//在主執行緒觀察(操作UI在主執行緒)
}
在登入按鈕 點選事件裡面 測試
// //使用HttpObserver 和 404
// Api.getInstance().sheetDetail("1")
// .subscribe(new HttpObserver<DetailResponse<Sheet>>() {
// @Override
// public void onSucceeded(DetailResponse<Sheet> data) {
// LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle());
// }
// });
//模擬500錯誤 使用者名稱錯誤
Api.getInstance().userDetail("-1", "1111111111")
.subscribe(new HttpObserver<DetailResponse<User>>() {
@Override
public void onSucceeded(DetailResponse<User> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData());
}
});
最後確保能正確的執行。
2.3.手動處理錯誤(HttpObserver新增程式碼)
有些時候,我們可能希望自定義錯誤處理,而現在預設是,出錯了就在父類處理了。
如何實現?
可以使用onFailed方法的返回值來實現,可以這樣,如果返回true表示子類處理錯誤,如果返回false父類處理錯誤。
//模擬500錯誤 使用者名稱錯誤
Api.getInstance().userDetail("-1", "1111111111")
.subscribe(new HttpObserver<DetailResponse<User>>() {
@Override
public void onSucceeded(DetailResponse<User> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData());
}
@Override
public boolean onFailed(DetailResponse<User> data, Throwable e) {
LogUtil.d(TAG, "onFailed:" + e);
// return super.onFailed(data, e);//呼叫父類,內部處理錯誤
//return true 表示:手動處理錯誤
return true;//外部處理,(就是說內部的那個提示沒有彈出來)
}
});
HttpObserver 類
/**
* 網路請求Observer
* <p>
* 有些錯誤不方便放在ObserverAdapter處理,所以定義了HttpObserver類這個是繼承ObserverAdapter<T>的
* <p>
* 本類有成功與失敗方法
*/
public abstract class HttpObserver<T> extends ObserverAdapter<T> {
private static final String TAG = "HttpObserver";
/**
* 請求成功
*
* @param data 資料(物件或者集合)
* Succeeded:success 後面的2個s改成ed
* 改成抽象類,讓子類實現
*/
public abstract void onSucceeded(T data);
/**
* 請求失敗
*
* @param data 資料(物件或者集合)
* @param e Throwable
* @return boolean false :表示父類(本類)要處理錯誤(內部處理);true:表示子類要處理錯誤(外部處理)
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
/**
* 如果要404錯誤,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就變成404
* 如果要500錯誤,只要使用者名稱不存在就會變成500錯誤
* 比如:
* http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111這個地址就是500錯誤
* <p>
* 總結:發生錯誤都會發生在onError中
*
*
* 已經請求成功,但是登入失敗了
* 但是如果使用者名稱 密碼錯誤會返回false
*
* 可以理解為200~299之間的值就會返回到這裡來
* 這裡面的錯誤,可以先看看,到時候遇到再說
* @param t 具體的物件或者集合
*/
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
if (isSucceeded(t)) {
//請求正常
onSucceeded(t);
} else {
//請求出錯了(就是登入失敗了)
requestErrorHandler(t, null);
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
requestErrorHandler(null, e);//第一個引數為null,出錯了,沒有資料物件傳遞到這個方法裡面來
}
/**
* 判斷網路請求是否成功
*
* @param t T
* @return 是否成功
*/
private boolean isSucceeded(T t) {
//比如返回的code=200,(比如使用者名稱 密碼錯誤)
if (t instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse baseResponse = (BaseResponse) t;
//沒有狀態碼錶示成功
//這是我們和服務端的一個規定(一般情況下status等於0才是成功,我們這裡是null才成功)
//一般==null,則return true;否則return false
return baseResponse.getStatus() == null;
}
return false;
}
/**
* 處理錯誤網路請求
*
* @param data T 資料模型物件
* @param error Throwable錯誤物件
*/
private void requestErrorHandler(T data, Throwable error) {
if (onFailed(data, error)) {//fasle就會走else,父類(可以說本類)處理錯誤;true:就是外部處理錯誤
//回撥了請求失敗方法
//並且該方法返回了true
//返回true就表示外部手動處理錯誤
//那我們框架內部就不用做任何事情了
} else {
if (error != null) {
//判斷錯誤型別
if (error instanceof UnknownHostException) {
ToastUtil.errorShortToast(R.string.error_network_unknown_host);
} else if (error instanceof ConnectException) {
ToastUtil.errorShortToast(R.string.error_network_connect);
} else if (error instanceof SocketTimeoutException) {
ToastUtil.errorShortToast(R.string.error_network_timeout);
} else if (error instanceof HttpException) {
HttpException exception = (HttpException) error;
int code = exception.code();
if (code == 401) {
ToastUtil.errorShortToast(R.string.error_network_not_auth);
} else if (code == 403) {
ToastUtil.errorShortToast(R.string.error_network_not_permission);
} else if (code == 404) {
ToastUtil.errorShortToast(R.string.error_network_not_found);
} else if (code >= 500) {
ToastUtil.errorShortToast(R.string.error_network_server);
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {//error為null(這個時候是走onNext-->else-->requestErrorHandler)
//(登入失敗的這種錯誤)
if (data instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse response = (BaseResponse) data;
if (TextUtils.isEmpty(response.getMessage())) {
//沒有錯誤提示資訊(message可能沒有錯誤提示資訊) (未知錯誤,請稍後再試!)
ToastUtil.errorShortToast(R.string.error_network_unknown);
} else {//message不為空
ToastUtil.errorShortToast(response.getMessage());
}
}
}
}
}
}
3.重構錯誤到工具類(HttpUtil)
HttpUtil 類
/**
* 網路請求相關輔助方法
*/
public class HttpUtil {
/**
* 網路請求錯誤處理
*
* @param data Object
* @param error Throwable
* 這個static後面的<T>是必須要的,否則引數那裡會找不到這個泛型T
* 也可以不用泛型T,直接用Object
*/
// public static <T> void handlerRequest(T data, Throwable error) {
public static void handlerRequest(Object data, Throwable error) {
if (error != null) {
//判斷錯誤型別
if (error instanceof UnknownHostException) {
ToastUtil.errorShortToast(R.string.error_network_unknown_host);
} else if (error instanceof ConnectException) {
ToastUtil.errorShortToast(R.string.error_network_connect);
} else if (error instanceof SocketTimeoutException) {
ToastUtil.errorShortToast(R.string.error_network_timeout);
} else if (error instanceof HttpException) {
HttpException exception = (HttpException) error;
int code = exception.code();
if (code == 401) {
ToastUtil.errorShortToast(R.string.error_network_not_auth);
} else if (code == 403) {
ToastUtil.errorShortToast(R.string.error_network_not_permission);
} else if (code == 404) {
ToastUtil.errorShortToast(R.string.error_network_not_found);
} else if (code >= 500) {
ToastUtil.errorShortToast(R.string.error_network_server);
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {
ToastUtil.errorShortToast(R.string.error_network_unknown);
}
} else {//error為null(這個時候是走onNext-->else-->requestErrorHandler)
//(登入失敗的這種錯誤)
if (data instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse response = (BaseResponse) data;
if (TextUtils.isEmpty(response.getMessage())) {
//沒有錯誤提示資訊(message可能沒有錯誤提示資訊) (未知錯誤,請稍後再試!)
ToastUtil.errorShortToast(R.string.error_network_unknown);
} else {//message不為空
ToastUtil.errorShortToast(response.getMessage());
}
}
}
}
}
在原來的HTTPObserver上 改:
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
if (isSucceeded(t)) {
//請求正常
onSucceeded(t);
} else {
//請求出錯了(就是登入失敗了)
handlerRequest(t, null);
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
handlerRequest(null, e);//第一個引數為null,出錯了,沒有資料物件傳遞到這個方法裡面來
}
/**
* 處理錯誤網路請求
*
* @param data T 資料模型物件
* @param error Throwable錯誤物件
*/
private void handlerRequest(T data, Throwable error) {
if (onFailed(data, error)) {//fasle就會走else,父類(可以說本類)處理錯誤;true:就是外部處理錯誤
//回撥了請求失敗方法
//並且該方法返回了true
//返回true就表示外部手動處理錯誤
//那我們框架內部就不用做任何事情了
} else {
//呼叫工具處理錯誤(這個是父類,內部處理錯誤)
HttpUtil.handlerRequest(data, error);
}
}
子類這裡我們也呼叫它的工具類HttpUtil來處理錯誤。
注意:這樣的話,處理錯誤的邏輯是和父類一樣的(因為都封裝到HTTPUtil這個工具類裡面了),但是這裡我們還是返回true,呼叫下HTTPUtil類方法。
//模擬500錯誤 使用者名稱錯誤
Api.getInstance().userDetail("-1", "1111111111")
.subscribe(new HttpObserver<DetailResponse<User>>() {
@Override
public void onSucceeded(DetailResponse<User> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData());
}
@Override
public boolean onFailed(DetailResponse<User> data, Throwable e) {
LogUtil.d(TAG, "onFailed:" + e);
// return super.onFailed(data, e);//呼叫父類,內部處理錯誤
//return true 表示:手動處理錯誤
//呼叫工具類處理錯誤(這個時候會有提示彈出來)
HttpUtil.handlerRequest(data, e);
return true;//外部處理,(就是說內部的那個提示沒有彈出來)
}
});
4.網路請求載入對話方塊顯示和隱藏(網路請求時顯示,成功和失敗都隱藏)
在HttpObserver的構造方法中新增一個引數控制是否顯示對話方塊
HttpObserver類
package com.ixuea.courses.mymusicold.listener;
import com.ixuea.courses.mymusicold.activity.BaseCommonActivity;
import com.ixuea.courses.mymusicold.domain.response.BaseResponse;
import com.ixuea.courses.mymusicold.util.HttpUtil;
import com.ixuea.courses.mymusicold.util.LoadingUtil;
import com.ixuea.courses.mymusicold.util.LogUtil;
import io.reactivex.disposables.Disposable;
/**
* 網路請求Observer
* <p>
* 有些錯誤不方便放在ObserverAdapter處理,所以定義了HttpObserver類這個是繼承ObserverAdapter<T>的
* <p>
* 本類有成功與失敗方法
*/
public abstract class HttpObserver<T> extends ObserverAdapter<T> {
private static final String TAG = "HttpObserver";
//final 修飾的成員變數,必須要初始化一次,而這裡有個空構造方法,有可能沒有初始化成員變數,
// 所以final修飾的成員變數在編譯的時候可能沒有初始化,故報錯
// private final BaseCommonActivity activity;
// private final boolean isShowLoading;
private BaseCommonActivity activity;//公共Activity
private boolean isShowLoading;//是否顯示載入對話方塊
/**
* 無參構造方法 新增這個的主要目的是:父類中有個有參構造方法(這時父類沒有無參構造方法,子類在new無參構造的時候就會報錯,所以這裡要新增無參構造方法)
*/
public HttpObserver() {
}
/**
* 有參構造方法
*
* @param activity BaseCommonActivity
* @param isShowLoading 是否顯示載入提示框
*/
public HttpObserver(BaseCommonActivity activity, boolean isShowLoading) {
this.activity = activity;
this.isShowLoading = isShowLoading;
}
/**
* 請求成功
*
* @param data 資料(物件或者集合)
* Succeeded:success 後面的2個s改成ed
* 改成抽象類,讓子類實現
*/
public abstract void onSucceeded(T data);
/**
* 請求失敗
*
* @param data 資料(物件或者集合)
* @param e Throwable
* @return boolean false :表示父類(本類)要處理錯誤(內部處理);true:表示子類要處理錯誤(外部處理)
*/
public boolean onFailed(T data, Throwable e) {
return false;
}
@Override
public void onSubscribe(Disposable d) {
super.onSubscribe(d);
if (isShowLoading) {
//顯示載入對話方塊
LoadingUtil.showLoading(activity);
}
}
/**
* 如果要404錯誤,只需要把http://dev-my-cloud-music-api-rails.ixuea.com/v1/sheets中的sheets改下,就變成404
* 如果要500錯誤,只要使用者名稱不存在就會變成500錯誤
* 比如:
* http://dev-my-cloud-music-api-rails.ixuea.com/v1/users/-1?nickname=11111111這個地址就是500錯誤
* <p>
* 總結:發生錯誤都會發生在onError中
*
*
* 已經請求成功,但是登入失敗了
* 但是如果使用者名稱 密碼錯誤會返回false
*
* 可以理解為200~299之間的值就會返回到這裡來
* 這裡面的錯誤,可以先看看,到時候遇到再說
* @param t 具體的物件或者集合
*/
@Override
public void onNext(T t) {
super.onNext(t);
LogUtil.d(TAG, "onNext:" + t);
//檢查是否需要隱藏載入提示框(其他地方如onError中用到,提取到一個方法中)
checkHideLoading();
if (isSucceeded(t)) {
//請求正常
onSucceeded(t);
} else {
//請求出錯了(就是登入失敗了)
handlerRequest(t, null);
}
}
@Override
public void onError(Throwable e) {
super.onError(e);
LogUtil.d(TAG, "onError:" + e.getLocalizedMessage());
//檢查是否需要隱藏載入提示框
checkHideLoading();
handlerRequest(null, e);//第一個引數為null,出錯了,沒有資料物件傳遞到這個方法裡面來
}
/**
* 判斷網路請求是否成功
*
* @param t T
* @return 是否成功
*/
private boolean isSucceeded(T t) {
//比如返回的code=200,(比如使用者名稱 密碼錯誤)
if (t instanceof BaseResponse) {
//判斷具體的業務請求是否成功
BaseResponse baseResponse = (BaseResponse) t;
//沒有狀態碼錶示成功
//這是我們和服務端的一個規定(一般情況下status等於0才是成功,我們這裡是null才成功)
//一般==null,則return true;否則return false
return baseResponse.getStatus() == null;
}
return false;
}
/**
* 處理錯誤網路請求
*
* @param data T 資料模型物件
* @param error Throwable錯誤物件
*/
private void handlerRequest(T data, Throwable error) {
if (onFailed(data, error)) {//fasle就會走else,父類(可以說本類)處理錯誤;true:就是外部處理錯誤
//回撥了請求失敗方法
//並且該方法返回了true
//返回true就表示外部手動處理錯誤
//那我們框架內部就不用做任何事情了
} else {
//呼叫工具處理錯誤(這個是父類,內部處理錯誤)
HttpUtil.handlerRequest(data, error);
}
}
/**
* 檢查是否需要隱藏載入提示框
*/
private void checkHideLoading() {
if (isShowLoading) {
LoadingUtil.hideLoading();
}
}
}
使用
//測試自動顯示載入對話方塊
Api.getInstance().sheetDetail("1")
.subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(), true) {
//false表示不顯示
// .subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(),false) {
//無參構造方法(沒有引數,也是不顯示提示載入對話方塊)
// .subscribe(new HttpObserver<DetailResponse<Sheet>>(getMainActivity(),false) {
@Override
public void onSucceeded(DetailResponse<Sheet> data) {
LogUtil.d(TAG, "onSucceeded:" + data.getData().getTitle());
}
});
相關文章
- Retrofit網路框架介紹框架
- Android 網路框架 Retrofit 原始碼解析Android框架原始碼
- Android網路請求(終) 網路請求框架RetrofitAndroid框架
- 使用retrofit進行網路請求
- 網路元件 基於Retrofit2+RxJava2+GSON/Fastjson的網路框架元件RxJavaASTJSON框架
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,Retrofit常用註解的使用
- 使用Retrofit+RxJava實現網路請求RxJava
- Retrofit + Kotlin + MVVM 的網路請求框架的封裝嘗試KotlinMVVM框架封裝
- 分分鐘使用Retrofit+Rxjava實現網路請求RxJava
- Kotlin實戰--Retrofit網路模型Kotlin模型
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,及常用註解使用
- Retrofit網路請求原始碼解析原始碼
- Retrofit原始碼解析之網路請求原始碼
- Android使用Kotlin+Retrofit+Rxjava實現簡單的網路請求AndroidKotlinRxJava
- OkHttp、rxJava、Retrofit聯合網路請求(二)HTTPRxJava
- OkHttp、rxJava、Retrofit聯合網路請求(一)HTTPRxJava
- Retrofit和OkHttp實現 Android網路快取HTTPAndroid快取
- RxJava+Retrofit2搭建網路請求元件完整配置、示例程式碼及流程梳理RxJava元件
- redhat 7 使用nmcli 命令配置網路Redhat
- VMware中配置使用NAT模式網路模式
- Retrofit基本使用
- flutter網路請求框架dio基本使用Flutter框架
- Kotlin中Retrofit網路請求簡單封裝Kotlin封裝
- HTTP客戶端框架之RetrofitHTTP客戶端框架
- 幾種Linux網路配置工具的使用Linux
- Retrofit+okhttp+Rxjava封裝網路請求工具類HTTPRxJava封裝
- Retrofit 2.0 使用教程
- 框架學習| Retrofit2.x(一)框架
- centos 網路配置CentOS
- linux網路配置Linux
- pfsense配置網路
- Linux|網路配置Linux
- 社會網路分析及其Python實現Python
- 淺談網路架構及其演變架構
- 網路分流器-網路分流器-網路流量監控技術及其方法
- 【Linux】(小白向)詳解VirtualBox網路配置-配置Linux網路Linux
- [轉載]使用IntelliJ IDEA開發SpringMVC網站(二)框架配置IntelliJIdeaSpringMVC網站框架
- python網路爬蟲(14)使用Scrapy搭建爬蟲框架Python爬蟲框架