Android 网络框架之 OkHttp 基础使用

以下代码基于 OkHttp 3.6.0 版本且后台为 Java,基本使用 OkHttp 的场景如下:
先说下一般的请求编写的步骤:
1. 构造 Request 实例,其中 url 为必须的,其他选项在链式操作中添加。
2. 构造 RequestBody ( get 请求时则无)
3. 获得 OkHttpClient 实例,然后根据 Request 实例 构造出 Call 实例
4. 提交执行 Call 任务(可异步、同步),然后在回调中处理自己的逻辑
直接来看代码把,需要注意是这边为了方便,就使用了全局的 OkHttpClient 实例。另外服务端代码会在最后贴出。

public static final String BASE_URL = "http://xx.xx.xx.xx:8080/OkHttpServer/"; // cookieJar 等后面的操作是为了 session 保持 // 这边仅是保存在内存中,当然也可以持久化 private OkHttpClient okHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() { private final HashMap> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List cookies) { cookieStore.put(url.host(), cookies); }@Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList(); } }).build();

get 请求
客户端代码:
private void doGet() { // 构造的请求 Request request = new Request.Builder() .url(BASE_URL + "login?username=ss&password=123") .build(); final Call call = okHttpClient.newCall(request); // 异步请求方式 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response body = " + response.body().string()); } }); // 同步请求 /*// 由于同步,将不可在主线程中调用,耗时操作将阻塞主线程 new Thread(new Runnable() { @Override public void run() { try { Response response = call.execute(); System.out.println("response body = " + response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).start(); */}

客户端接收的 Response
I/System.out: response body = login success!username=sspassword = 123

说明:
同步与异步请求的方式均已写出,但异步请求中的匿名类 CallBack 中的 onResponse 回调方法,仍然不是处于主线程中,这边为了偷懒而直接将信息打印在控制台中。因此在 onResponse 中若有操作 UI ,记得要切回 UI 线程!
post 请求
private void doPost() { // 表单构造,携带参数 FormBody body = new FormBody.Builder() .add("username", "Jack") .add("password", "123") .build(); Request request = new Request.Builder() .url(BASE_URL + "login") // 对比 get 请求 .post(body) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response body = " + response.body().string()); } }); }

客户端接收的 Response
I/System.out: response body = login success!username=Jackpassword = 123

post 上传文件
private void doPostFile() { File file = new File(Environment.getExternalStorageDirectory(), "testupload.apk"); RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file); Request request = new Request.Builder() .url(BASE_URL + "postFile") .post(requestBody) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response body = " + response.body().string()); } });

说明:这边的 RequestBody 的构造通过 RequestBody.create(MediaType.parse("application/octet-stream"), file); 由抽象类 RequestBody 中多个重载的 create 方法可知,比如想要仅仅上传给服务端一个字符串,可以写成 RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "server!can you see me?"); 具体类型可查看 create 源码。
表单上传,且获得进度反馈
经常性的需求是上传给服务端普通文件/图片也好,需要反馈给用户实时的上传进度。
private void postMultipart() { // 表单提交包括用户名、密码和一个文件 MultipartBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) // multipart/form-data 类型的表单提交 .addFormDataPart("username", "Jack") .addFormDataPart("password", "123") .addFormDataPart("file", "multipar.apk", RequestBody.create(MediaType.parse("application/octet-stream") , new File(Environment.getExternalStorageDirectory(), "testupload.apk"))) .build(); // 包装 RequestBody CountingRequestBody body = new CountingRequestBody(requestBody, new CountingRequestBody.Listener() { @Override public void onProcessUpload(long byteWritten, long contentLength) { // 已写出的 byte 长度除以文件总的 byte 数 System.out.println(((float) byteWritten) / contentLength * 100 + "%"); } }); Request request = new Request.Builder() .url(BASE_URL + "postMultipart") .post(body) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response body = " + response.body().string()); } }); }public class CountingRequestBody extends RequestBody {// 真正的包含数据的 RequestBody private RequestBody delegate; private Listener mListener; private CountingSink countingSink; public CountingRequestBody(RequestBody delegate, Listener mListener) { this.delegate = delegate; this.mListener = mListener; }@Override public MediaType contentType() { return delegate.contentType(); }@Override public long contentLength() throws IOException { try { return delegate.contentLength(); } catch (Exception e) { return -1; } }@Override public void writeTo(BufferedSink sink) throws IOException {countingSink = new CountingSink(sink); BufferedSink bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); }interface Listener { void onProcessUpload(long byteWritten, long contentLength); }protected final class CountingSink extends ForwardingSink {private long byteWritten; public CountingSink(Sink delegate) { super(delegate); }@Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); byteWritten += byteCount; mListener.onProcessUpload(byteWritten, contentLength()); } }}

可参看 hongyang 大神的 Android网络框架-OkHttp使用
多文件上传
private void postMultiFile() { MultipartBody.Builder builder = new MultipartBody.Builder(); builder.setType(MultipartBody.FORM); builder.addFormDataPart("username", "Jack"); builder.addFormDataPart("password", "123"); // 虽然上传的同一个文件,但后台确实收到了上传了 3 次不同命名的 apk 文件 // 这里仅是为了方便起见,改了文件名 i + "multipart.apk" for (int i = 0; i < 3; i++) { builder.addFormDataPart("files", i + "multipart.apk", RequestBody.create(MediaType.parse("application/octet-stream") , new File(Environment.getExternalStorageDirectory(), "testupload.apk"))); } // 一个 xml builder.addFormDataPart("files", "jay.xml", RequestBody.create(MediaType.parse("application/octet-stream") , new File(Environment.getExternalStorageDirectory(), "jay.xml"))); MultipartBody body = builder.build(); Request request = new Request.Builder() .url(BASE_URL + "postMultipleFile") .post(body) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException { System.out.println("response body = " + response.body().string()); } }); }

可参看 Upload multiple files at once to a Struts2 @Action
下载文件
// 为了简单方便,文件直接给的是 url 服务端文件路径,而不涉及接口调用 private void downloadImage() { Request request = new Request.Builder() .url(BASE_URL + "files/author_avatar.png") .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {}@Override public void onResponse(Call call, Response response) throws IOException {// 若对图片有其他的要求,再额外进行设置 final Bitmap bitmap = BitmapFactory.decodeStream(response.body().byteStream()); // 仅是展示出来 runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); // 当然既然可以拿到 response.body().byteStream() 的输入流 // 所以就将文件可以保存到手机上 //FileOutputStream fos = new FileOutputStream(new File( //Environment.getExternalStorageDirectory(), "author_avatar.png")); //InputStream inputStream = response.body().byteStream(); //int len = 0; //byte[] buffer = new byte[1024]; //while ((len = inputStream.read(buffer)) != -1) { //fos.write(buffer, 0, len); //} //fos.flush(); //fos.close(); //inputStream.close(); } }); }

说明:这边是以将输入流转为 Bitmap 展示图片为例而已,实际上拿到了 response.body().byteStream() 的输入流要很容易通过 IO 保存起来了。
服务端代码:
public class UserAction extends ActionSupport {private String username; private String password; private File mPhoto; private String mPhotoFileName; private List files; private List filesFileName; public void login() { System.out.println("username = " + username + "password = " + password); HttpServletResponse response = ServletActionContext.getResponse(); try { PrintWriter writer = response.getWriter(); writer.write("login success!username=" + username + "password = " + password); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }}/** * 获取客户端传来的字符串,再返回给客户端 */ public void postString() {try { System.out.println("postString is running"); HttpServletResponse response = ServletActionContext.getResponse(); HttpServletRequest request = ServletActionContext.getRequest(); InputStream is = request.getInputStream(); StringBuffer sb = new StringBuffer(); int len = 0; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1) { sb.append(new String(buffer, 0, len)); } PrintWriter writer = response.getWriter(); writer.write("your string is = " + sb.toString()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }/** * 响应客户端上传的文件 */ public void postFile() { System.out.println("postFile is runnning"); HttpServletResponse response = ServletActionContext.getResponse(); try { HttpServletRequest request = ServletActionContext.getRequest(); InputStream is = request.getInputStream(); String dir = ServletActionContext.getServletContext().getRealPath( "files"); File path = new File(dir); if (!path.exists()) { path.mkdirs(); } FileOutputStream fos = new FileOutputStream(new File(path, "12306.apk")); int len = 0; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1) { fos.write(buffer, 0, len); } fos.flush(); fos.close(); is.close(); PrintWriter writer = response.getWriter(); writer.write("upload file success!"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }/** * 相应客户端多文件上传 */ public void postMultipleFile() { // 检验是否上传的是多个文件,size 为正确 System.out.println("files size = " + files.size()); String dir = ServletActionContext.getServletContext().getRealPath( "files"); File path = new File(dir); if (!path.exists()) { path.mkdirs(); } try { // 根据文件名保存文件 for (int i = 0; i < files.size(); i++) { File file = new File(path, filesFileName.get(i)); FileUtils.copyFile(files.get(i), file); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }}/** * 响应客户端上传的用户名、密码和一个文件 */ public void postMultipart() { System.out.println("username = " + username + "password = " + password); System.out.println("postMultipart is runnning"); HttpServletResponse response = ServletActionContext.getResponse(); try { String dir = ServletActionContext.getServletContext().getRealPath( "files"); File path = new File(dir); if (!path.exists()) { path.mkdirs(); } File file = new File(path, mPhotoFileName); FileUtils.copyFile(mPhoto, file); PrintWriter writer = response.getWriter(); writer.write("your username = " + username + "password = " + password); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }public void downloadFile() { System.out.println("invoke downloadfile"); HttpServletResponse response = ServletActionContext.getResponse(); File file = new File(ServletActionContext.getServletContext() .getRealPath("files"), "12306.apk"); try { FileInputStream fis = new FileInputStream(file); OutputStream os = response.getOutputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len = fis.read(buffer)) != -1) { os.write(buffer, 0, len); } os.flush(); os.close(); fis.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }public File getmPhoto() { return mPhoto; }public void setmPhoto(File mPhoto) { this.mPhoto = mPhoto; }public String getmPhotoFileName() { return mPhotoFileName; }public void setmPhotoFileName(String mPhotoFileName) { this.mPhotoFileName = mPhotoFileName; }public List getFiles() { return files; }public void setFiles(List files) { this.files = files; }public List getFilesFileName() { return filesFileName; }public void setFilesFileName(List filesFileName) { this.filesFileName = filesFileName; }}

参考:
Android OkHttp完全解析 是时候来了解OkHttp了
【Android 网络框架之 OkHttp 基础使用】Android网络框架-OkHttp使用

    推荐阅读