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()里面写如下代码:
注意以下取消的请求不要全部用,自己按需要写,我只是写出来,告诉你有这么几个方法。