最近Android老项目改造, 之前老代码一直用AndroidHttpClient实现网络请求,存在以下问题
1. 版本太老旧, 最近无更新;
2. 网络请求模块代码太散乱,不好管理,
3. 不支持SSL/TLS1.2以上版本
反正就是旧时代的产物, 需要进行整改.
于是就开始网络请求模块重构, 组训以下原则:
1. 修改网络请求不能修改之前的业务到吗逻辑,包括其他人的代码, 避免引入bug;
2.网络请求只能用一两个类实现, 统一管理整个http请求功能, 便于逻辑控制;
选择用当下比较流行的okhttp进行改造.
1. 历史上Http请求库优缺点
借用oncealong的介绍
在讲述OkHttp之前, 我们看下没有OkHttp的时代, 我们是如何完成http请求的.
在没有OkHttp的日子, 我们使用HttpURLConnection
或者HttpClient
. 那么这两者都有什么优缺点呢? 为什么不在继续使用下去呢?HttpClient
是Apache基金会的一个开源网络库, 功能十分强大, API数量众多, 但是正是由于庞大的API数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展, 所以Android团队在提升和优化HttpClient方面的工作态度并不积极.HttpURLConnection
是一种多用途, 轻量极的HTTP客户端, 提供的API比较简单, 可以容易地去使用和扩展. 不过在Android 2.2版本之前, HttpURLConnection
一直存在着一些令人厌烦的bug. 比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
// 这是一个2.2版本之前的bug
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
因此, 一般的推荐是在2.2之前, 使用HttpClient
, 因为其bug较少. 在2.2之后, 推荐使用HttpURLConnection
, 因为API简单, 体积小, 并且有压缩和缓存机制, 并且Android团队后续会继续优化HttpURLConnection
.
但是, 上面两个类库和OkHttp
比起来就弱爆了, 因为OkHttp不仅具有高效的请求效率, 并且提供了很多开箱即用的网络疑难杂症解决方案.
- 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据
- 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时
- 支持GZIP, 可以压缩下载体积
- 响应缓存可以直接避免重复请求
- 会从很多常用的连接问题中自动恢复
- 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP
- OkHttp还处理了代理服务器问题和SSL握手失败问题
使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果你用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。
作者:oncealong
链接:https://www.jianshu.com/p/ca8a982a116b
由于目前网络上关于okhttp讲理论的人比较多, 笔者就不赘述, 直接上代码, 函数都有详细注释, 观众可以直接复制使用; 我会把完整的module到吗放到个人资源,可以直接下载: https://download.csdn.net/download/zhanghao_Hulk/16020524
基本的okhttp实现
2.1.1 使用SSL证书验证的 Okhttp client
适用于https 代码如下, 目前用的比较多, 因为大部分网站和服务器都是https了.
其中使用的sslSocketFactory和TrustManager参考下文的 SSLUtils
/**
* 创键天机的服务器请求的 OK http client builder
* <p>其中包含: 天机服务器的证书验证, token和参数验证等等.
* @return
*/
public static OkHttpClient.Builder createSslOkHttpBuilder() {
return new OkHttpClient.Builder()
.cookieJar(sMyCookieJar)
.connectTimeout(CONNECTED_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.sslSocketFactory(getSSLSocketFactory(), getTrustManager())
.hostnameVerifier(getCustomHostnameVerifier())
.authenticator(getAuthenticator())
.addInterceptor(getHeaderInterceptor())
.addInterceptor(getUrlParamInterceptor());
// 其中的拦截器请参考完整代码中举例,如果用不到也可以删掉,或者自定义
}
/**
* 获取天机的服务器请求的 OK http client builder(优先使用缓存)
* <p>其中包含: 天机服务器的证书验证, token和参数验证等等.
* @return
*/
public static OkHttpClient.Builder getOkHttpBuilder() {
if (sOkHttpBuilder == null) {
sOkHttpBuilder = createSslOkHttpBuilder();
}
return sOkHttpBuilder;
}
/**
* 获取天机的服务器请求的 OK http client (金莲使用缓存)
* <p>其中包含: 天机服务器的证书验证, token和参数验证等等.
* @return
*/
public static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) {
synchronized (OkHttpManager.class) {
if (sOkHttpClient == null) {
sOkHttpClient = getOkHttpBuilder()
//通用接口请求需要打印response body 日志,
// 但是在文件下载响应中不能使用Level.BODY,否则会导致下载请求被卡主,
// 直到文件下载完成后, execute()才返回,或者异步回调onResponse()或者,
// 导致事件非常耗时,无法实现下载精度和断点续传
.addInterceptor(sLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
}
}
}
return sOkHttpClient;
}
/**
* 获取下载 OK http client
* <p>下载需要使用进度监听器,不能使用缓存单利的 ok client, 不许创建一个新的 client
* @param listener
* @return
*/
public static OkHttpClient getDownloadSslClient(ProgressListener listener) {
//下载因为需要使用进度监听器,不能使用单利的 client he builder
// 必须每次创键一个新的client,传入回调接口,
// 避免进度拦截器越加越多,后面的的下载进度篡改前面的下载进度
OkHttpClient.Builder clientBuilder = createSslOkHttpBuilder();
if (listener != null) {
clientBuilder.addNetworkInterceptor(new ProgressInterceptor(listener));
}
return clientBuilder.build();
}
public static OkHttpClient getDownloadSslClient() {
return getDownloadSslClient(null);
}
2.1.2 普通的 Okhttp client, 适用于http
函数方法都有介绍
/**
* 创键极简素版本 OK http client builder.
* @param listener
* @return
*/
public static OkHttpClient.Builder createPlainHttpBuilder(ProgressListener listener) {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(sMyCookieJar)
.connectTimeout(CONNECTED_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
if (listener != null) {
builder.addNetworkInterceptor(new ProgressInterceptor(listener));
}
return builder;
}
/**
* 浏览器下载OK http client, 不含有证书和域名等等验证
* 浏览器下载可能其他服务器,不是天机服务器,不能验证生疏等等
* @return
*/
public static OkHttpClient getBrowserDownloadClient(ProgressListener listener) {
OkHttpClient.Builder clientBuilder = getPlainHttpBuilder(listener);
return clientBuilder.build();
}
public static OkHttpClient getBrowserDownloadClient() {
return getBrowserDownloadClient(null);
}
/**
* 创键极简素版本 OK http client builder.
* @return
*/
public static OkHttpClient.Builder getPlainHttpBuilder() {
if (sPlainHttpBuilder == null) {
sPlainHttpBuilder = createPlainHttpBuilder();
}
return sPlainHttpBuilder;
}
/**
* 创键极简素版本 OK http client builder.
* @return
*/
public static OkHttpClient.Builder getPlainHttpBuilder(ProgressListener listener) {
if (listener != null) {
return createPlainHttpBuilder(listener);
} else {
return getPlainHttpBuilder();
}
}
/**
* 创键极简素版本 OK http client builder.
* @return
*/
public static OkHttpClient.Builder createPlainHttpBuilder() {
return createPlainHttpBuilder(null);
}
2.2 下载进度进本实现
使用response精度拦截器可实现监控数据管道进度, 从而实现下载进度
package com.hulk.android.http.ok;
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Response;
/**
* 下载进度拦截器
* @author: zhanghao
* @Time: 2021-02-24 22:04
*/
public class ProgressInterceptor implements Interceptor {
private static final String TAG = "ProgressInterceptor";
ProgressListener listener;
public ProgressInterceptor(ProgressListener listener) {
this.listener = listener;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (listener == null) {
Log.i(TAG, "intercept: listener is null");
return response;
}
Log.i(TAG, "intercept: listener is " + listener);
//这里将ResponseBody包装成我们的ProgressResponseBody
return response.newBuilder()
.body(new ProgressResponseBody(response, listener))
.build();
}
}
响应数据管道监控实现类
package com.hulk.android.http.ok;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
/**
* 下载进度响应
* @author: zhanghao
* @Time: 2021-02-24 21:58
*/
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
private Response response;
public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener){
this.responseBody = responseBody;
this.progressListener = progressListener;
onPreExecute();
}
public ProgressResponseBody(Response response, ProgressListener progressListener){
this.response = response;
this.responseBody = response.body();
this.progressListener = progressListener;
onPreExecute();
}
private void onPreExecute() {
if (progressListener != null) {
progressListener.onPreExecute(contentLength(), response);
}
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null){
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
/**
* 回调下载精度函数
* @param source
* @return
*/
private Source source(Source source){
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = 0;
try {
bytesRead = super.read(sink,byteCount);
//不断统计当前下载好的数据
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
boolean done = bytesRead == -1;
//接口回调
if (progressListener != null) {
progressListener.update(totalBytesRead, done, response);
}
} catch (IOException e) {
if (progressListener != null) {
progressListener.onError(e, response);
}
throw e;
}
return bytesRead;
}
};
}
}
2.3 okhttp中以上代码的函数注释
/**
* 创键okhttp Call,用于执行Request
* @param request
* @return
*/
public static Call createCall(Request request) {
OkHttpClient okHttpClient = getOkHttpClient();
Call call = okHttpClient.newCall(request);
return call;
}
/**
* 创建请求POST/GET
* @param request
* @return
* @throws IOException
*/
public static Response executeRequest(Request request) throws IOException {
Call call = createCall(request);
return call.execute();
}
/**
* 请求服务器, eg: 上传文件
* @param url
* @param requestBody
* @return
* @throws IOException
*/
public static Response executeRequest(String url, RequestBody requestBody) throws IOException {
if (requestBody == null) {
logw("executeRequest: requestBody is null");
return null;
}
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = OkHttpManager.executeRequest(request);
boolean isSuccessful = response != null ? response.isSuccessful() : false;
if (isSuccessful) {
logi("executeRequest response: " + response);
} else {
logw("executeRequest Failed response: " + response);
}
return response;
}
/**
* 请求服务器, eg: 上传文件
* @param url
* @param multipartBody 表格提交数据 eg: 文件上传
* @return
* @throws IOException
*/
public static Response uploadMultipartData(String url, MultipartBody multipartBody) throws IOException {
if (multipartBody == null) {
logw("uploadMultipartData: multipartBody is null");
return null;
}
return executeRequest(url, multipartBody);
}
2.4 常用封装好的方法工具类
/**
* Ok http 请求工具类
* @author hulk
*/
public class OkHttpUtils {
private static final String TAG = "OkHttpUtils";
public static final int REQUEST_TIMEOUT = 30 * 1000;
public static final String CONTENT_TYPE_PROTOBUF = OkHttpManager.CONTENT_TYPE_PROTOBUF;
/**
* 发起 http het 请求
* <p>可用于文下载和普通接口请求
* <p>使用完成一定要记得关闭 ResponseBody(close)
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static Response sendOkHttpGetRequest(String url) throws IOException {
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.get();
logi("sendOkHttpGetRequest url: " + url);
Response response = OkHttpManager.executeRequest(requestBuilder.build());
return response;
}
/**
* 发起 http het 请求
* <p>可用于文下载和普通接口请求
* <p>使用完成一定要记得关闭 ResponseBody(close)
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static ResponseBody sendHttpGetRequest(String url)
throws IOException, HttpException {
Response response = sendOkHttpGetRequest(url);
int code = response.code();
if (code != OkHttpStatus.SC_OK) {
LogUtil.e(TAG, "sendHttpGetRequest: Failed HttpException code= " + code);
throw new HttpException(code, url);
} else {
LogUtil.i(TAG, "sendHttpGetRequest: SC_OK");
}
ResponseBody responseBody = response.body();
return responseBody;
}
/**
* 发起 http het 请求
* <p>可用于文下载和普通接口请求
* <p>使用完成一定要记得关闭输入流(close)
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static InputStream sendInputGetRequest(String url)
throws IOException, HttpException {
ResponseBody responseBody = sendHttpGetRequest(url);
if (responseBody != null) {
InputStream inputStream = responseBody.byteStream();
return inputStream;
} else {
LogUtil.e(TAG, "sendInputGetRequest responseBody is null ");
}
return null;
}
/**
* Get方式请求网络接口,返回byte数组,可以使json字字符串,或者图片视频等等
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static byte[] sendDataGetRequest(String url) throws IOException, HttpException {
InputStream inputStream = sendInputGetRequest(url);
if (inputStream != null) {
try {
byte[] data = StreamTool.read(inputStream);
if (data != null) {
logi("sendDataGetRequest success response data.length= " + data.length);
return data;
} else {
logw("sendDataGetRequest failed to read inputStream ");
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} else {
LogUtil.w(TAG, "sendGetDataRequest Content inputStream is null ");
}
return null;
}
/**
* Get方式请求网络接口,返回 String
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static String sendStringGetRequest(String url) throws IOException, HttpException {
byte[] data = sendDataGetRequest(url);
if (data != null) {
String str = new String(data);
return str;
} else {
LogUtil.w(TAG, "sendStringGetRequest: data is null ");
}
return null;
}
/**
* Get方式请求网络接口,返回 String
* @param url
* @return
* @throws IOException
* @throws HttpException
*/
public static HttpResult<String> sendResultGetRequest(String url) throws IOException, HttpException {
HttpResult<String> result = new HttpResult<>(-1, "");
byte[] data = sendDataGetRequest(url);
if (data != null) {
try {
result = parseJsonData(data);
} catch (JSONException e) {
Log.w(TAG, "sendResultGetRequest failed: " + e, e);
}
} else {
result = new HttpResult<>(-1, "data is null");
LogUtil.w(TAG, "sendGetResultRequest: data is null ");
}
return result;
}
/**
* 解析服务器返回的通用json数据(存在错误码和错误信息)
* @param resultData
* @return
* @throws JSONException
*/
public static HttpResult<String> parseJsonData(byte[] resultData) throws JSONException {
HttpResult<String> result = new HttpResult<>(-1, "");
String resultStr = new String(resultData);
result.data = resultStr;
if (!TextUtils.isEmpty(resultStr)) {
JSONObject resultJson = new JSONObject(resultStr);
result.code = resultJson.optInt("code");
result.msg = resultJson.optString("errorMessage");
if (result.code != 0) {
result.detail = resultStr;
LogUtil.w(TAG, "parseJsonData failedresult: " + result);
} else {
LogUtil.i(TAG, "parseJsonData result: " + result);
}
} else {
LogUtil.w(TAG, "parseJsonData: resultStr is empty: " + result);
}
return result;
}
/**
* Post方式请求网络接口,返回byte数组,可以使json字字符串,或者图片视频等等
* @param url 服务器接口地址
* @param mediaType okhttp3.MediaType onject eg: "application/json" or CONTENT_TYPE_PROTOBUF...
* @param postData
* @return
* @throws RuntimeException
* @throws IOException
* @throws HttpException
*/
public static byte[] sendHttpPostRequest(String url, MediaType mediaType, byte[] postData)
throws IOException, RuntimeException, HttpException {
if (TextUtils.isEmpty(url)) {
logw("sendHttpPostRequest: failed for invalid url: " + url);
throw new IllegalArgumentException("url is null");
}
if (TextUtils.isEmpty(url)) {
logw("sendHttpPostRequest: postData is null");
throw new IllegalArgumentException("postData is null");
}
if (mediaType == null) {
logw("sendHttpPostRequest: mediaType is null");
throw new IllegalArgumentException("mediaType is null");
}
RequestBody requestBody = RequestBody.create(mediaType, postData);
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(requestBody);
Response response = OkHttpManager.executeRequest(requestBuilder.build());
ResponseBody responseBody = null;
logw("sendHttpPostRequest url: " + url + ", postData length: " + postData.length);
try {
int code = response.code();
logw("sendHttpPostRequest Http Status Code " + code);
if (code != OkHttpStatus.SC_OK) {
LogUtil.e(TAG, "Throw HttpException code= " + code + ", url path: " + UrlParser.getUrlPath(url));
throw new HttpException(code, url);
}
responseBody = response.body();
if (responseBody != null) {
InputStream inputStream = responseBody.byteStream();
if (inputStream != null) {
byte[] data = StreamTool.read(inputStream);
if (data != null) {
//logi("sendHttpGetRequest success response data: " + Arrays.toString(data));
logi("sendHttpPostRequest success response data.length= " + data.length);
return data;
} else {
logw("sendHttpPostRequest failed to read inputStream ");
}
} else {
LogUtil.w(TAG, "sendHttpPostRequest Content inputStream is null ");
}
} else {
LogUtil.e(TAG, "sendHttpPostRequest HttpEntity is null ");
}
} finally {
if (responseBody != null) {
responseBody.close();
}
}
return null;
}
/**
* Post方式请求网络接口,返回byte数组,可以使json字字符串,或者图片视频等等
* @param url 服务器地址
* @param contentType "application/json" or CONTENT_TYPE_PROTOBUF...
* @param url
* @return
* @throws RuntimeException
* @throws IOException
* @throws HttpException
*/
public static byte[] sendHttpPostRequest(String url, String contentType, byte[] postData)
throws IOException, RuntimeException, HttpException {
MediaType mediaType = MediaType.parse(contentType);
return sendHttpPostRequest(url, mediaType, postData);
}
/**
* Post方式请求网络接口,返回byte数组
* <p>contentType = CONTENT_TYPE_PROTOBUF;
* @param url 服务器地址
* @param url
* @return
* @throws RuntimeException
* @throws IOException
* @throws HttpException
*/
public static byte[] sendProtoPostRequest(String url, byte[] requestData)
throws IOException, RuntimeException, HttpException {
String contentType = CONTENT_TYPE_PROTOBUF;
return sendHttpPostRequest(url, contentType, requestData);
}
/**
* Post方式请求网络接口,返回byte数组
* <p>contentType = "application/json";
* @param url 服务器地址
* @param url
* @param requestData
* @return
* @throws IOException
* @throws RuntimeException
* @throws HttpException
*/
public static byte[] sendJsonPostRequest(String url, byte[] requestData)
throws IOException, RuntimeException, HttpException {
String contentType = OkHttpManager.CONTENT_TYPE_JSON;
return sendHttpPostRequest(url, contentType, requestData);
}
/**
* Post方式请求网络接口,返回 String
* <p>contentType = "application/json";
* @param url 服务器地址
* @param url
* @param requestData
* @return
* @throws IOException
* @throws RuntimeException
* @throws HttpException
*/
public static String sendStringJsonPostRequest(String url, byte[] requestData)
throws IOException, RuntimeException, HttpException {
byte[] res = sendJsonPostRequest(url, requestData);
return new String(res, "UTF-8");
}
/**
* Post发送json数据 (通用)
* @param url
* @param requestData
* @return HttpResult<String> 对象,其中data为json eg: {"code":0,"errorMessage":"Success"}
*/
public static HttpResult<String> sendJsonPostRequestCommon(String url, byte[] requestData) {
HttpResult<String> result = new HttpResult<>(-1, "");
try {
byte[] resultData = sendJsonPostRequest(url, requestData);
if (resultData != null) {
try {
result = parseJsonData(resultData);
} catch (JSONException e) {
Log.w(TAG, "sendGetResultRequest failed: " + e, e);
}
} else {
result = new HttpResult<>(-1, "data is null");
LogUtil.w(TAG, "sendGetResultRequest: data is null ");
}
} catch (Exception e) {
String detail = e + ", url= " + url;
if (e instanceof HttpException) {
result.code = ((HttpException)e).code();
}
result.msg = e.getMessage();
result.detail = detail;
result.error = e;
LogUtil.e(TAG, "sendJsonPostRequestCommon failed: " + e + ", detail= " + detail, e);
}
return result;
}
/**
* 请求服务器, eg: 上传文件
* @param url
* @param requestBody
* @return
* @throws IOException
*/
public static Response executeRequest(String url, RequestBody requestBody) throws IOException {
return OkHttpManager.executeRequest(url, requestBody);
}
/**
* 请求服务器, eg: 上传文件
* @param url
* @param multipartBody 表格提交数据 eg: 文件上传
* @return
* @throws IOException
*/
public static Response uploadMultipartData(String url, MultipartBody multipartBody) throws IOException {
return OkHttpManager.uploadMultipartData(url, multipartBody);
}
/**
* 上传文件(可多个)
* @param url
* @param formDataName 表数据自丢按名称,自定义, 与服务端协商 eg: file, data ...
* @param filePaths
* @param url
* @param formDataName
* @param filePaths
* @return
* @throws IOException
*/
public static Response uploadFiles(String url, String formDataName, String... filePaths) throws IOException {
MultipartBody.Builder multipartBuilder = OkRequestUtils.createMultipartBody(formDataName, filePaths);
if (multipartBuilder == null) {
logw("uploadFiles: multipartBuilder si null");
return null;
}
MultipartBody requestBody = multipartBuilder.build();
Response response = uploadMultipartData(url, requestBody);
return response;
}
/**
* 上传文件
* @param url
* @param formDataName
* @param filePath
* @return
* @throws IOException
*/
public static String uploadFile(String url, String formDataName, String filePath) throws IOException {
logi("uploadFile: formDataName=" + formDataName + ", filePath= " + filePath);
Response response = uploadFiles(url, formDataName, filePath);
ResponseBody responseBody = null;
try {
if (response != null) {
responseBody = response.body();
String resStr = new String(responseBody.bytes());
logw("uploadFile: " + resStr);
return resStr;
} else {
logw("uploadFile: response is null");
}
} finally {
if (responseBody != null) {
responseBody.close();
}
}
return "";
}
private static void logw(String msg) {
LogUtil.w(TAG, msg + ", date: " + new Date().toLocaleString());
}
private static void logi(String msg) {
LogUtil.i(TAG, msg + ", date: " + new Date().toLocaleString());
}
}
2.5. SSL证书相关的工具类 SSLUtils
public class SSLUtils {
private static final String TAG = "SSLUtils" ;
private static Context sContext;
static HostnameVerifier sCustomHostnameVerifier = null;
/**
* 证书验证开关: https 和 mqtt 连接是否验证服务端证书
*/
private static boolean sServerCertVerifyEnabled = true;
public static void setContext(Context context) {
SSLUtils.sContext = context;
}
/**
* 设置是否需要验证服务器证书
* @param serverCertVerifyEnabled
*/
public static void setServerCertVerifyEnabled(boolean serverCertVerifyEnabled) {
sServerCertVerifyEnabled = serverCertVerifyEnabled;
}
/**
* 是否需要验证服务器证书
* @return
*/
public static boolean isServerCertVerifyEnabled() {
return sServerCertVerifyEnabled;
}
public static HostnameVerifier getCustomHostnameVerifier() {
if (sCustomHostnameVerifier == null) {
sCustomHostnameVerifier = new NoneHostnameVerifier();
}
return sCustomHostnameVerifier;
}
/**
* 默认信任所有的证书
* <p>最好加上证书认证,主流App都有自己的证书
* @return
*/
public static SSLSocketFactory createSSLSocketFactory(X509TrustManager trustManager) {
SSLSocketFactory sslSocketFactory = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "createSSLSocketFactory: NoSuchAlgorithmException", e);
} catch (KeyManagementException e) {
Log.e(TAG, "createSSLSocketFactory: KeyManagementException", e);
} catch (Exception e) {
Log.e(TAG, "createSSLSocketFactory: ", e);
}
return sslSocketFactory;
}
public static SSLSocketFactory createNoneSSLSocketFactory() {
return createSSLSocketFactory(createTrustNoneManager());
}
public static SSLSocketFactory getSSLSocketFactory() {
return getSSLSocketFactory(sContext);
}
/**
* 信任指定的证书的工厂
* @param context
* @return
*/
public static SSLSocketFactory getSSLSocketFactory(Context context) {
if (context == null) {
throw new NullPointerException("context == null, please call setContext() at first.");
}
try {
CustomTrustManager manager = new CustomTrustManager(context);
manager.setServerCertVerifyEnabled(isServerCertVerifyEnabled());
return createSSLSocketFactory(manager);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "createSSLSocketFactory: NoSuchAlgorithmException", e);
} catch (IOException e) {
Log.e(TAG, "createSSLSocketFactory: IOException", e);
} catch (Exception e) {
Log.e(TAG, "createSSLSocketFactory: ", e);
}
Log.w(TAG, "getSSLSocketFactory: create None SSLSocketFactory");
return createNoneSSLSocketFactory();
}
/**
* 获取本地自定义证书
* @param context
* @return
*/
public static X509TrustManager getLocalCustomManager(Context context) {
if (context == null) {
return null;
}
try {
X509Certificate rootCert = generateCertFromAssets(context,"root_ca.crt");
// 设置算法
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// 设置类型
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
// 不加载外部证书
keyStore.load(null);
// 加载内置的根证书到keyStore中
if (rootCert != null) {
Log.i(TAG, "getLocalCustomManager: add root_ca.crt");
keyStore.setCertificateEntry("root_ca", rootCert);
}
// 还可以添加其他证书
X509Certificate rootCert2 = generateCertFromAssets(context,"root_ca2.crt");
if (rootCert2 != null) {
Log.i(TAG, "getLocalCustomManager: add root_ca2.crt");
keyStore.setCertificateEntry("root_ca2", rootCert2);
}
factory.init(keyStore);
return getFirstX509TrustManager(factory);
} catch (CertificateException e) {
Log.w(TAG, "getLocalCustomManager CertificateException: " + e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, "getLocalCustomManager NoSuchAlgorithmException: " + e);
e.printStackTrace();
} catch (KeyStoreException e) {
Log.w(TAG, "getLocalCustomManager KeyStoreException: " + e);
e.printStackTrace();
} catch (IOException e) {
Log.w(TAG, "getLocalCustomManager IOException: " + e);
e.printStackTrace();
}
return null;
}
/**
* 获得系统默认的 X509TrustManager
* @return
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
public static X509TrustManager getDefaultManager() throws KeyStoreException, NoSuchAlgorithmException {
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore) null);
return getFirstX509TrustManager(factory);
}
/**
* 获取Assets中的内置证书
* @param context
* @param filename
* @return
*/
private static X509Certificate generateCertFromAssets(Context context, String filename) {
try {
InputStream inputStream = context.getAssets().open(filename);
// 生成X509证书
X509Certificate cert = generateX509Cert(inputStream);
return cert;
} catch (CertificateException e) {
Log.w(TAG, "generateCertFromAssets Failed: " + e + ", filename: " + filename);
e.printStackTrace();
} catch (IOException e) {
Log.w(TAG, "generateCertFromAssets Failed: " + e + ", filename: " + filename);
e.printStackTrace();
}
return null;
}
/**
* 获取输入流中的内置X509证书
* @param inputStream 可以使assets里面的输入流,或者FileInputStream
* @return
*/
private static X509Certificate generateX509Cert(InputStream inputStream) throws CertificateException {
// X509方案
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 生成X509证书
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
return cert;
}
public static X509TrustManager getFirstX509TrustManager(TrustManagerFactory trustManagerFactory) {
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
public static X509TrustManager createTrustNoneManager() {
X509TrustManager tm = null;
try {
tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意客户端证书
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new CertificateException("Local X509TrustManager Init Failed");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
} catch (Exception e) {
Log.e(TAG, "createTrustNoneManager: ", e);
}
return tm;
}
/**
* 获得KeyStore.
* @param type 证书类型
* @param keyStorePath
* 密钥库路径
* @param password
* 密码
* @return 密钥库
* @throws Exception
*/
public static KeyStore getKeyStore(String type, String password, String keyStorePath) throws Exception {
// 实例化密钥库
KeyStore ks = KeyStore.getInstance(type);
// 获得密钥库文件流
FileInputStream is = new FileInputStream(keyStorePath);
// 加载密钥库
ks.load(is, password.toCharArray());
// 关闭密钥库文件流
is.close();
return ks;
}
/**
* 获得 JKS KeyStore.
* @param password
* @param keyStorePath
* @return
*/
public static KeyStore getJksKeyStore(String password, String keyStorePath) throws Exception {
return getKeyStore("JKS", password, keyStorePath);
}
/**
* 获得SSLSocketFactory剩下文
* @param password 密钥库密码
* @param keyStore key 密钥库
* @param trustStore 信任库
* 密钥库路径
* @return SSLContext
* @throws Exception
*/
public static SSLContext getSSLContext(String password, KeyStore keyStore, KeyStore trustStore) throws Exception {
// 实例化密钥库
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 初始化密钥工厂
keyManagerFactory.init(keyStore, password.toCharArray());
// 实例化信任库
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// 初始化信任库
trustManagerFactory.init(trustStore);
// 实例化SSL上下文
SSLContext ctx = SSLContext.getInstance("TLS");
// 初始化SSL上下文
ctx.init(keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(), null);
// SSLContext, 可获得SSLSocketFactory
return ctx;
}
/**
* 获得SSLSocketFactory.
* @param password
* 密码
* @param keyStorePath
* 密钥库路径
* @param trustStorePath
* 信任库路径
* @return SSLSocketFactory
* @throws Exception
*/
public static SSLContext getSSLContext(String password, String keyStorePath, String trustStorePath) throws Exception {
// 获得密钥库
KeyStore keyStore = getJksKeyStore(password, keyStorePath);
// 获得信任库
KeyStore trustStore = getJksKeyStore(password, trustStorePath);
return getSSLContext(password, keyStore, trustStore);
}
public static SSLSocketFactory getSSLSocketFactory(String password, String keyStorePath, String trustStorePath) throws Exception {
// 声明SSL上下文
SSLContext sslContext = null;
try {
sslContext = getSSLContext(password, keyStorePath, trustStorePath);
} catch (GeneralSecurityException e) {
e.printStackTrace();
Log.e(TAG, "getSSLSocketFactory: ", e);
}
if (sslContext != null) {
return sslContext.getSocketFactory();
}
return null;
}
/**
* 初始化HttpsURLConnection.
* @param password
* 密码
* @param keyStorePath
* 密钥库路径
* @param trustStorePath
* 信任库路径
* @throws Exception
*/
public static void initHttpsURLConnection(String password, String keyStorePath, String trustStorePath) throws Exception {
// 声明SSL上下文
SSLContext sslContext = null;
// 实例化主机名验证接口
SSLSocketFactory factory = getSSLSocketFactory(password, keyStorePath, trustStorePath);
if (factory != null) {
HttpsURLConnection.setDefaultSSLSocketFactory(factory);
}
// 实例化主机名验证接口
HostnameVerifier hnv = new NoneHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
}
}
补充
Http请求结果 (对应服务器返回的json数据)
package com.hulk.android.http.conn;
/**
* Http请求结果 (对应服务器返回的json数据)
* <p> json:
* {"code":0,"msg":"Success","data":"This is response data"}
* 此处是举例返回数据, 具体的返回数据还更具真实数据确定
* @author: zhanghao
* @Time: 2021-03-04 17:35
*/
public class HttpResult<T> {
public int code = -1;
public String msg;
public String detail;
public Throwable error;
public T data = null;
//TODO 此处是举例返回数据, 具体的返回数据还更具真实数据确定
public HttpResult() {
}
public HttpResult(int code, String msg) {
this.code = code;
this.msg = msg;
}
public HttpResult(int code, String msg, String detail, Throwable error) {
this.code = code;
this.msg = msg;
this.detail = detail;
this.error = error;
}
public void setData(T data) {
this.data = data;
}
}
以上主要代码展示, 具体的代目可在参考源代码 https://download.csdn.net/download/zhanghao_Hulk/16020524 , 或者留言单独提供