在项目开发过程中,我们或多或少的使用过很多网络请求库。基本点的就是原生的http请求框架,好比HttpClient以及HttpUrlConnection等,略懂android开发的估计无人不知android-async-http或者volley啥的,再往上走,你可能会接触okhttp等。今天我们将来介绍一个新的http请求框架,隆重推荐Retrofit
Retrofit是何方神圣
retrofit是Square公司出品的,为android和java提供一个类型安全的Http网络请求库,这里是官网地址。
Retrofit的优点
使用注解来描述http请求
1.URL参数的替换和query参数的支持
2.对象转化为请求体(如:JSON,protocol buffers等)
3.多重请求体和文件上传
以上都是官网描述
使用流程
- 权限
<uses-permission android:name="android.permission.INTERNET" />
这个没什么好说的,没有网络权限什么都做不了
- 导包
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile 'io.reactivex:rxandroid:1.2.1'
这里几个库的含义是:我们使用retrofit2.0去进行网络请求操作,同时我们使用gson去进行数据解析,并且结合rxjava去进行相应的代码编写
- 基本配置
new Retrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create()).client(okhttpBuilder.build()).baseUrl(baseUrl).build();
这段就是使用RxJava,利用gson做解析(这边解析器可以设置注入Jackson之类的,甚至可以自定义),http引擎框架是okhttp
-
API说明
Retrofit需要通过注解请求方法以及请求参数来表明应该如何去进行一个Http请求,目前内置了5种注解方式GET、POST、PUT、DELETE以及HEAD。同时资源的相对URL要在注解中明确的指出。比如请求方法
@Get("/a/b")
-
api使用
配置都OK之后,现在就开始写URL接口了。
案例1
假设有这么一个请求
GET请求看看这个GET请求,有header也有urlParam。我们可以使用@Header对header部分进行描述,后面使用@Query去添加每一个跟随urlParam
@GET("weatherservice/cityname")
Observable<WeatherModel> getWeatherModels(@Header("apikey") String apikey, @Query("cityname") String cityname);
同时如果你觉得一个一个的@Query写的有点烦,Retrofit支持使用@QueryMap,将请求的urlParam都存储在一个Map里
案例2
假设有这么一个请求,来自gankio
GET请求看看这个GET请求,跟之前的区别在于,他没有urlParam,但是参数是在url里面,这个时候我们就要采用动态替换url里面的参数的方法,如何做呢?用{}来表明url中的哪部分参数需要替换,相应的参数用@Path来注解同样的字符串
@GET("{type}/{pagenum}/{page}")
public Observable<GankioModel> getGankioModels(@Path("type") String type, @Path("pagenum") int pagenum, @Path("page") int page);
案例3
假设有这么一个请求,
Post请求
看看这个post请求,与之前的get请求基本类似,只不过请求参数在bodyparams里面了,这个也很简单,通过@Body注解来指定一个方法作为HTTP请求主体
@POST("shipin_kg/shipin_kg")
public Observable<MovieModel> getMovieLists(@Header("apikey") String apikey, @Body MoviePostModel postModel);
案例4
我们在post请求的时候会遇到一种情况就是content-type被指定为某一种格式了如果服务端告诉你,我们的请求一定要用x-www-form-urlencoded,那么之前说的那种@body就不起作用了,这个时候我们@FormUrlEncoded去表明这个content-type类型,同时要用@Field去处理每一个键值对
@FormUrlEncoded
@POST("product_tool/tool/stagequan")
Observable<ResponseModel> upload(@FieldMap Map<String, String> params);
当然一个个写@Field也很烦,可以直接用@FieldMap去统一用map来处理
案例5
上传文件时候content-type一般都是multipart/form-data,所以这边要加上 **@Multipart **注解,同时每个请求部分需要使用 **@Part **来注解。这边用七牛上传文件来说明
@Multipart
@POST("http://upload.qiniu.com/")
Call<ResponseBody> uploadImage(@PartMap Map<String, RequestBody> params);
同样使用了@PartMap **
来看看RequestBody**是怎么创建的
public static RequestBody create(final MediaType contentType, final File file)
public static RequestBody create(MediaType contentType, String content)
public static RequestBody create(final MediaType contentType, final byte[] content)
找了3个基本方法,它是为了告诉我们,你可以通过contentType以及内容组成任意一个RequestBody对象
RequestBody body = RequestBody.create(MediaType.parse("image/jpeg"),
new File(Environment.getExternalStorageDirectory().getPath() + "/PictureTest/saveTemp.jpg"));
params.put("file", body);
params.put("token", RequestBody.create(MediaType.parse("text/plain"), token));
params.put("x:jsonbody", RequestBody.create(MediaType.parse("text/plain"), "{}"));
等一下,产品经理叫我过去了,说你现在仅仅提示上传还不行,要给我把上传进度加上去,怎么搞呢?显然要从ResponseBody身上下手。来段我抄的代码吧
public class ProgressRequestBody extends RequestBody {
public interface OkHttpProgressListener {
void onProgress(long currentBytesCount, long totalBytesCount);
}
//实际的待包装请求体
private final RequestBody requestBody;
//进度回调接口
private final OkHttpProgressListener progressListener;
//包装完成的BufferedSink
private BufferedSink bufferedSink;
public ProgressRequestBody(RequestBody requestBody, OkHttpProgressListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
/**
* 重写调用实际的响应体的contentType
* @return MediaType
*/
@Override
public MediaType contentType() {
return requestBody.contentType();
}
/**
* 重写调用实际的响应体的contentLength
* @return contentLength
* @throws IOException 异常
*/
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
/**
* 重写进行写入
* @param sink BufferedSink
* @throws IOException 异常
*/
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (null == bufferedSink) {
bufferedSink = Okio.buffer(sink(sink));
}
requestBody.writeTo(bufferedSink);
//必须调用flush,否则最后一部分数据可能不会被写入
bufferedSink.flush();
}
/**
* 写入,回调进度接口
* @param sink Sink
* @return Sink
*/
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
//当前写入字节数
long writtenBytesCount = 0L;
//总字节长度,避免多次调用contentLength()方法
long totalBytesCount = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
//增加当前写入的字节数
writtenBytesCount += byteCount;
//获得contentLength的值,后续不再调用
if (totalBytesCount == 0) {
totalBytesCount = contentLength();
}
progressListener.onProgress(writtenBytesCount, totalBytesCount);
}
};
}
}
使用的时候,只需要在刚才file的那个RequestBody上包裹一层就行了
params.put("file", new ProgressRequestBody(body, new ProgressRequestBody.OkHttpProgressListener() {
@Override
public void onProgress(long currentBytesCount, long totalBytesCount) {
Log.d("MainActivity", currentBytesCount + " " + totalBytesCount);
}
}));
案例6
刚才看过了上传,现在来看看下载。这边只要借鉴了小凳子提供的下载方法一般情况下retrofit是将整个文件都读进内存里面的,这样会造成OOM,所以大文件下载需使用@Streaming,同时我们也需要使用动态地址以便于下载不同的文件,这边使用@Url来填充
@Streaming
@GET
Call<ResponseBody> downloadFileWithFixedUrl(@Url String url);
剩下的就是保存文件了
Response<ResponseBody> response=api.downloadFileWithFixedUrl("http://7b1g8u.com1.z0.glb.clouddn.com/app_newkey_release_8_4.apk").execute();
try {
if (response != null && response.isSuccessful()) {
//文件总长度
long fileSize = response.body().contentLength();
long fileSizeDownloaded = 0;
is = response.body().byteStream();
File file = new File(Environment.getExternalStorageDirectory().getPath() + File.separator + "app_newkey_release_8_4.apk");
if (file.exists()) {
file.delete();
}
else {
file.createNewFile();
}
fos = new FileOutputStream(file);
int count = 0;
byte[] buffer = new byte[1024];
while ((count = is.read(buffer)) != -1) {
fos.write(buffer, 0, count);
fileSizeDownloaded += count;
subscriber.onNext("file download: " + fileSizeDownloaded + " of " + fileSize);
}
fos.flush();
subscriber.onCompleted();
}
else {
subscriber.onError(new Exception("接口请求异常"));
}
} catch (Exception e) {
subscriber.onError(e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
具体使用
无论你是何种请求方式,在app上面调用的方式基本上都是差不多的,我就拿第一个天气预报的接口加以说明
WeatherApi api = Retrofit2Utils.getInstance(getApplicationContext()).enableCache(true)
.getRetrofit("http://apis.baidu.com/apistore/").create(WeatherApi.class);
subscription = api.getWeatherModels("a7802d983b3d58ed6e70ed71bb0c7f14", "南京") .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<WeatherModel>() {
@Override public void onCompleted() {
}
@Override public void onError(Throwable e) {
}
@Override public void onNext(WeatherModel weatherModel) {
if (!subscription.isUnsubscribed()) {
Log.d("MainActivity", (weatherModel.getRetData().getCity() + " "+weatherModel.getRetData().getDate() + "-" + weatherModel.getRetData().getTime() + " " + weatherModel.getRetData().getWeather()));
}
}
});
我这里使用了缓存操作,这个后面会加以说明。同时使用了Rxjava对请求的线程切换以及对返回结果进行调度
自定义解析
我们有时候会遇到这种特定的数据格式
{
"status": "success",
"data": [
{
"id": "575e53a771d03f50a0bff5df"
}
],
"msg": "操作成功"
}
这是一种list格式,并且数据都是在data里面的,但是我需要直接将data对象直接返回过来而不是整体返回,这样就设计到重新定义gson的解析。重写这个功能其实也是相当简单,我们直接照抄GsonConverterFactory.create(),看他是怎么实现的。我列举出关键代码
public class ListGsonResponseBodyConverter<T> implements Converter<ResponseBody, List<T>> {
private final Gson gson;
Class<T> class_;
ListGsonResponseBodyConverter(Gson gson, Class<T> class_) {
this.gson = gson;
this.class_=class_;
}
@Override public List<T> convert(ResponseBody value) throws IOException {
List<T> models=GsonUtils.getModelListValue2(value.string(), class_);
value.close();
return models;
}
}
照抄GsonResponseBodyConverter,把convert方法重写就行了
参照范例,你可以定义任意一种解析数据的方式
缓存
可以通过这篇文章Retrofit 源码解读之离线缓存策略的实现学习到Retrofit缓存的一些知识,真正实践时我是在这里发现如何使用的Github
public class CacheInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//如果没有网络,则启用 FORCE_CACHE
if(!isNetworkConnected()) {
request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build();
}
Response originalResponse = chain.proceed(request);
if(isNetworkConnected()) {
//有网的时候读接口上的@Headers里的配置
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder() .header("Cache-Control", cacheControl) .removeHeader("Pragma") .build();
} else {
return originalResponse.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=3600") .removeHeader("Pragma") .build();
}
}
public static boolean isNetworkConnected() {
Context context = Retrofit2Utils.context;
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
}
日志查看
之前一个小伙伴在看完本篇代码之后,提出“如何查看我真正的网络请求连接”,因为在配置model的过程中难免会粗心大意少参数或者相对url写的不完整之类的。这个问题也是很好处理的,
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
添加okhttp的log库,并且在初始化的时候配置到okhttp上
HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
okhttpBuilder.addInterceptor(interceptor);
这样,在请求的时候,就会有如下Log出现在logcat里面,方便查阅
超时重连
Retrofit2默认没有提供超时重连的方法,所以这部分我们也需要自己实现。恰巧Rxjava有retryWhen这个操作符,所以我们就从这个地方下手
retryWhen是当这个Observable发生发射错误的,在提供给它一次发射的机会。这个函数将重新产生一个Observable,如果这个Observable重新发射一项数据,那么就重写订阅,如果返回onError,那么就直接通知观察者后停止
public class CustomerRetryWhen implements Func1<Observable<? extends Throwable>, Observable<?>> {
int retryTimes=3;
int timeExtra=5;
public CustomerRetryWhen(int retryTimes, int timeExtra) {
this.retryTimes=retryTimes;
this.timeExtra=timeExtra;
}
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.zipWith(Observable.range(1, retryTimes + 1), new Func2<Throwable, Integer, Object>() {
@Override
public Object call(Throwable throwable, Integer integer) {
return new CombineClass(throwable, integer);
}
}).flatMap(new Func1<Object, Observable<?>>() {
@Override
public Observable<?> call(Object o) {
CombineClass combineClass=(CombineClass) o;
if (combineClass.throwable instanceof ConnectException
|| combineClass.throwable instanceof SocketTimeoutException
|| combineClass.throwable instanceof TimeoutException
|| combineClass.integer.intValue()<retryTimes + 1) {
Log.d("CustomerRetryWhen", "重试");
return Observable.timer(timeExtra, TimeUnit.SECONDS);
}
return Observable.error(combineClass.throwable);
}
});
}
public class CombineClass {
Throwable throwable;
Integer integer;
public CombineClass(Throwable throwable, Integer integer) {
this.throwable=throwable;
this.integer=integer;
}
}
}
我这里就简单的实现了3次超时重连,每次重连时间间隔是5s
生命周期的管理
Retrofit本身跟Activity或者Fragment的生命周期是毫无任何关联的,但是这里有一种情况就是我们可能需要在页面关闭的时候去断开链接,不然可能会出现意想不到的一些问题,比如dialog或者fragment的操作上会丢失状态等。当然我们可以选择重写onStop或者onDestory,在其中关闭这些Subscription,但是就算你用CompositeSubscription来统一处理,这样还是太麻烦,需要每个界面都得去处理实现。幸好Rxjava提供了lift函数,帮助我们去处理这种问题
lift是针对事件序列进行处理和在发送的操作符,虽然rxjava不推荐你直接自定义lift去实现功能,我们看下源码
lift()
那个Subscriber是新的了,指代对象不同了
啰嗦了半天,来看看我们这个需求的实现。我的思路是在添加Subscription的时候将它记录到一个list中,同时,将它的生命周期也一并带入保存。当外层activity或者fragment执行到这个生命周期的,遍历list,循环unsubscribe
public enum ActivityLifeCycle {
OnCreate, OnStart, OnRestart, OnReasume, OnPause, OnStop, OnDestroy
}
这是我们要处理的生命周期,放到一个枚举里面方便操作
public class BaseActivity extends AppCompatActivity {
List<LifeCircleClass> lifeCircleClasses;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifeCircleClasses=new ArrayList<>();
}
public class LifeCircleClass {
Subscription subscription;
ActivityLifeCycle lifeCycle;
public LifeCircleClass(Subscription subscription, ActivityLifeCycle lifeCycle) {
this.subscription = subscription;
this.lifeCycle = lifeCycle;
}
}
public void addSubscription(Subscription subscription, ActivityLifeCycle lifeCycle) {
LifeCircleClass circleClass=new LifeCircleClass(subscription, lifeCycle);
lifeCircleClasses.add(circleClass);
}
@Override
protected void onDestroy() {
super.onDestroy();
for (LifeCircleClass lifeCircleClass : lifeCircleClasses) {
if (lifeCircleClass.lifeCycle==ActivityLifeCycle.OnDestroy) {
lifeCircleClass.subscription.unsubscribe();
}
}
}
}
注意看看BaseActivity的实现,里面有一个list,作为管理subscription对象集合
public class LifeCircleOperator<T> implements Observable.Operator<T, T> {
BaseActivity activity;
ActivityLifeCycle lifeCycle;
public LifeCircleOperator(BaseActivity activity, ActivityLifeCycle lifeCycle) {
this.activity=activity;
this.lifeCycle=lifeCycle;
}
@Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
activity.addSubscription(subscriber, lifeCycle);
return subscriber;
}
}
这里自定义了lift(),做了添加subscriber到BaseActivity的功能
使用时候吗,直接lift()即可
lift(new LifeCircleOperator(this, ActivityLifeCycle.OnDestroy))
拦截器
在项目开发中,我们可能写着写着就发现,某些接口都有一些固定的参数需要上传。比如机器码,比如token等等,在这种情况下,如果我们依然在每一个接口中都加上这些重复参数,势必会造成代码看上去让人觉得眼花缭乱,这时候,有需要我们定制拦截器了。
什么是拦截器,拦截器是一个能够监视、重写和重试请求的强大机制。简单的说我们可以使用它去获得请求的内容还有响应的内容
请注意一下多个拦截器的走向
来看下最基础的拦截器示例
public class KuaidiInterceptor1 implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
可以看到,在拦截器的接口实现中有两个重要的参数Request和Response,它们分别代表发出前的请求和接收到的响应。而Chain类型的参数保存了Request和Response的相关数据。
看一个简单的拦截器的示例,这是一个获取快递单信息的请求,我添加了两个拦截器。一个是我自己的KuaidiInterceptor1,还有一个是chuck的。
public void addInterceptorParams() {
Retrofit2Utils retrofit2Utils=Retrofit2Utils.getInstance();
retrofit2Utils.addExtraInterceptor(new KuaidiInterceptor1());
retrofit2Utils.addExtraInterceptor(new ChuckInterceptor(getApplicationContext()));
KuaidiApi api =retrofit2Utils.getRetrofit("http://www.kuaidi100.com/").create(KuaidiApi.class);
api.getKuaidiInfo("yunda", "3101449726243")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<KuaidiModel>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(KuaidiModel model) {
for (KuaidiModel.DataBean dataBean : model.getData()) {
Log.d("MainActivity", dataBean.getContext());
}
}
});
}
具体看看我们的拦截器吧,先演示如何添加Header参数。这里我添加三个Header参数version、access-token、user-token
public class KuaidiInterceptor1 implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl.Builder url = request.url().newBuilder();
Request.Builder newRequestBuilder = request.newBuilder()
.addHeader("version", "version")
.addHeader("access-token", "access-token")
.addHeader("user-token", "user-token")
.url(url.build());
Request newRequest = newRequestBuilder.build();
Response response = chain.proceed(newRequest);
return response;
}
}
可以通过chuck看到header已经添加完成
添加Header
接下来我想看看传递的参数并且添加额外的参数
HttpUrl.Builder url = request.url().newBuilder();
if (request.method().equals("GET")) {
for (String s : request.url().queryParameterNames()) {
Log.d("KuaidiInterceptor1", request.url().queryParameter(s));
}
url.addQueryParameter("key", "value");
}
Get参数打印
额外参数添加
最后看看Post请求方式参数的添加,先看看Form表单形式post请求,我也是打印参数还有添加post参数
@FormUrlEncoded
@POST("http://www.kuaidi100.com/query?type=yunda&postid=3101449726243&temp=0.057969198168630776")
Observable<KuaidiModel> getKuaidiInfo(@FieldMap Map<String, String> params);
if (request.method().equals("POST")) {
RequestBody requestBody = request.body();
if (requestBody instanceof FormBody) {
FormBody.Builder formBuilder = new FormBody.Builder();
for (int i = 0; i < ((FormBody) requestBody).size(); i++) {
Log.d("KuaidiInterceptor1", ((FormBody) requestBody).encodedName(i) + " " + ((FormBody) requestBody).encodedValue(i));
formBuilder.addEncoded(((FormBody) requestBody).encodedName(i), ((FormBody) requestBody).encodedValue(i));
}
formBuilder.add("key", "value");
request = request.newBuilder().method(request.method(), formBuilder.build()).build();
}
}
Post参数打印
额外参数添加
最后看看post传递json的方式
@POST("http://www.kuaidi100.com/query?type=yunda&postid=3101449726243&temp=0.057969198168630776")
Observable<KuaidiModel> getKuaidiInfo(@Body MoviePostModel postModel);
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
String oldParamsJson = buffer.readUtf8();
Gson gson = new Gson();
HashMap map = gson.fromJson(oldParamsJson, HashMap.class);
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Log.d("KuaidiInterceptor1", entry.getKey() + " " + entry.getValue());
}
map.put("key", "value");
String newParams = gson.toJson(map);
request = request.newBuilder().post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), newParams)).build();
Post参数打印
额外参数添加
最后是打印返回值
if (response.body()!=null) {
MediaType mediaType=response.body().contentType();
String body=response.body().string();
Log.d("KuaidiInterceptor1", "response.code():" + response.code()+" "+body);
return response.newBuilder().body(ResponseBody.create(mediaType, body)).build();
}
注意这边response.body().string()一旦执行了,那么之前的response中相应的值就会为空了,所以这边重新生成了一个新的Response对象
通过拦截器,统一处理重复的参数,生成新的http请求,是不是很舒服.
HTTPS
苹果将于2017.1.1强行要求新上架的APP网络请求走HTTPS协议,苹果要改接口,android不可能不改,难道还部署两套环境吗?所以我们也要看看Retrofit如何去解决HTTPS的访问。
如果是CA机构颁发的证书,OKHttp默认是信任这些HTTPS的,但是其他情况下就不行了,比如12306。庆幸的是鸿洋大神已经提供了解决方案,我们直接拷贝到自己的工程里面去使用即可,我这边就简单的说下如何使用
OkHttpClient.Builder okhttpBuilder=new OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS);
HttpsUtils.SSLParams sslParams = null;
try {
sslParams = HttpsUtils.getSslSocketFactory(new InputStream[]{getAssets().open("12306.cer")}, null, null);
OkHttpClient client=okhttpBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}).sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager).build();
Request.Builder reqBuilder=new Request.Builder();
Request request=reqBuilder.url("https://kyfw.12306.cn/otn/leftTicket/init").build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
Log.d("MainActivity", response.body().string());
}
});
} catch (IOException e) {
e.printStackTrace();
}
主要是调用OkHttpClient.Builder中的sslSocketFactory与hostnameVerifier去分别设置HTTPS所需的socket factory与trust manager以及确认HTTPS主机验证。
如果信任所有HTTPS证书,那么直接getSslSocketFactory(null, null, null)一站式解决,当然这也存在一定的安全风险
如果仅仅是单向认证,那么直接将证书cer导出就行了,放到assets目录下即可,如本文
如果是级别较高的双向认证,那么就需要同时提供cer与bks两种证书了,同时还需要证书的密码,一共三个参数
具体证书的生成制作,请继续参考鸿洋的博客Android Https相关完全解析 当OkHttp遇到Https
-
本篇博文上的代码已经共享到Github上,欢迎大家多多提意见
-
参考文章
网友评论