安卓專案實戰之強大的網路請求框架okGo使用詳解(一):實現get,post基本網路請求,下載上傳進度監聽以及對Callback自定義的深入理解
1.新增依賴
//必須使用
compile 'com.lzy.net:okgo:3.0.4'
//以下三個選擇新增,okrx和okrx2不能同時使用,一般選擇新增最新的rx2支援即可
compile 'com.lzy.net:okrx:1.0.2'
compile 'com.lzy.net:okrx2:2.0.2'
compile 'com.lzy.net:okserver:2.0.5'
OkHttp
okgo使用的okhttp的版本是最新的3.8.0版本,okGo框架的GitHub地址:https://github.com/jeasonlzy/okhttp-OkGo
OkRx2
是針對RxJava2,將OkGO擴充套件的專案,使用的RxJava2的版本如下:
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
OkRx2主要功能:
1.可以很完美結合RxJava做網路請求
2.在使用上比Retrofit更簡單方便,門檻更低,靈活性更高
3.網路請求和RxJava呼叫可以做成一條鏈試,寫法優雅
4.使用Converter介面,支援任意型別的資料自動解析
5.OkRx是擴充套件的OkGo,所以OkGo包含的所有功能和寫法,OkRx全部支援。
OkServer
也是對okGo的擴充套件,包括兩個核心入口類:
1.OkDownload是統一的下載管理,支援斷點下載功能,該類詳細文件檢視
2.OkUpload是統一的上傳管理,
2.全域性的配置
一般在Aplication,或者基類中配置,只需要呼叫一次即可,配置示例如下:
public class GApp extends Application { @Override public void onCreate() { super.onCreate(); initOkGo(); // 全域性配置okGo } private void initOkGo() { //---------這裡給出的是示例程式碼,告訴你可以這麼傳,實際使用的時候,根據需要傳,不需要就不傳-------------// HttpHeaders headers = new HttpHeaders(); headers.put("commonHeaderKey1", "commonHeaderValue1"); //header不支援中文,不允許有特殊字元 headers.put("commonHeaderKey2", "commonHeaderValue2"); HttpParams params = new HttpParams(); params.put("commonParamsKey1", "commonParamsValue1"); //param支援中文,直接傳,不要自己編碼 params.put("commonParamsKey2", "這裡支援中文引數"); //----------------------------------------------------------------------------------------// OkHttpClient.Builder builder = new OkHttpClient.Builder(); //log相關 HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor("OkGo"); loggingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY); //log列印級別,決定了log顯示的詳細程度 loggingInterceptor.setColorLevel(Level.INFO); //log顏色級別,決定了log在控制檯顯示的顏色 builder.addInterceptor(loggingInterceptor); //新增OkGo預設debug日誌 //第三方的開源庫,使用通知顯示當前請求的log,不過在做檔案下載的時候,這個庫好像有問題,對檔案判斷不準確 //builder.addInterceptor(new ChuckInterceptor(this)); //超時時間設定,預設60秒 builder.readTimeout(OkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS); //全域性的讀取超時時間 builder.writeTimeout(OkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS); //全域性的寫入超時時間 builder.connectTimeout(OkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS); //全域性的連線超時時間 //自動管理cookie(或者叫session的保持),以下幾種任選其一就行 //builder.cookieJar(new CookieJarImpl(new SPCookieStore(this))); //使用sp保持cookie,如果cookie不過期,則一直有效 builder.cookieJar(new CookieJarImpl(new DBCookieStore(this))); //使用資料庫保持cookie,如果cookie不過期,則一直有效 //builder.cookieJar(new CookieJarImpl(new MemoryCookieStore())); //使用記憶體保持cookie,app退出後,cookie消失 //https相關設定,以下幾種方案根據需要自己設定 //方法一:信任所有證書,不安全有風險 HttpsUtils.SSLParams sslParams1 = HttpsUtils.getSslSocketFactory(); //方法二:自定義信任規則,校驗服務端證書 //HttpsUtils.SSLParams sslParams2 = HttpsUtils.getSslSocketFactory(new SafeTrustManager()); //方法三:使用預埋證書,校驗服務端證書(自簽名證書) //HttpsUtils.SSLParams sslParams3 = HttpsUtils.getSslSocketFactory(getAssets().open("srca.cer")); //方法四:使用bks證書和密碼管理客戶端證書(雙向認證),使用預埋證書,校驗服務端證書(自簽名證書) //HttpsUtils.SSLParams sslParams4 = HttpsUtils.getSslSocketFactory(getAssets().open("xxx.bks"), "123456", getAssets().open("yyy.cer")); builder.sslSocketFactory(sslParams1.sSLSocketFactory, sslParams1.trustManager); //配置https的域名匹配規則,詳細看demo的初始化介紹,不需要就不要加入,使用不當會導致https握手失敗 //builder.hostnameVerifier(new SafeHostnameVerifier()); // 其他統一的配置 OkGo.getInstance().init(this) // 必須呼叫初始化 .setOkHttpClient(builder.build()) // 底層okhttp框架的引數設定 // okGo框架的特有設定 .setCacheMode(CacheMode.NO_CACHE) //全域性統一快取模式,預設不使用快取,可以不傳 .setCacheTime(CacheEntity.CACHE_NEVER_EXPIRE) //全域性統一快取時間,預設永不過期,可以不傳 .setRetryCount(3) //全域性統一超時重連次數,預設為三次,那麼最差的情況會請求4次(一次原始請求,三次重連請求),不需要可以設定為0 .addCommonHeaders(headers) //全域性公共頭 .addCommonParams(params); //全域性公共引數 } /** * 這裡只是我誰便寫的認證規則,具體每個業務是否需要驗證,以及驗證規則是什麼,請與服務端或者leader確定 * 重要的事情說三遍,以下程式碼不要直接使用,只是提供一個自定義認證規則的實現案例供參考,請自行註釋掉 */ private class SafeTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { for (X509Certificate certificate : chain) { certificate.checkValidity(); //檢查證書是否過期,簽名是否通過等 } } catch (Exception e) { throw new CertificateException(e); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } /** * 這裡只是我誰便寫的https的域名匹配規則,具體每個業務驗證規則是什麼,請與服務端或者leader確定 * 重要的事情說三遍,以下程式碼不要直接使用,此處只是提供自定義例子以供參考,請自行註釋掉 */ private class SafeHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { //驗證主機名是否匹配 //return hostname.equals("server.jeasonlzy.com"); return true; } } }
如果你什麼都不想自己寫,那麼下面一行程式碼就夠了:
OkGo.getInstance().init(this);
即採用框架預設配置。
3.執行網路請求
關於使用okGo進行網路請求時所支援的所有配置見下,這裡演示的是一次普通請求所有能配置的引數,真實使用時不需要配置這麼多,按自己的需要選擇性的使用即可:
無論做什麼請求,第一行的泛型一定要加!!!
關於上面每個配置的解釋:
1,第一行的泛型ServerMode一定要特別注意,這個表示你請求到的網路的資料型別是什麼,必須指定,否則無法解析網路資料。
2,.post(url):這個表示當前請求是post請求,當然一共支援GET,HEAD,OPTIONS,POST,PUT,DELETE, PATCH, TRACE這8種請求方式,你只需要改改這個方法名就行了,很方便。
3,.params():新增引數的時候,最後一個isReplace為可選引數,預設為true,即代表相同key的時候,後新增的會覆蓋先前新增的,為false時不會覆蓋,圖上只有兩個引數的寫法:
4,.tag(this):請求的tag,用於標識當前的請求,方便後續取消對應的請求,如果你不需要取消請求,也可以不用設定,取消請求的寫法:
通常我們在Activity中做網路請求,當Activity銷燬時要取消請求否則會發生記憶體洩露,那麼就可以在onDestory()裡面寫如下程式碼:
前兩個方法用於取消okHttpClient是我們在全域性配置的情況下(okHttpClient的配置也就是application中builder部分的配置)。
後兩個方法用於取消okHttpClient是我們通過呼叫.client()傳入的外部配置(見第12小點處說明),而非application中我們的配置的情況。
5, .isMultipart():該方法表示是否強制使用multipart/form-data表單上傳,因為該框架在有檔案的時候,無論你是否設定這個引數,預設都是multipart/form-data格式上傳,但是如果引數中不包含檔案,預設使用application/x-www-form-urlencoded格式上傳,如果你的伺服器要求無論是否有檔案,都要使用表單上傳,那麼可以用這個引數設定為true。
6,.isSpliceUrl():該方法表示是否強制將params的引數拼接到url後面,預設false不拼接。一般來說,post、put等有請求體的方法應該把引數都放在請求體中,不應該放在url上,但是有的服務端可能不太規範,url和請求體都需要傳遞引數,那麼這時候就使用該引數,他會將你所有使用.params()方法傳遞的引數,自動拼接在url後面。
7,.retryCount():該方法是配置超時重連次數,也可以在全域性初始化的時候設定,預設使用全域性的配置,即為3次,你也可以在這裡為你的這個請求特殊配置一個,並不會影響全域性其他請求的超時重連次數。
8,.cacheKey() .cacheTime() .cacheMode():這三個是快取相關的配置,詳細請看快取介紹
9,.headers():該方法是傳遞服務端需要的請求頭
記住任何時候,任何地方,以下這兩行設定程式碼永遠不起作用:
headers("Content-Type", "你自己設定的值")
headers("Content-Length", 一個數值)
Content-Type為框架根據上傳型別智慧識別並新增,其中的對應規則見文章末尾的表格,Content-Length永遠自動根據你上傳的內容的真實大小自動新增,不可修改。
10,.params():該方法傳遞鍵值對引數,所有up開頭的方法不能與params()方法混用,如果混用,將按up方法的行為來,所有params()設定的引數將丟失。
11,.addUrlParams() .addFileParams() .addFileWrapperParams():這裡是支援一個key傳遞多個文字引數,也支援一個key傳遞多個檔案引數。
12,每個請求都有一個.client()方法可以傳遞一個OkHttpClient物件,表示當前這個請求將使用外界傳入的這個OkHttpClient物件,其他的請求還是使用全域性的保持不變。那麼至於這個OkHttpClient你想怎麼配置,或者配置什麼東西,那就隨意了是不,改個超時時間,加個攔截器什麼的統統都是可以的,看你心情嘍,一般該種情況使用很少,但是也會存在。
如果你的當前請求使用的是你傳遞的OkHttpClient物件的話,那麼當你呼叫OkGo.getInstance().cancelTag(tag)的時候,是取消不了這個請求的,因為OkGo只能取消使用全域性配置的請求,不知道你這個請求是用你自己的OkHttpClient的,如果一定要取消,可以是使用OkGo提供的過載方法,具體看取消請求的其他過載方法。
4,在回撥中處理response,如下:
@Override
public void onSuccess(Response<String> response) {
handleResponse(response); // 該方法能夠顯示成功時的請求狀態,請求頭,響應資料以及響應頭資訊
String result = response.body(); // 獲取資料
}
// 或者
@Override
public void onSuccess(Response<LzyResponse<ServerModel>> response) {
handleResponse(response); // 該方法能夠顯示成功時的請求狀態,請求頭,響應資料以及響應頭資訊
LzyResponse<ServerModel> result = response.body(); // 只獲取資料
ServerModel model = response.body().results;
}
@Override
public void onError(Response<LzyResponse<ServerModel>> response) {
handleError(response); // 該方法能夠顯示失敗時的請求狀態,請求頭,響應資料以及響應頭資訊
}
其中handleResponse(response);和handleError(response);的方法定義如下:
protected <T> void handleResponse(T data) {
Response<T> response = new Response<>();
response.setBody(data);
handleResponse(response);
}
protected <T> void handleResponse(Response<T> response) {
StringBuilder sb;
Call call = response.getRawCall();
if (call != null) {
requestState.setText("請求成功 請求方式:" + call.request().method() + "\n" + "url:" + call.request().url());
Headers requestHeadersString = call.request().headers();
Set<String> requestNames = requestHeadersString.names();
sb = new StringBuilder();
for (String name : requestNames) {
sb.append(name).append(" : ").append(requestHeadersString.get(name)).append("\n");
}
requestHeaders.setText(sb.toString());
} else {
requestState.setText("--");
requestHeaders.setText("--");
}
T body = response.body();
if (body == null) {
responseData.setText("--");
} else {
if (body instanceof String) {
responseData.setText((String) body);
} else if (body instanceof List) {
sb = new StringBuilder();
List list = (List) body;
for (Object obj : list) {
sb.append(obj.toString()).append("\n");
}
responseData.setText(sb.toString());
} else if (body instanceof Set) {
sb = new StringBuilder();
Set set = (Set) body;
for (Object obj : set) {
sb.append(obj.toString()).append("\n");
}
responseData.setText(sb.toString());
} else if (body instanceof Map) {
sb = new StringBuilder();
Map map = (Map) body;
Set keySet = map.keySet();
for (Object key : keySet) {
sb.append(key.toString()).append(" : ").append(map.get(key)).append("\n");
}
responseData.setText(sb.toString());
} else if (body instanceof File) {
File file = (File) body;
responseData.setText("資料內容即為檔案內容\n下載檔案路徑:" + file.getAbsolutePath());
} else if (body instanceof Bitmap) {
responseData.setText("圖片的內容即為資料");
} else {
responseData.setText(Convert.formatJson(body));
}
}
okhttp3.Response rawResponse = response.getRawResponse();
if (rawResponse != null) {
Headers responseHeadersString = rawResponse.headers();
Set<String> names = responseHeadersString.names();
sb = new StringBuilder();
sb.append("url : ").append(rawResponse.request().url()).append("\n\n");
sb.append("stateCode : ").append(rawResponse.code()).append("\n");
for (String name : names) {
sb.append(name).append(" : ").append(responseHeadersString.get(name)).append("\n");
}
responseHeader.setText(sb.toString());
} else {
responseHeader.setText("--");
}
}
protected <T> void handleError() {
Response<T> response = new Response<>();
handleResponse(response);
}
protected <T> void handleError(Response<T> response) {
if (response == null) return;
if (response.getException() != null) response.getException().printStackTrace();
StringBuilder sb;
Call call = response.getRawCall();
if (call != null) {
requestState.setText("請求失敗 請求方式:" + call.request().method() + "\n" + "url:" + call.request().url());
Headers requestHeadersString = call.request().headers();
Set<String> requestNames = requestHeadersString.names();
sb = new StringBuilder();
for (String name : requestNames) {
sb.append(name).append(" : ").append(requestHeadersString.get(name)).append("\n");
}
requestHeaders.setText(sb.toString());
} else {
requestState.setText("--");
requestHeaders.setText("--");
}
responseData.setText("--");
okhttp3.Response rawResponse = response.getRawResponse();
if (rawResponse != null) {
Headers responseHeadersString = rawResponse.headers();
Set<String> names = responseHeadersString.names();
sb = new StringBuilder();
sb.append("stateCode : ").append(rawResponse.code()).append("\n");
for (String name : names) {
sb.append(name).append(" : ").append(responseHeadersString.get(name)).append("\n");
}
responseHeader.setText(sb.toString());
} else {
responseHeader.setText("--");
}
}
CallBack
Callback一共有以下幾個回撥,除onSuccess必須實現以外,其餘均可以按需實現:
由於Callback介面實現了Converter介面,所以這個接口裡面的方法也必須實現:
為了避免每一次自定義callback回撥時都要重寫這8個方法,okgo框架幫我們已經內建預先定義好了一個抽象回撥類AbsCallback,該抽象類中將除onSuccess()和convertResponse()方法之外的其餘6個方法均已定義為非抽象方法,這樣在我們需要自定義實現介面CallBack的實現類的時候,就不用直接實現CallBack介面重寫8個方法了,而是間接繼承抽象類AbsCallback,這樣我們只需重寫onSuccess()和convertResponse()這兩個方法,
其他6個方法此時允許我們在需要的時候再去重寫。
以後我們自定義的CallBack抽象類均通過繼承AbsCallback來定義。
自定義的指定泛型的Callback在繼承AbsCallback抽象類重寫convertResponse(response)方法的邏輯:其實就是要先建立該泛型對應的Converter的轉換器實現類的物件,在底層呼叫的其實是轉換器類物件中重寫的convertResponse方法的實現,該方法中的邏輯就是對response的資料內容進行解析邏輯,返回泛型指定的型別,所以我們還需要針對不同的自定義泛型的Callback實現Convert<泛型>來具體實現轉換器類,在該類中提供對convertResponse方法的實現,然後就可以在callback的回撥方法中呼叫轉換器類物件的convertResponse方法完成請求結果資料的解析,然後以指定泛型返回。
一句話總結即就是兩個convertResponse方法之間的呼叫:自定義Callback重寫的convertResponse方法實現呼叫了我們額外定義的返回指定泛型型別的實現Converter介面的轉換器類中的convertResponse方法。
例如我們來看框架幫我們內建的StringCallback抽象類的實現,程式碼如下:
public abstract class StringCallback extends AbsCallback<String> {
private StringConvert convert; // 轉換器物件
public StringCallback() {
convert = new StringConvert();
}
@Override
public String convertResponse(Response response) throws Throwable {
String s = convert.convertResponse(response);
response.close();
return s;
}
}
我們可以看到重寫的convertResponse方法中呼叫了針對泛型為String定義的轉換器StringConvert物件convert的convertResponse方法,而這個額外的StringConvert轉換器類定義程式碼如下:
public class StringConvert implements Converter<String> {
@Override
public String convertResponse(Response response) throws Throwable {
ResponseBody body = response.body();
if (body == null) return null;
return body.string();
}
}
上面的8個回撥方法中,針對我們每次自定義的Callback抽象類都同時預設提供一種convertResponse方法的實現方式,在確保convertResponse方法解析正確的前提下,這樣我們就只需考慮其他7個跟請求有關的方法,並且這些回撥方法中的泛型可以直接替換成本次請求我們指定的泛型型別,而且在這7個方法中,我們必須回撥的只有onsuccess方法,其餘回撥方法均以通過AbsCallback轉成了非抽象方法,我們按需實現即可,例如StringCallback的某次請求程式碼如下:
其中的onSuccess方法是必須要回調的,此處的onError方法是我們按需回撥的,此時回撥就簡潔多了,不像之前上面使用JsonCallback那樣一下子回調了8個方法。
按照以上的思路我們可以自定義一個JsonCallback抽象類,將網路返回的JSON資料自動解析成物件。
注意
對於我們定義的轉換器類中convertResponse方法的解析邏輯,如果解析邏輯寫的有問題無法正常轉換得到我們指定的泛型物件,那麼通過檢視原始碼我們發現,此時同樣會回撥onError方法,雖然網路請求沒有問題,但是解析轉換有問題同樣會回撥onError方法,如果網路請求成功,並且解析轉換邏輯正確那麼就會回撥onSuccess方法。
執行error方法有以下三種情況:
1,網路請求錯誤
2,網路請求正確,轉換器類中解析轉換邏輯錯誤
3,網路請求正確,轉換器書寫正確,能拿到正確返回的資料,但是是伺服器端返回給我們的錯誤提示資訊,比如使用者在登入時的使用者名稱不存在,密碼錯誤等
自定義Callback的高階用法:
- DialogCallback
我們經常需要在網路請求前顯示一個loading,請求結束後取消loading,這是個重複的工作,我們完全可以自定義一個Callback,讓這個Callback幫我們完成這個工作,那麼我們就需要用到Callback中的兩個回撥方法,onStart()和onFinish(),詳細原始碼如下:
- EncriptCallback
有時候我們需要在請求前,將我們的引數都加密或者簽名,然而加密也是個重複的工作,我們沒必要每次都寫,也可以放在自定義的Callback中,原始碼如下:
okGo框架預設內建的四種Callback的使用
okGo框架預設已經幫我們定義了四種Callback的抽象類,其中AbsCallback類中方法均為空實現,只做我們自定義時繼承的父類,除此之外沒有其他作用,我們著重來看另外三個:StringCallback,BitmapCallback,FileCallback(著重講解),這三個類都提供了一種對於convertResponse方法的解析實現,可以直接拿來用,下面分別來講:
StringCallback
使用場景:伺服器端返回的資料為字串時,我們可以如下使用:
然後在onSuccess方法中獲取到返回的字串值,然後如果是json字串,則使用Gson解析等操作。
BitmapCallback
如果你知道你的請求資料是圖片,可以使用這樣的方法:
FileCallback:基本的檔案下載功能,包括下載進度監聽
如果你要下載檔案,可以這麼寫。
FileCallback具有三個過載的構造方法,分別是
FileCallback():空參構造
FileCallback(String destFileName):可以額外指定檔案下載完成後的檔名
FileCallback(String destFileDir, String destFileName):可以額外指定檔案的下載目錄和下載完成後的檔名
檔案目錄如果不指定,預設下載的目錄為 sdcard/download/,檔名如果不指定,則按照以下規則命名:
1.首先檢查使用者是否傳入了檔名,如果傳入,將以使用者傳入的檔名命名
2.如果沒有傳入,那麼將會檢查服務端返回的響應頭是否含有Content-Disposition=attachment;filename=FileName.txt該種形式的響應頭,如果有,則按照該響應頭中指定的檔名命名檔案,如FileName.txt
3.如果上述響應頭不存在,則檢查下載的檔案url,例如:http://image.baidu.com/abc.jpg,那麼將會自動以abc.jpg命名檔案
4.如果url也把檔名解析不出來,那麼最終將以"unknownfile_" + System.currentTimeMillis()命名檔案
這裡面有個新物件叫Progress,他儲存了當前進度的很多資訊,原始碼中的定義如下,欄位很多,所以如果你需要什麼進度資訊,直接就能取出來,如下:
okGo基本的上傳功能
1,上傳String型別的文字
一般此種用法用於與伺服器約定的資料格式,當使用該方法時,params中的引數設定是無效的,所有引數均需要通過需要上傳的文字中指定,此外,額外指定的header引數仍然保持有效。
預設會攜帶以下請求頭
Content-Type: text/plain;charset=utf-8
如果你對請求頭有自己的要求,可以使用這個過載的形式,傳入自定義的content-type
// 比如上傳xml資料,這裡就可以自己指定請求頭
upString("這是要上傳的長文字資料!", MediaType.parse("application/xml"))
2,上傳JSON型別的文字
該方法與upString沒有本質區別,只是資料格式是json,一般來說,需要自己建立一個實體bean或者一個map,把需要的引數設定進去,然後通過三方的Gson或者fastjson轉換成json物件,最後直接使用該方法提交到伺服器。
預設會攜帶以下請求頭,請不要手動修改,okgo也不支援自己修改
Content-Type: application/json;charset=utf-8
3,上傳檔案
上傳檔案支援檔案與引數一起同時上傳,也支援一個key上傳多個檔案,以下方式可以任選
特別要注意的是
1). 很多人會說需要在上傳檔案到時候,要把Content-Type修改掉,變成multipart/form-data,就像下面這樣的。其實在使用OkGo的時候,只要你添加了檔案,這裡的的Content-Type不需要你手動設定,OkGo自動新增該請求頭,同時,OkGo也不允許你修改該請求頭。
Content-Type: multipart/form-data; boundary=f6b76bad-0345-4337-b7d8-b362cb1f9949
2). 如果沒有檔案,那麼OkGo將自動使用以下請求頭,同樣OkGo也不允許你修改該請求頭。
Content-Type: application/x-www-form-urlencoded
3). 如果你的伺服器希望你在沒有檔案的時候依然使用multipart/form-data請求,那麼可以使用.isMultipart(true)這個方法強制修改,一般來說是不需要強制的。
有人說他有很多檔案,不知道數量,又要一個檔案對應一個key該怎麼辦,其實很簡單,把呼叫鏈斷開,用迴圈新增就行了嘛。
4,取消請求
之前講到,我們為每個請求前都設定了一個引數tag,取消就是通過這個tag來取消的。通常我們在Activity中做網路請求,當Activity銷燬時要取消請求否則會發生記憶體洩露,那麼就可以在onDestory()裡面寫如下程式碼:
注意以下取消的請求不要全部用,自己按需要寫,我只是寫出來,告訴你有這麼幾個方法。