我们在项目中或许会碰见各种各样的网络请求工具,这些工具都是基于网络框架的api上加以封装,这时我们使用时大大便利了----减少了代码重复问题、可以直接在回调中更新UI,框架更新(框架层修改)我们不必修改应用层的代码等等。因此学会二次封装网络框架,不仅可以使我们看懂别人的代码,更利于个人的进步。本文就详细讲解下Okhttp的简单封装。
写在前面
在总结之前先啰嗦几句:
1、由于okhttp源码中使用Builder模式较多。我们有必要了解下,
2、okhttp淡忘了可以重温下。
3、我们最好熟悉下我们框架的几个重要api及其作用,以及我们要实现的功能(如下图)
4、本文为封装提供了简单的实现,像企业中那种完善的网络框架,还需我们根据自己的业务不断完善吧。
封装
分析:
通过uml图我们可以知道okhttp可分成不同的小块(request,callback…),分别完成不同的工作,于是我们也可以按照不同的模块开始封装。
1 request模块
RequestParams请求参数处理:
package com.example.administrator.okhttputil.net.request;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Create by SunnyDay on 2019/03/04
*
* @function 封装所有的请求参数到hashMap中。
* 友情链接:
* 1 map集合遍历参考 https://www.cnblogs.com/blest-future/p/4628871.html
* 2 匿名内部类,非静态代码块 使用参考 https://blog.csdn.net/luman1991/article/details/53034602#commentBox
* https://www.cnblogs.com/chenssy/p/3390871.html
* https://zhangbo-peipei-163-com.iteye.com/blog/2023001
* (具体参考 查找资料 java 匿名内部类的写法 ,java相关属性参数初始化顺序)
*/
public class RequestParams {
public ConcurrentHashMap<String, String> urlParams = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, Object> fileParams = new ConcurrentHashMap<>();
/**
* @param source map类型参数
* @function 构造器 吧用户传来map类型的参数存入我们的集合
*/
public RequestParams(Map<String, String> source) {
if (source != null) {
//遍历key value集合
for (Map.Entry<String, String> entry : source.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
}
/**
* @function 构造器 接收 String类型的key value 请求参数 存入集合
*/
public RequestParams(final String key, final String value) {
// 参考 类注释的友情链接
this(new HashMap<String, String>() {
{
put(key, value);
}
});
}
/**
* @function 空请求参数
*/
public RequestParams() {
this((Map<String, String>) null);
}
/**
* @param key 请求参数key
* @param value 请求参数value
* @function key value 放入hashmap
*/
public void put(String key, String value) {
if (key != null && value != null) {
urlParams.put(key, value);
}
}
/**
* @param key 请求参数key
* @param object 请求参数obj
* @function 吧请求参数存入集合
*/
public void put(String key, Object object) throws FileNotFoundException {
if (key != null) {
fileParams.put(key, object);
}
}
/**
* @funtion 判断是否有请求参数
*/
public boolean hasParams() {
if (urlParams.size() > 0 || fileParams.size() > 0) {
return true;
}
return false;
}
}
我们自己的CommonRequest
package com.example.administrator.okhttputil.net.request;
import java.util.Map;
import okhttp3.FormBody;
import okhttp3.Request;
/**
* Create by SunnyDay on 2019/03/04
*
* @function 接收请求参数 为我们生成Request对象
*/
public class CommonRequest {
/**
* @function post 请求
* @param url url
* @param params 请求参数
*
*/
public static Request createPostRequest(String url, RequestParams params) {
FormBody.Builder builder = new FormBody.Builder();
// 吧请求内容添加到请求体中
for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
// 构建请求体
FormBody formBody = builder.build();
// 返回封装的Request请求
return new Request.Builder().post(formBody).build();
}
/**
* @function get 请求
* @param url url
* @param params 请求参数
* 通过url+请求参数的拼接 成我们的get请求url,在生成Request请求
*
* get url 的方式 域名 ? key = value & key = value ......
* 有参数就拼接url再请求, 没有参数就使用给的拼接好的url
*/
public static Request createGetRequest(String url, RequestParams params){
StringBuilder sb = new StringBuilder(url).append("?");
if (params!=null){
for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
sb.append(entry.getKey())
.append("=")
.append(entry.getValue())
.append("&");
}
String disposedUrl = sb.toString().substring(sb.length()-1);// 去掉最后一个多余的&字符串
return new Request.Builder()
.url(disposedUrl)
.get()
.build();
}else{
return new Request.Builder()
.url(url)
.get()
.build();
}
}
}
2 请求客户端
package com.example.administrator.okhttputil.net;
import com.example.administrator.okhttputil.net.https.HttpsUtils;
import com.example.administrator.okhttputil.net.response.CommonJsonCallback;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Create by SunnyDay on 2019/03/04
*
* @function 请求的发送 请求参数的配置 https的支持
*/
public class CommonOkHttpClient {
private static final int TIME_OUT = 30;// 超时
private static OkHttpClient okHttpClient;
// client的一些参数配置 在类第一次加载时完成
static {
OkHttpClient.Builder okhttpBuilder = new OkHttpClient.Builder();
okhttpBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);//连接超时
okhttpBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);//读超时
okhttpBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);//写超时
okhttpBuilder.followRedirects(true);// 允许重定向
// 添加对所有https的支持
okhttpBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true; //true: 支持任意类型的https(无论官方购买的https,或者是自己生成的https请求)
}
});
okhttpBuilder.sslSocketFactory(HttpsUtils.getSslSorcetFactory());
// 生成client对象
okHttpClient = okhttpBuilder.build();
}
/**
* 发送具体的http/https请求
* @param request
* @param commCallback
* @return call
*
* */
public static Call sendRequest(Request request, CommonJsonCallback commCallback) {
Call call = okHttpClient.newCall(request);
call.enqueue((Callback) commCallback);
return call;
}
}
使用的工具类
package com.example.administrator.okhttputil.net.https;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
/**
* Create by SunnyDay on 2019/03/05
* <p>
* https 相关知识 参考 张鸿洋 Android Https相关完全解析 当OkHttp遇到Https
* 友情链接:https://blog.csdn.net/lmj623565791/article/details/48129405
*/
public class HttpsUtils {
/**
* trust all https point
*/
public static SSLSocketFactory getSslSorcetFactory(){
// 1 生成信任管理器类
X509TrustManager manager = new X509TrustManager() {
// 所有的重写方法空实现即可
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
// 2 创建加密上下文
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");// 协议类型与服务器保持一致
X509TrustManager[] xTrustArray = new X509TrustManager[]{manager};
sslContext.init(null, xTrustArray, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext.getSocketFactory();
}
}
3 回调
package com.example.administrator.okhttputil.net.response;
import android.os.Handler;
import android.os.Looper;
import com.example.administrator.okhttputil.net.exception.OkHttpException;
import com.example.administrator.okhttputil.net.listener.DisposeDataHandle;
import com.example.administrator.okhttputil.net.listener.DisposeDataListener;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
/**
* Create by SunnyDay on 2019/03/05
*/
public class CommonJsonCallback implements Callback {
/**
* 自定义类型异常
*/
protected final int NETWORK_ERROR = -1;
protected final int JSON_ERROR = -2;
protected final int OTHER_ERROR = -3;
private Handler mDeliveryHandler; //进行消息转发
private DisposeDataListener mlistener;
private Class<?> mClass;
public CommonJsonCallback(DisposeDataHandle handle) {
this.mlistener = handle.listener;
this.mClass = handle.mClass;
this.mDeliveryHandler = new Handler(Looper.getMainLooper());
}
/**
* 请求失败处理
*/
@Override
public void onFailure(Call call, final IOException e) {
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
mlistener.onFailure(new OkHttpException(NETWORK_ERROR, e.getMessage()));
}
});
}
/**
* 请求成功回调
*/
@Override
public void onResponse(Call call, Response response) throws IOException {
final String result = response.body().string();// json 类型 为String就行
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
handleResponse(result);
}
});
}
/**
* 处理json数据
*
* @param responseObj json字符串
*/
private void handleResponse(String responseObj) {
// 判空处理
if (responseObj == null && responseObj.trim().equals("")) {
mlistener.onFailure(new OkHttpException(NETWORK_ERROR, "net work error"));
return;
}
try {
if (mClass == null) {
// 用户不想让我们处理json,直接回传给用户
mlistener.onSuccess(responseObj);
} else {
// 我们处理把json转化为实体
Gson gson = new Gson();
Object obj = gson.fromJson(responseObj, mClass);
if (obj != null) {
mlistener.onSuccess(obj);
} else {
// 不是合法的json
mlistener.onFailure(new OkHttpException(JSON_ERROR, "illegal json"));
}
}
} catch (Exception e) {
mlistener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
}
}
}
4 回调使用到的监听处理
监听
package com.example.administrator.okhttputil.net.listener;
/**
* Create by SunnyDay on 2019/03/05
*/
public interface DisposeDataListener {
/**
* 请求成功回调事件处理
*/
void onSuccess(Object responseObj);
/**
* 请求失败回调事件处理
*/
void onFailure(Object reasonObj);
}
处理
package com.example.administrator.okhttputil.net.listener;
/**
* Create by SunnyDay on 2019/03/05
*/
public class DisposeDataHandle<T> {
public DisposeDataListener listener = null;
public Class<?> mClass = null;
public DisposeDataHandle(DisposeDataListener listener) {
this.listener = listener;
}
public DisposeDataHandle(DisposeDataListener listener,Class<?> mClass ) {
this.listener = listener;
this.mClass = mClass;
}
}
异常
package com.example.administrator.okhttputil.net.exception;
/**
* Create by SunnyDay on 2019/03/05
*/
public class OkHttpException extends Exception {
private int ecode;// 服务端返回码
private Object emsg;// 服务端返回信息
public OkHttpException(int ecode, Object emsg) {
this.ecode = ecode;
this.emsg = emsg;
}
public int getEcode() {
return ecode;
}
public void setEcode(int ecode) {
this.ecode = ecode;
}
public Object getEmsg() {
return emsg;
}
public void setEmsg(Object emsg) {
this.emsg = emsg;
}
}
callback中测试
更新UI
直接获得实体测试
@Override
public void onSuccess(Object responseObj) {
TestBean testBean = (TestBean) responseObj;
Log.i(TAG, "onSuccess: "+testBean.getButtonName());
//okhttputil I/abc: onSuccess: 我知道了
}
小结
这样简单的封装就完成了,作者在学习时在里面添加了不少注释,补充资料,想必大家都能看懂,如果实在看不懂的话可以下载源码慢慢分析,或者联系作者,作者有视屏资料哦,哈哈溜了。。。。