所有網絡請求相關的參數和配置全部通過層的api和鏈式調用封裝到了ConfigInfo中,后在start()方法中調用retrofit層,開始網絡請求.
/**
* 在這里組裝請求,然后發出去
* @param <E>
* @return
*/
@Override
public <E> ConfigInfo<E> start(ConfigInfo<E> configInfo) {
String url = Tool.appendUrl(configInfo.url, isAppendUrl());//組拼baseUrl和urltail
configInfo.url = url;
configInfo.listener.url = url;
//todo 這里token還可能在請求頭中,應加上此類情況的自定義.
if (configInfo.isAppendToken){
Tool.addToken(configInfo.params);
}
if (configInfo.loadingDialog != null && !configInfo.loadingDialog.isShowing()){
try {//預防badtoken簡便和直接的方法
configInfo.loadingDialog.show();
}catch (Exception e){
}
}
if (getCache(configInfo)){//異步,去拿緩存--只針對String類型的請求
return configInfo;
}
T request = generateNewRequest(configInfo);//根據類型生成/執行不同的請求對象
/*
這三個方式是給volley預留的
setInfoToRequest(configInfo,request);
cacheControl(configInfo,request);
addToQunue(request);*/
return configInfo;
}
分類生成/執行各類請求:
private <E> T generateNewRequest(ConfigInfo<E> configInfo) {
int requestType = configInfo.type;
switch (requestType){
case ConfigInfo.TYPE_STRING:
case ConfigInfo.TYPE_JSON:
case ConfigInfo.TYPE_JSON_FORMATTED:
return newCommonStringRequest(configInfo);
case ConfigInfo.TYPE_DOWNLOAD:
return newDownloadRequest(configInfo);
case ConfigInfo.TYPE_UPLOAD_WITH_PROGRESS:
return newUploadRequest(configInfo);
default:return null;
}
}
所以,對retrofit的使用,只要實現以下三個方法就行了:
如果切換到volley或者其他網絡框架,也是實現這三個方法就好了.
newCommonStringRequest(configInfo),
newDownloadRequest(configInfo);
newUploadRequest(configInfo)
@Override
protected <E> Call newCommonStringRequest(final ConfigInfo<E> configInfo) {
Call<ResponseBody> call;
if (configInfo.method == HttpMethod.GET){
call = service.executGet(configInfo.url,configInfo.params);
}else if (configInfo.method == HttpMethod.POST){
if(configInfo.paramsAsJson){//參數在請求體以json的形式發出
String jsonStr = MyJson.toJsonStr(configInfo.params);
Log.e("dd","jsonstr request:"+jsonStr);
RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), jsonStr);
call = service.executeJsonPost(configInfo.url,body);
}else {
call = service.executePost(configInfo.url,configInfo.params);
}
}else {
configInfo.listener.onError("不是get或post方法");//暫時不考慮其他方法
call = null;
return call;
}
configInfo.tagForCancle = call;
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
if (!response.isSuccessful()){
configInfo.listener.onCodeError("http錯誤碼為:"+response.code(),response.message(),response.code());
Tool.dismiss(configInfo.loadingDialog);
return;
}
String string = "";
try {
string = response.body().string();
Tool.parseStringByType(string,configInfo);
Tool.dismiss(configInfo.loadingDialog);
} catch (final IOException e) {
e.printStackTrace();
configInfo.listener.onError(e.toString());
Tool.dismiss(configInfo.loadingDialog);
}
}
@Override
public void onFailure(Call<ResponseBody> call, final Throwable t) {
configInfo.listener.onError(t.toString());
Tool.dismiss(configInfo.loadingDialog);
}
});
return call;
}
既然要封裝,肯定就不能用retrofit的常規用法:ApiService接口里每個接口文檔上的接口都寫一個方法,而是應該用QueryMap/FieldMap注解,接受一個以Map形式封裝好的鍵值對.這個與我們上一層的封裝思路和形式都是一樣的.
@GET()
Call<ResponseBody> executGet(@Url String url, @QueryMap Map<String, String> maps);
/**
* 注意:
* 1.如果方法的泛型指定的類不是ResonseBody,retrofit會將返回的string成用json轉換器自動轉換該類的一個對象,轉換不成功就報錯.
* 如果不需要gson轉換,那么就指定泛型為ResponseBody,
* 只能是ResponseBody,子類都不行,同理,下載上傳時,也必須指定泛型為ResponseBody
* 2. map不能為null,否則該請求不會執行,但可以size為空.
* 3.使用@url,而不是@Path注解,后者放到方法體上,會強制先urlencode,然后與baseurl拼接,請求無法成功.
* @param url
* @param map
* @return
*/
@FormUrlEncoded
@POST()
Call<ResponseBody> executePost(@Url String url, @FieldMap Map<String, String> map);
/**
* 直接post體為一個json格式時,使用這個方法.注意:@Body 不能與@FormUrlEncoded共存
* @param url
* @param body
* @return
*/
@POST()
Call<ResponseBody> executeJsonPost(@Url String url, @Body RequestBody body);
retrofit其實有請求時傳入一個javabean的注解方式,確實可以在框架內部轉換成json.但是不適合封裝.
其實很簡單,搞清楚以json形式發出參數的本質: 請求體中的json本質上還是一個字符串.那么可以將Map攜帶過來的參數轉成json字符串,然后用RequestBody包裝一層就好了:
String jsonStr = MyJson.toJsonStr(configInfo.params);
RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), jsonStr);
call = service.executeJsonPost(configInfo.url,body);
@GET()
<T> Call<BaseNetBean<T>> getStandradJson(@Url String url, @QueryMap Map<String, String> maps);
//注:BaseNetBean就是三個標準字段的json:
public class BaseNetBean<T>{
public int code;
public String msg;
public T data;
}
這樣寫會拋出異常:
報的錯誤
Method return type must not include a type variable or wildcard: retrofit2.Call<T>
JakeWharton的回復:
You cannot. Type information needs to be fully known at runtime in order for deserialization to work.
因為上面的原因,我們只能通過retrofit發請求,返回一個String,自己去解析.但這也有坑:
1.不能寫成下面的形式:
@GET()
Call<String> executGet(@Url String url, @QueryMap Map<String, String> maps);
你以為指定泛型為String它就返回String,不,你還太年輕了.
這里的泛型,意思是,使用retrofit內部的json轉換器,將response里的數據轉換成一個實體類xxx,比如UserBean之類的,而String類明顯不是一個有效的實體bean類,自然轉換失敗.
所以,要讓retrofit不適用內置的json轉換功能,你應該直接指定類型為ResponseBody:
@GET()
Call<ResponseBody> executGet(@Url String url, @QueryMap Map<String, String> maps);
2.既然不采用retrofit內部的json轉換功能,那就要在回調那里自己拿到字符串,用自己的json解析了.那么坑又來了:
泛型擦除:
回調接口上指定泛型,在回調方法里直接拿到泛型,這是在java里很常見的一個泛型接口設計:
public abstract class MyNetListener<T>{
public abstract void onSuccess(T response,String resonseStr);
....
}
//使用:
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
String string = response.body().string();
Gson gson = new Gson();
Type objectType = new TypeToken<T>() {}.getType();
final T bean = gson.fromJson(string,objectType);
configInfo.listener.onSuccess(bean,string);
...
}
...
}
但是,拋出異常:
java.lang.ClassCastException: com..gson.internal.LinkedTreeMap cannot be cast to xxx
這是因為在運行過程中,通過泛型傳入的類型T丟失了,所以無法轉換,這叫做泛型擦除:.
要解析的話,還是老老實實傳入javabean的class吧.所以在頂層的API里,有一個必須傳的Class clazz:
postStandardJson(String url, Map map, Class clazz, MyNetListener listener)
綜上,我們需要傳入class對象,完全自己去解析json.解析已封裝成方法.也是根據三個不同的小類型(字符串,一般json,標準json)
這里處理緩存時,如果要緩存內容,當然是緩存成功的內容,失敗的就不必緩存了.
Tool.parseStringByType(string,configInfo);
public static void parseStringByType(final String string, final ConfigInfo configInfo) {
switch (configInfo.type){
case ConfigInfo.TYPE_STRING:
//緩存
cacheResponse(string, configInfo);
//處理結果
configInfo.listener.onSuccess(string, string);
break;
case ConfigInfo.TYPE_JSON:
parseCommonJson(string,configInfo);
break;
case ConfigInfo.TYPE_JSON_FORMATTED:
parseStandJsonStr(string, configInfo);
break;
}
}
json解析框架選擇,gson,fastjson隨意,不過好也是自己再包一層api:
public static <T> T parseObject(String str,Class<T> clazz){ // return new Gson().fromJson(str,clazz); return JSON.parseObject(str,clazz); }
private static <E> void parseCommonJson( String string, ConfigInfo<E> configInfo) {
if (isJsonEmpty(string)){
configInfo.listener.onEmpty();
}else {
try{
if (string.startsWith("{")){
E bean = MyJson.parseObject(string,configInfo.clazz);
configInfo.listener.onSuccessObj(bean ,string,string,0,"");
cacheResponse(string, configInfo);
}else if (string.startsWith("[")){
List<E> beans = MyJson.parseArray(string,configInfo.clazz);
configInfo.listener.onSuccessArr(beans,string,string,0,"");
cacheResponse(string, configInfo);
}else {
configInfo.listener.onError("不是標準json格式");
}
}catch (Exception e){
e.printStackTrace();
configInfo.listener.onError(e.toString());
}
}
}
三個字段對應的數據直接用jsonObject.optString來取:
JSONObject object = null;
try {
object = new JSONObject(string);
} catch (JSONException e) {
e.printStackTrace();
configInfo.listener.onError("json 格式異常");
return;
}
String key_data = TextUtils.isEmpty(configInfo.key_data) ? NetDefaultConfig.KEY_DATA : configInfo.key_data;
String key_code = TextUtils.isEmpty(configInfo.key_code) ? NetDefaultConfig.KEY_CODE : configInfo.key_code;
String key_msg = TextUtils.isEmpty(configInfo.key_msg) ? NetDefaultConfig.KEY_MSG : configInfo.key_msg;
final String dataStr = object.optString(key_data);
final int code = object.optInt(key_code);
final String msg = object.optString(key_msg);
注意,optString后字符串為空的判斷:一個字段為null時,optString的結果是字符串"null"而不是null
public static boolean isJsonEmpty(String data){
if (TextUtils.isEmpty(data) || "[]".equals(data)
|| "{}".equals(data) || "null".equals(data)) {
return true;
}
return false;
}
然后就是相關的code情況的處理和回調:
狀態碼為未登錄時,執行自動登錄的邏輯,自動登錄成功后再重發請求.登錄不成功才執行unlogin()回調.
注意data字段可能是一個普通的String,而不是json.
private static <E> void parseStandardJsonObj(final String response, final String data, final int code,
final String msg, final ConfigInfo<E> configInfo){
int codeSuccess = configInfo.isCustomCodeSet ? configInfo.code_success : BaseNetBean.CODE_SUCCESS;
int codeUnFound = configInfo.isCustomCodeSet ? configInfo.code_unFound : BaseNetBean.CODE_UN_FOUND;
int codeUnlogin = configInfo.isCustomCodeSet ? configInfo.code_unlogin : BaseNetBean.CODE_UNLOGIN;
if (code == codeSuccess){
if (isJsonEmpty(data)){
if(configInfo.isResponseJsonArray()){
configInfo.listener.onEmpty();
}else {
configInfo.listener.onError("數據為空");
}
}else {
try{
if (data.startsWith("{")){
final E bean = MyJson.parseObject(data,configInfo.clazz);
configInfo.listener.onSuccessObj(bean ,response,data,code,msg);
cacheResponse(response, configInfo);
}else if (data.startsWith("[")){
final List<E> beans = MyJson.parseArray(data,configInfo.clazz);
configInfo.listener.onSuccessArr(beans,response,data,code,msg);
cacheResponse(response, configInfo);
}else {//如果data的值是一個字符串,而不是標準json,那么直接返回
if (String.class.equals(configInfo.clazz) ){//此時,E也應該是String類型.如果有誤,會拋出到下面catch里
configInfo.listener.onSuccess((E) data,data);
}else {
configInfo.listener.onError("不是標準的json數據");
}
}
}catch (final Exception e){
e.printStackTrace();
configInfo.listener.onError(e.toString());
return;
}
}
}else if (code == codeUnFound){
configInfo.listener.onUnFound();
}else if (code == codeUnlogin){
//自動登錄
configInfo.client.autoLogin(new MyNetListener() {
@Override
public void onSuccess(Object response, String resonseStr) {
configInfo.client.resend(configInfo);
}
@Override
public void onError(String error) {
super.onError(error);
configInfo.listener.onUnlogin();
}
});
}else {
configInfo.listener.onCodeError(msg,"",code);
}
}
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。