Android网络框架学习之OkHttp

来源:互联网 发布:淘宝大学vip课程 编辑:程序博客网 时间:2024/05/29 07:24

OkHttp官网地址
OkHttp GitHub地址
官网中有很详细的Java doc文档,我们可以很方便的查到OkHttp里面各个API的功能和使用方法。

get方式获取数据

private String doGet(String areaId)  {        //1.创建一个okClient对象        OkHttpClient client = new OkHttpClient();        //2.创建一个Request请求        String url = String.format(URL_Weather,areaId);        Request request  = new Request.Builder().url(url).build();        Call call = client.newCall(request);        call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.d(TAG,"请求服务器失败");            }            @Override            public void onResponse(Call call, Response response) throws IOException {                // 注:该回调是子线程,非主线程                Log.d(TAG,"请求服务器成功:");                weatherResult = response.body().string();            }        });        return  weatherResult;    }

以上是OkHttp的异步get请求,是okhttp3异步方法,不需要我们开启工作线程执行网络请求,返回的结果也在工作线程中;

 Response response = client.newCall(request).execute();    if (response.isSuccessful()) {        return response.body().string();    } else {        throw new IOException("Unexpected code " + response);    }

以上是同步方法,如果使用同步get方法,需要自行开辟子线程进行网络请求,此处不推荐~

post方式获取数据

OkHttp的post方法可以提交键值对、文本、Stream、file等形式的数据,然后使用Request的post方法来提交请求体RequestBody。

private String doPost(String areaId) {        new Thread(new Runnable() {            @Override            public void run() {                try {                    // 请求完整url:http://api.k780.com:88/?app=weather.future&weaid=101281001&&appkey=%s&sign=%s&format=json                    String url = "http://api.k780.com:88/";                    OkHttpClient.Builder builder = new OkHttpClient.Builder()                            .connectTimeout(800, TimeUnit.MILLISECONDS)                            .cache(new Cache(getExternalCacheDir().getAbsoluteFile(),10*1024*1024))                            .writeTimeout(5, TimeUnit.SECONDS);                    OkHttpClient okHttpClient = builder.build();                    RequestBody formBody = new FormBody.Builder().add("app", "weather.future")                            .add("weaid", "101281001").add("appkey", APPKEY).add("sign",                                    SIGN).add("format", "json")                            .build();                    Request request = new Request.Builder().url(url).post(formBody).build();                    okhttp3.Response response = okHttpClient.newCall(request).execute();                    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);                    weatherResult = response.body().string();                    Log.i(TAG, weatherResult);                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();        return weatherResult;    }

接口参数
以下代码参考自OkHttp官方示例:Http官方教程

post提交流:

public static final MediaType MEDIA_TYPE_MARKDOWN      = MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {    RequestBody requestBody = new RequestBody() {      @Override public MediaType contentType() {        return MEDIA_TYPE_MARKDOWN;      }      @Override public void writeTo(BufferedSink sink) throws IOException {        sink.writeUtf8("Numbers\n");        sink.writeUtf8("-------\n");        for (int i = 2; i <= 997; i++) {          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));        }      }      private String factor(int n) {        for (int i = 2; i < n; i++) {          int x = n / i;          if (x * i == n) return factor(x) + " × " + i;        }        return Integer.toString(n);      }    };    Request request = new Request.Builder()        .url("https://api.github.com/markdown/raw")        .post(requestBody)        .build();    Response response = client.newCall(request).execute();    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);    System.out.println(response.body().string());}

post提交文件:

public static final MediaType MEDIA_TYPE_MARKDOWN  = MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {    File file = new File("README.md");    Request request = new Request.Builder()        .url("https://api.github.com/markdown/raw")        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))        .build();    Response response = client.newCall(request).execute();    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);    System.out.println(response.body().string());

post提交表单(键值对):

private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {    RequestBody formBody = new FormBody.Builder()        .add("search", "Jurassic Park")        .build();    Request request = new Request.Builder()        .url("https://en.wikipedia.org/w/index.php")        .post(formBody)        .build();    Response response = client.newCall(request).execute();    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);    System.out.println(response.body().string());}

此处需注意:
okHttp2.7用的是new FormEncodingBuilder(),而OkHttp3.x用的是FormBody.Builder();

Post提交String

public static final MediaType MEDIA_TYPE_MARKDOWN  = MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {    String postBody = ""        + "Releases\n"        + "--------\n"        + "\n"        + " * _1.0_ May 6, 2013\n"        + " * _1.1_ June 15, 2013\n"        + " * _1.2_ August 11, 2013\n";    Request request = new Request.Builder()        .url("https://api.github.com/markdown/raw")        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))        .build();    Response response = client.newCall(request).execute();    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);    System.out.println(response.body().string());}

Post提交分块请求

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

