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()裡面寫如下代碼:
注意以下取消的請求不要全部用,自己按需要寫,我隻是寫出來,告訴你有這麼幾個方法。