// 1、拿到okhttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
// 2、构造Request
Request.Builder builder = new Request.Builder();
Request request = builder
.get()
.url(mBaseUrl + "login?username=zbq&userpassword=123")
.build();
// 3、将Request请求封装为Call
Call call = okHttpClient.newCall(request);
// 执行Call
// Response response = call.execute(); 同步执行
call.enqueue(new Callback() { // 异步执行
@Override
public void onFailure(Call call, IOException e) {
LogUtils.e("onFailure" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String res = response.body().toString();
LogUtils.e(response.body().toString());
// 4、response回调在子线程中而不是UI线程
runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText(res);
}
});
}
});
虽然代码里加了注释相信很多人也应该能看的懂,以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。
上面我用的Get请求,接下来看看Post请求是什么样子的吧
// 1、拿到okhttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
FormBody requestBody = new FormBody.Builder()
.add("username","huanhuan").add("userpassword","12345").build();
// 2、构造Request
// 2.1 Post请求需要构造FormBody
Request.Builder builder = new Request.Builder();
Request request = builder.url(mBaseUrl +"login").post(requestBody).build();
// 3、将Request请求封装为Call
Call call = okHttpClient.newCall(request);
// 执行Call
// Response response = call.execute(); 同步执行
call.enqueue(new Callback() { // 异步执行
@Override
public void onFailure(Call call, IOException e) {
LogUtils.e("onFailure" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String res = response.body().toString();
LogUtils.e(response.body().toString());
// response回调在子线程中而不是UI线程
runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText(res);
}
});
}
});
是不是Post请求的代码要比Get要多一些,没错,大家都清楚,post的时候,参数是包含在请求体中的;所以我们通过FormBody添加多个String键值对,然后去构造RequestBody,最后完成我们Request的构造。
public ConcurrentHashMap urlParams = new ConcurrentHashMap<>();
public ConcurrentHashMap fileParams = new ConcurrentHashMap<>()
public void put(String key, String value) {
if (key != null && value != null) {
urlParams.put(key, value);
}
}
public void put(String key, Object object) throws FileNotFoundException {
if (key != null) {
fileParams.put(key, object);
}
}
/**
* 创建一个Get请求
* @param url
* @param params
* @return 返回一个创建好的Get请求对象
*/
public static Request createGetRequest(String url,RequestParams params){
StringBuilder stringBuilder = new StringBuilder(url).append("?");
if(params !=null){
for (Map.Entry entry : params.urlParams.entrySet()){
stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
return new Request.Builder().url(stringBuilder.substring(0,stringBuilder.length()-1))
.get().build();
}
这里我将方法声明为static,就是把它当工具以后方便调用,这里大家也可以用单例对象实现。参数里面的RequestParams就是我们刚刚封装的请求参数,中间那部分逻辑其实就是Get请求中拼接请求参数了。最后我们要构建一个Request对象将参数填充进去然后返回给我们的调用者OkHttpClient。接下来看看Post请求:
/**
* 创建一个Post请求
* @param url
* @param params
* @return 返回一个创建好的Post请求对象
*/
public static Request createPostRequest(String url,RequestParams params){
FormBody.Builder mFormBodyBuild = new FormBody.Builder();
if(params !=null){
for (Map.Entry entry : params.urlParams.entrySet()){
// 将请求参数遍历添加到请求构建类中
mFormBodyBuild.add(entry.getKey(),entry.getValue());
}
}
// 通过请求构建类的build方法获取到真正的请求体对象
FormBody formBody = mFormBodyBuild.build();
return new Request.Builder().url(url).post(formBody).build();
}
这里大家请注意以下之前有些人可能看到网上用是
FormEncodingBuilder,这里为什么用FormBody?因为OkHttp3.x,FormEncodingBuilder已被FormBody取代,所以请大家在用的时候一定要引用最新的网络框架到工程中!!!FormBody的作用就是方便添加键值对表单数据,这里用到了构建者模式,他继承了RequestBody,给大家看看里面的部分源码就明白原理是什么 了
public static final class Builder { private final List<String> names = new ArrayList<>(); private final List<String> values = new ArrayList<>(); public Builder add(String name, String value) { names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true)); values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true)); return this; } public Builder addEncoded(String name, String value) { names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true)); values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true)); return this; } public FormBody build() { return new FormBody(names, values); } }
// 为我们的client去配置参数
static {
// 创建我们client对象的构建者
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;
}
});
okHttpBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());
// 生成Client对象
mOkHttpClient = okHttpBuilder.build();
}
这里我要提一下okHttpBuilder.sslSocketFactory(),这个方法设置用于保护HTTPS连接的套接字工厂和信任管理器,里面可以设置对Https的一个支持,说实话这一块涉及到网络的SSL这一块原理我也不是很清楚(好尴尬)也是参考别人的方法实现,我这里把代码贴出来大家直接用就好了。
public static SSLSocketFactory initSSLSocketFactory() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
X509TrustManager[] xTrustArray = new X509TrustManager[]
{initTrustManager()};
sslContext.init(null,
xTrustArray, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext.getSocketFactory();
}
public static X509TrustManager initTrustManager() {
X509TrustManager mTrustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
};
return mTrustManager;
}
好了配置好基本的网络参数后,就可以发送请求了,这里是调用Call的异步处理。
/**
* 通过构造好的Get Request,Callback去发送请求
* @param request
* @param commCallback
* @return Call
*/
public static Call get(Request request, Callback commCallback){
Call call = mOkHttpClient.newCall(request);
call.enqueue(new commCallback);
return call;
}
这样写看起来好像OK了,不过仔细一看感觉还是少了些什么,是不是没有对Callback回调做一个统一的处理啊。的确,接下来我就对最右边的部分Callback进行封装,然后回来优化这块逻辑。
public interface Callback { /** * Called when the request could not be executed due to cancellation, a connectivity problem or * timeout. Because networks can fail during an exchange, it is possible that the remote server * accepted the request before the failure. */ void onFailure(Call call, IOException e); /** * Called when the HTTP response was successfully returned by the remote server. The callback may * proceed to read the response body with {@link Response#body}. The response is still live until * its response body is {@linkplain ResponseBody closed}. The recipient of the callback may * consume the response body on another thread. * * Note that transport-layer success (receiving a HTTP response code, headers and body) does * not necessarily indicate application-layer success: {@code response} may still indicate an * unhappy HTTP response code like 404 or 500. */ void onResponse(Call call, Response response) throws IOException; }的确,源码里是有。但是!这不代表以后还是这些方法,我刚才也提到了,由于OkHttp3的项目负责人太敬业了,总想让自己的代码更加完美好用,于是版本一升级,难免有的方法就过时消失或者被整合到其他方法中了,这个坑我在刚刚在讲Request里的FormBody就提到过了。所以,保险起见就是我们自己重新自定义一个回调事件接口,同时自定还有一个好处就是方便以后好扩展,大家看源码里面就只提供了 两个方法,如果以后我想要看看当前下载进度如何,源码里没有这个监听下载进度的回调方法,那岂不是呵呵了。为此我们要定义实现为好。OK说完了该实现了
/**
* 自定义事件监听
* Created by Administrator on 2017/7/23.
*/
public interface DisposeDataListener {
/**
* 请求成功回调事件处理
*/
public void onSuccess(Object responseObj);
/**
* 请求失败回调事件处理
*/
public void onFailure(Object reasonObj);
}
写完回调接口后,我们可以对回调做一下预处理操作,因为我们在处理Json回调时,需要实现将Json对象转化成实体对象,所以我们就需要一个转化成对象的字节码对象,因此我们可以将响应回调Listener和要转换的字节码对象进行封装。例如
/**
* 相当于对callBack的预处理
* Created by Administrator on 2017/7/23.
*/
public class DisposeDataHandle {
public DisposeDataListener mListener = null;
public Class> mClass = null; // 字节码
public String mSource = null;
public DisposeDataHandle(DisposeDataListener listener)
{
this.mListener = listener;
}
public DisposeDataHandle(DisposeDataListener listener, Class> clazz)
{
this.mListener = listener;
this.mClass = clazz;
}
public DisposeDataHandle(DisposeDataListener listener, String source)
{
this.mListener = listener;
this.mSource = source;
}
}
2、异常处理
/**
* 自定义异常
* Created by Administrator on 2017/7/23.
*/
public class OkHttpException extends Exception {
private static final long serialVersionUID = 1L;
/**
* the server return code
*/
private int ecode;
/**
* the server return error message
*/
private Object emsg;
public OkHttpException(int ecode, Object emsg) {
this.ecode = ecode;
this.emsg = emsg;
}
public int getEcode() {
return ecode;
}
public Object getEmsg() {
return emsg;
}
}
好了,接下来就是重头戏转发消息到UI线程以及将Json转化成对应的实体。我将这两个一起封装到JsonCallbackCenter类中,一起实现。
// 与服务器返回的字段的一个对应关系
protected final String RESULT_CODE = "ecode"; // 服务器返回的状态
protected final int RESULT_CODE_VALUE = 0; // 服务返回的状态值
protected final String ERROR_MSG = "emsg"; // 服务器返回的消息
protected final String EMPTY_MSG = ""; // 空消息
protected final String COOKIE_STORE = "Set-Cookie"; // decide the server it
// 自定义异常类型
protected final int NETWORK_ERROR = -1; // 网络相关错误
protected final int JSON_ERROR = -2; // Json处理相关错误
protected final int OTHER_ERROR = -3; // 未知异常错误
/**
* 将其它线程的数据转发到UI线程
*/
private Handler mDeliveryHandler; // 进行消息转发
private DisposeDataListener mListener; // 自定义事件回调接口
private Class> mClass; // 要转换的字节码
接着实现Callback接口,然后就会实现onFailure()、onResponse()两个方法。onFailure()就是我们请求失败时调用的方法,例如:
@Override
public void onFailure(final Call call, final IOException ioexception) {
/**
* 此时还在非UI线程,因此要转发
*/
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, ioexception));
}
});
}
由于要将消息转发到Ui线程中去更新界面,所以更新是会抛异常的。这里我们使用handler去通知更新,同时这里我们还用到了刚刚自定义的回到方法onFailure()方法,将异常信息传入即可。接下来就该OnResponse()方法了
@Override
public void onResponse(final Call call, final Response response) throws IOException {
final String result = response.body().string();
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
handleResponse(result); // 处理从接口返回的数据
}
});
}
这里是处理网络请求成功之后的操作,说先就是要拿到服务器返回的数据。然后将这些数据转发到主线程中。这里我又单独写了一个处理数据的方法,就是将Json数据转化成实体对象。
/**
* 处理服务器返回的响应数据
* @param responseObj
*/
private void handleResponse(Object responseObj) {
// 为了保证代码的健壮性
if (responseObj == null || responseObj.toString().trim().equals("")) {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, EMPTY_MSG));
return;
}
try {
/**
* 协议确定后看这里如何修改
*/
JSONObject result = new JSONObject(responseObj.toString());
// 开始尝试解析json
if(result.has(RESULT_CODE)){
// 从json对象中取出我们的响应码,若为0,则是正确的响应(根据服务状态码来定)
if (mClass == null) { // 不需要解析,直接返回数据到应用层
mListener.onSuccess(result);
} else {
Gson gson = new Gson();
Object obj = gson.fromJson(responseObj.toString(),mClass);
// 表明正确的转化成实体对象
if (obj != null) {
mListener.onSuccess(obj);
} else {
// 返回的不是合法的json
mListener.onFailure(new OkHttpException(JSON_ERROR, EMPTY_MSG));
}
}
}else {
// 将服务器返回我们的异常回调到应用层去处理
mListener.onFailure(new OkHttpException(OTHER_ERROR,result.get(RESULT_CODE)));
}
} catch (Exception e) {
mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
e.printStackTrace();
}
}
compile 'com.google.code.gson:gson:2.4'
转化完后再进一步验证自己的转化后的结果,保证把正确的结果传递到UI线程中
/**
* 通过构造好的Get Request,Callback去发送请求
* @param request
* @param handle
* @return Call
*/
public static Call get(Request request, DisposeDataHandle handle){
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallback(handle));
return call;
}
优化后接受到数据后可以预处理好多数据,大大简化我们后面我们调用时的操作。好了封装思路图里的东西我们都已经实现,接下来我们要做最后一层封装。由于我们的请求操作很多,例如注册、登录、详情页、支付等,为了简化我们的请求操作,我们还可以对上述已经封装好的框架在封装一次,当然这里的封装的目的就是简化操作作为API用,以后好统一管理。这里我建立一个叫RequestCenter的类,我以登录请求为例,在
RequestCenter的类中写下如下方法:
//根据参数发送所有post请求
public static void postRequest(String url, RequestParams params,
DisposeDataListener listener, Class> clazz) {
CommonOkHttpClient.get(CommonRequest.
createGetRequest(url, params), new DisposeDataHandle(listener, clazz));
}
/**
* 用户登陆请求
*
* @param listener
* @param userName
* @param passwd
*/
public static void login(String userName, String passwd, DisposeDataListener listener) {
RequestParams params = new RequestParams();
params.put("mb", userName);
params.put("pwd", passwd);
RequestCenter.postRequest(HttpConstants.LOGIN, params, listener, User.class);
}
这个类还可以扩展,以后需要注册请求、下载文件等方法都可以放到这个类中。最后咱们实际调用时是什么样子的
RequestCenter.login(userName, password, new DisposeDataListener() {
@Override
public void onSuccess(Object responseObj) {
DialogManager.getInstnce().dismissProgressDialog();
/**
* 这部分可以封装起来,封装为到一个登陆流程类中
*/
User user = (User) responseObj;
UserManager.getInstance().setUser(user);//保存当前用户单例对象
........
}
@Override
public void onFailure(Object reasonObj) {
DialogManager.getInstnce().dismissProgressDialog();
}
});
}
现在看看这个网络请求的逻辑是不是相当干净简洁。这就是封装的好处。