private static final String IMGUR_CLIENT_ID = "...";private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception {   // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image                    RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)                            .addPart(                                    Headers.of("Content-Disposition", "form-data; name=\"title\""),                                    RequestBody.create(null, "Square Logo"))                            .addPart(                                    Headers.of("Content-Disposition", "form-data; name=\"image\""),                                    RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))                            .build();                    Request request = new Request.Builder()                            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)                            .url("https://api.imgur.com/3/image")                            .post(requestBody)                            .build();                    Response response = client.newCall(request).execute();                    if (!response.isSuccessful())                        throw new IOException("Unexpected code " + response);                    System.out.println(response.body().string());}

此处OkHttp3也有和之前版本不一样的地方,OkHttp3使用了MultipartBody.Builder()的方法来创建RequestBody,而之前是使用MultipartBuilder()。
更多OkHttp的改动详见:okhttp3与旧版本okhttp的区别分析

关于MediaType

public static final MediaType MEDIA_TYPE_MARKDOWN            = MediaType.parse("text/x-markdown; charset=utf-8");

text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以application开头的媒体格式类型:

application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/json : JSON数据格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
注意:MediaType.parse(“image/png”)里的”image/png”不知道该填什么,可以参考—》http://www.w3school.com.cn/media/media_mimeref.asp
如何使用呢?(在请求体里面写入类型和需要写入的数据,通过post请求)
String body = “hdsoifhjoihdsfh”;
RequestBody body = RequestBody.create(MEDIA_TYPE_MARKDOWN, body);

设置缓存目录和大小:

响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

public CacheResponse(File cacheDirectory) throws Exception {    int cacheSize = 10 * 1024 * 1024; // 10 MiB    Cache cache = new Cache(cacheDirectory, cacheSize);    client = new OkHttpClient();    client.setCache(cache);}

设置超时时间和缓存

和OkHttp2.x有区别的是不能通过OkHttpClient直接设置超时时间和缓存了,而是通过OkHttpClient.Builder来设置,通过builder配置好OkHttpClient后用builder.build()来返回OkHttpClient,所以我们通常不会调用new OkHttpClient()来得到OkHttpClient,而是通过builder.build():

File sdcache = getExternalCacheDir();        int cacheSize = 10 * 1024 * 1024;        OkHttpClient.Builder builder = new OkHttpClient.Builder()                .connectTimeout(15, TimeUnit.SECONDS)                .writeTimeout(20, TimeUnit.SECONDS)                .readTimeout(20, TimeUnit.SECONDS)                .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));        OkHttpClient mOkHttpClient=builder.build();      

代码示例:

private String doGet(String areaId) {        String url = String.format(URL_Weather, areaId);        Request request = new Request.Builder().url(url).build();        Call call = okHttpClient.newCall(request);        call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.d(TAG, "请求服务器失败");            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.d(TAG, "请求服务器成功:\n"+response.body().string());            }        });        return weatherResult;    }    private String doPost(String areaId) {        try {            // 请求完整url:http://api.k780.com:88/?app=weather.future&weaid=101281001&&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json            String url = "http://api.k780.com:88/";            RequestBody formBody = new FormBody.Builder().add("app", "weather.future")                    .add("weaid", areaId).add("appkey", "10003").add("sign",                            "b59bc3ef6191eb9f747dd4e83c99f2a4").add("format", "json")                    .build();            Request request = new Request.Builder().url(url).post(formBody).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 {                    weatherResult = response.body().string();                    Log.i(TAG, weatherResult);                }            });        } catch (Exception e) {            e.printStackTrace();        }        return weatherResult;    }    private void doUpload() {        File file = new File("/sdcard/zoky.txt");        Request request = new Request.Builder()                .url("https://api.github.com/markdown/raw")                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))                .build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.d(TAG,"上传失败");            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.i(TAG, "上传成功:\n" + response.body().string());            }        });    }    private void doDownload() {        String url = "http://avatar.csdn.net/5/8/5/1_zoky_ze.jpg";        Request request = new Request.Builder().url(url).build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.d(TAG,"下载失败");            }            @Override            public void onResponse(Call call, Response response) {                int len;                InputStream inputStream = response.body().byteStream();                FileOutputStream fileOutputStream;                try {                    fileOutputStream = new FileOutputStream(new File("/sdcard/zoky.jpg"));                    byte[] buffer = new byte[2048];                    while ((len = inputStream.read(buffer)) != -1) {                        fileOutputStream.write(buffer, 0, len);                    }                    fileOutputStream.flush();                    Log.d(TAG, "文件下载成功"+ response.body().string());                } catch (IOException e) {                    e.printStackTrace();                }            }        });    }

github示例

内部调用流程

内部调用流程
有上图可以看出,OkHttp不管是异步调用还是同步调用,最终都是通过getResponseWithInterceptorChain()方法来进行网络请求

Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();interceptors.addAll(client.interceptors());interceptors.add(retryAndFollowUpInterceptor);interceptors.add(new BridgeInterceptor(client.cookieJar()));interceptors.add(new CacheInterceptor(client.internalCache()));interceptors.add(new ConnectInterceptor(client));if (!forWebSocket) {  interceptors.addAll(client.networkInterceptors());}interceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(    interceptors, null, null, null, 0, originalRequest);return chain.proceed(originalRequest);}

在getResponseWithInterceptorChain()方法中加了一些拦截器,然后启动一个拦截器调用链,拦截器递归调用之后最后返回请求的响应Response。这里的拦截器分层的思想就是借鉴的网络里的分层模型的思想。请求从最上面一层到最下一层,响应从最下一层到最上一层,每一层只负责自己的任务,对请求或响应做各自的操作。OkHttp还支持用户自定义拦截器,插入到最顶层和CallServerInterceptor上一层的位置。square官方就有一个Logging Interceptor,用于打印网络请求日志的拦截器。

下图是拦截器的调用流程及各自的执行的操作。
拦截器

RealInterceptorChain的proceed(),每次重新创建一个RealInterceptorChain对象,然后调用下一层的拦截器的interceptor.intercept()方法。

@Override public Response intercept(Chain chain) throws IOException {    Request request = chain.request();    // 1、该拦截器在Request阶段负责的事情    // 2、调用RealInterceptorChain.proceed(),其实是递归调用下一层拦截器的intercept方法    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);    //3、该拦截器在Response阶段负责的事情,然后返回到上一层拦截器的 response阶段    return  response;         }  }

参考文章:
OkHttp使用进阶 译自OkHttp Github官方教程
OkHttp3用法全解析
okhttp3与旧版本okhttp的区别分析
Android OkHttp完全解析 是时候来了解OkHttp了
OkHTT分析

1 0