Okhttp源码解析(零)——Okhttp使用

一.概述

  • 一般的get请求和post请求
  • 基于HTTP的文件上传
  • 基于HTTP的文件下载

官方github项目

支持: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'

二.使用

参考OkHttp使用教程

参考洪洋大神博客

(一)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)"
View Code

、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 + "}";
    }
View Code
原文地址:https://www.cnblogs.com/could-deng/p/6733721.html