Okhttp源码解析(零)——Okhttp使用
一.概述
- 一般的get请求和post请求
- 基于HTTP的文件上传
- 基于HTTP的文件下载
支持:android2.3以上,JDK1.7以上
引入:
对于我使用的是okhttp3.2版本
在AS下,在gradle文件下
compile 'com.squareup.okhttp3:okhttp:3.2.0'
不过对于低版本的,例如2.4版本的,还需要依赖一个IO包,因此总共需要引入两个库
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.squareup.okio:okio:1.5.0'
二.使用
(一)HTTP get请求
get请求为最常用的S/C模式中常用的调用接口方法,与post请求不同的是get方式直接将参数暴露在url上,
例子:url=http://host:post/login.json?email=609931480@qq.com&password=123456
/** * 判断当前手机网络是否可用 * * @return true:是,false:否 */ public static boolean isNetWorkAvailable(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isAvailable(); } else { return false; } } private OkHttpClient mOkHttpClient; public OkHttpClient getClient() { if (mOkHttpClient == null) { mOkHttpClient = new OkHttpClient.Builder() .connectTimeout(90, TimeUnit.SECONDS) .writeTimeout(90, TimeUnit.SECONDS) .readTimeout(90, TimeUnit.SECONDS) .build(); } return mOkHttpClient; } /** * http get方式从后台api获取数据 * * @param url url地址 * @return 结果字符串 */ public String getDataFromApi(String url) { Request request = new Request.Builder() .url(url) .build(); String ret; if (isNetWorkAvailable(getApplicationContext())) {//判断当前网络状态 try { Response response = null; response = getClient().newCall(request).execute(); if (response != null) { if (response.isSuccessful()) { ret = response.body().string(); } else { ret = String.format("{ 'code':%s }", response.code());//http } } else { ret = String.format("{ 'code':%s }", 700);//http请求异常 } return ret; } catch (Exception ex) { ret = String.format("{ 'code':%s }", 700);//http请求异常 } } else { ret = String.format("{ 'code':%s }", 600);//网络不可用 } return ret; }
重点在我写的getDataFromApi()方法
1.首先构造Request对象,Request.Builder()的源码:
public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); }
只是设置了两个参数而已,“GET”方式是默认的方式。因此简单分析可知我们可以通过Request.Builder()设置其他参数例如,header(为HTTP的请求报头,一般调用接口情况下设置类型为User-Agent)
.url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(), ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk() : ""))//如Fitmix/44/2.3.0 (Android 4.0)"
、method等
2.通过request对象构造出Call对象,就是将请求分装成对象,任务的话,执行:execute(),取消:cancel()。
3.任务分同步和异步之分:例子中代码execute()表示同步,即线程阻塞方式,虽然是阻塞方式,但是对于接口访问,我们仍使用线程阻塞方式,因为毕竟接口访问速度很快。
适用异步方式的情况:允许相同工作同步执行
适用阻塞方式的情况:线程池控制,线程一个一个执行完成才能下一个,较稳定。
如果我们想要使用异步的方式执行请求。调用:call.enquene,就是将call加入调度队列,代码如下:
Callback callback = new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }; if (request == null) return; getClient().newCall(request).enqueue(responseCallback);
4.获取访问结果。
无论是同步还是异步,返回结果都再Response变量中,通过response.body().toString()获取返回的JSon字符串。response.code()获取返回码
(二)HTTP post请求
与Get方式不同的是没有将请求的参数暴露在url上,而是将参数作为请求的参数存入
例子:url=http://host:post/login.json
提交json:
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); JSONObject jsonObject = new JSONObject(); jsonObject.put("email", 609931480@qq.com); jsonObject.put("password", 123456); OkHttpClient mOkHttpClient = new OkHttpClient(); RequestBody body = RequestBody.create(JSON, jsonObject .toString()); //创建一个Request final Request request = new Request.Builder() .url(url) .post(body) .build();
提交键值对:
OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody formBody = new FormEncodingBuilder() .add("platform", "android") .add("name", "bug") .add("subject", "XXXXXXXXXXXXXXX") .build(); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); } }
以上就是将url的参数封装成键值对存储在jsonObject对象中,构建RequestBody,最后完成在请求的Request的构建
二.HTTP的文件上传
String url = "http://www.baidu.com"; private String uploadFile(){ File localFile = new File("xxx");//要上传的文件 String uploadFileTag = "png";//上传的文件类型,自定义的 // 2.创建 OkHttp 对象相关 OkHttpClient client = new OkHttpClient(); // 5.设置上传http请求头 Request request; if (localFile.exists()) { String sString = "form-data; name=\"" + uploadFileTag + "\";filename=\"" + localFile.getName() + "\""; RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart(uploadFileTag, localFile.getName(), RequestBody.create(null, localFile)) .addPart( Headers.of("Content-Disposition", sString), RequestBody.create( MediaType.parse("application/octet-stream"), localFile)).build(); request = new Request.Builder().url(url) .post(requestBody).build(); }else{ return ""; } // 5.上传操作 try { Response response = client.newCall(request).execute(); if (response != null) { if (response.isSuccessful()) { //成功 } else { //失败 } return "{code:" + response.code() + "}"; } } catch (Exception e) { } return null; }
上述代码向服务器传递了一个文件,使用了MultipartBody,调用addPart()添加文件。就是提交表单消息,POST请求
三.文件下载
String url = "http://www.baidu.com"; File localTempFile = new File("xxx" + ".tmp");//下载到存放的位置 public String downloadFile() throws Exception { // 1.先检查是否有之前的临时文件 String httpRange = ""; if (localTempFile.exists()) { httpRange = "bytes=" + localTempFile.length() + "-"; } // 2.创建 OkHttp 对象相关 OkHttpClient client = new OkHttpClient(); // 3.检测网络状况 // 4.如果有临时文件,则在下载的头中添加下载区域,即断点续传 Request request; if (!TextUtils.isEmpty(httpRange)) { request = new Request.Builder().url(url).header("Range", httpRange) .build(); } else { request = new Request.Builder().url(url) .build(); } Call call = client.newCall(request); try { bytes2File(call); } catch (IOException e) { e.printStackTrace(); return null; } return null; } /** * 将下载的数据存到本地文件 * * @param call OkHttp的Call对象 * @throws IOException 下载的异常 */ private void bytes2File(Call call) throws IOException { //设置输出流. OutputStream outPutStream; //1.检测是否支持断点续传 Response response = call.execute(); ResponseBody responseBody = response.body(); String responseRange = response.headers().get("Content-Range"); if (responseRange == null || !responseRange.contains(Long.toString(localTempFile.length()))) { //最后的标记为 true 表示下载的数据可以从上一次的位置写入,否则会清空文件数据. outPutStream = new FileOutputStream(localTempFile, false); // 文件不存在 } else { outPutStream = new FileOutputStream(localTempFile, true); // 文件存在,追加下载 } int length; byte[] buffer = new byte[1024];//设置缓存大小 //4.开始写入文件 InputStream inputStream = responseBody.byteStream(); while ((length = inputStream.read(buffer)) != -1) { //写文件 outPutStream.write(buffer, 0, length); } //清空缓冲区 outPutStream.flush(); outPutStream.close(); inputStream.close(); responseBody.close(); }
其中重要的就是通过Call.execute()执行访问,返回访问的Response的回复内容,response.body().byteStream()返回输入流,通过输入流不断在文件上写。同理如果下载图片,一样道理,只不过通过返回的inputStream输入流decode出Bitmap。
三.封装
import android.text.TextUtils; import com.fitmix.sdk.common.Logger; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class OkHttpUtil { /** http MIME json头*/ public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static OkHttpUtil instance; private OkHttpClient mOkHttpClient; public static OkHttpUtil getInstance() { if (instance == null) { instance = new OkHttpUtil(); } return instance; } public OkHttpClient getClient() { if (mOkHttpClient == null) { mOkHttpClient = new OkHttpClient.Builder() .connectTimeout(90, TimeUnit.SECONDS) .writeTimeout(90, TimeUnit.SECONDS) .readTimeout(90, TimeUnit.SECONDS) .build(); } return mOkHttpClient; } /** * 阻塞方式http请求。 * * @param request http请求实体 * * @return http响应实体 */ public Response execute(Request request) { if (request == null) return null; Response response = null; try { response = getClient().newCall(request).execute(); } catch (IOException e) { e.printStackTrace(); Logger.e(Logger.DATA_FLOW_TAG,"execute error:"+e.getMessage()); } if (response == null) return null; return response; } /** * 异步方式http请求。 * * @param request http请求实体 * @param responseCallback http请求回调 * */ public void enqueue(Request request, Callback responseCallback) { if (request == null) return; getClient().newCall(request).enqueue(responseCallback); } /** * 异步方式http请求,且不在意返回结果(实现空callback) * * @param request http请求实体 */ public void enqueue(Request request) { if (request == null) return; Callback callback = new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }; enqueue(request, callback); } /** * 阻塞方式http请求。 * * @param url http请求url * * @return http响应实体 * */ public Response getResponseFromServer(String url) { if (url == null || url.isEmpty()) return null; Request request = new Request.Builder().url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(), ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk() : ""))//如Fitmix/44/2.3.0 (Android 4.0)" .build(); return execute(request); } /** * 阻塞方式http请求 * @param url http请求url * * @return 字符串结果 */ public String getStringFromServer(String url) { Response response = getResponseFromServer(url); if (response == null) return null; if (response.isSuccessful()) { String responseUrl = null; try { responseUrl = response.body().string(); } catch (IOException e) { e.printStackTrace(); } return responseUrl; } return "{code:" + response.code() + "}"; } /** * 上传单个文件请求 * * @param url http请求url * @param sFile 要上传的文件的本地绝对路径 * @param sTag 要上传的文件标签 * * @return * */ public String uploadToServer(String url, String sFile, String sTag) { if (url == null || url.isEmpty()) return null; if (sFile == null || sFile.isEmpty()) return null; if (sTag == null || sTag.isEmpty()) return null; File file = new File(sFile); Request request; if (file.exists()) { String sString = "form-data; name=\"" + sTag + "\";filename=\"" + file.getName() + "\""; RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart(sTag, file.getName(), RequestBody.create(null, file)) .addPart( Headers.of("Content-Disposition", sString), RequestBody.create( MediaType.parse("application/octet-stream"), file)).build(); request = new Request.Builder().url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(), ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk() : ""))//如Fitmix/44/2.3.0 (Android 4.0)" .post(requestBody).build(); } else { RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addPart(Headers.of("Content-Disposition","form-data; name=\"" + sTag + "\""), RequestBody.create(null, "")) .build(); request = new Request.Builder().url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(), ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk() : ""))//如Fitmix/44/2.3.0 (Android 4.0)" .post(requestBody).build(); } try { OkHttpClient client = new OkHttpClient(); Response response = client.newCall(request).execute(); if (response == null) return null; if (response.isSuccessful()) { String responseUrl = null; try { responseUrl = response.body().string(); } catch (IOException e) { e.printStackTrace(); } return responseUrl; } return "{code:" + response.code() + "}"; }catch (IOException e){ e.printStackTrace(); }catch (Exception ex){ ex.printStackTrace(); } return "{code:" + ApiUtils.HTTP_REQUEST_EXCEPTION + "}"; } /** * 上传多个文件请求 * * @param url http请求url * @param files 要上传的文件的本地绝对路径集合 * @param tags 要上传的文件标签集合 * * @return * */ public String uploadToServer(String url, List<String> files, List<String> tags) { if (url == null || url.isEmpty()) return null; if(files == null || tags == null)return null; if(files.size() != tags.size())return null; boolean bExist = false; List<File> listFiles = new ArrayList<>(); for(String fileName : files){ if(TextUtils.isEmpty(fileName)) continue; File file = new File(fileName); if(file.exists()){ bExist = true; }else{ file = null; } listFiles.add(file); } Request request; if (bExist) { MultipartBody.Builder builder = new MultipartBody.Builder(); for(int i = 0; i < files.size(); i++) { if(listFiles.get(i) == null)continue; String sString = "form-data;name=\"" + tags.get(i) + "\";filename=\"" + listFiles.get(i).getName() + "\""; builder.addPart( Headers.of("Content-Disposition", sString), RequestBody.create( MediaType.parse("application/octet-stream"), listFiles.get(i))); } RequestBody requestBody = builder.setType(MultipartBody.FORM).build(); request = new Request.Builder().url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(),ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk():""))//如Fitmix/44/2.3.0 (Android 4.0)" .post(requestBody) .build(); } else { RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addPart(Headers.of("Content-Disposition","form-data; name=\"" + tags.get(0) + "\""), RequestBody.create(null, "")) .build(); request = new Request.Builder().url(url) .addHeader("User-Agent", String.format("Fitmix/%s/%s (Android %s)", ApiUtils.getApkVersionCode(),ApiUtils.getApkVersionName(), ApiUtils.getPhoneSdk() != null ? ApiUtils.getPhoneSdk():""))//如Fitmix/44/2.3.0 (Android 4.0)" .post(requestBody).build(); } try { OkHttpClient client = new OkHttpClient(); Response response = client.newCall(request).execute(); if (response != null && response.isSuccessful()) { String responseUrl; try { responseUrl = response.body().string(); if(responseUrl != null){ // Logger.i(Logger.DATA_FLOW_TAG,"uploadToServer responseUrl:"+responseUrl); return responseUrl; } } catch (IOException e) { e.printStackTrace(); if(!TextUtils.isEmpty(e.getMessage())) { Logger.e(Logger.DATA_FLOW_TAG, "uploadToServer error:" + e.getMessage()); } } return "{code:" + response.code() + "}"; } }catch (IOException e){ e.printStackTrace(); }catch (Exception ex){ ex.printStackTrace(); } return "{code:" + ApiUtils.HTTP_REQUEST_EXCEPTION + "}"; }