1.Okio
1.1 输入与输出
以前总是搞不清输入与输出,是因为处的位置不对。输入与输出是站在程序自身的角度来看的。
1.2 历史
java.io --> java.nio --> okio
1.3 ByteString
1.4 Buffer
2.OkHttp
2.1 简介
OKHttp是由Square公司开发。使用的时候需要添加依赖库,如下所示:
compile 'com.squareup.okhttp3:okhttp:3.14.0' //注意:不同的okhttp版本需要的minSdkVersion不一样,可能会报如下错误: java.lang.BootstrapMethodError: Exception from call site #4 bootstrap method,将okhttp版本调低即可。
添加上述依赖会下载两个库:OKHttp与Okio库,后者是前者的通信基础,
使用步骤如下所示:
1. 不管是异步还是通过都是先创建OkHttpClient,建议全局只有一个 2. 然后使用Request的内部类Builder来创建一个Request,并设置网址 3. 调用newCall的newCall方法传入request 4. 调用newCall方法返回的Call对象上面的execute或者enqueue来执行这个请求 5. 调用response的isSuccessful方法来判断是否请求成功 6. 调用response的body上面的string方法来获取一个字符串结果 我还可以通过response.body()的其他方法拿到bytes数组,输入流等信息。 其中我们拿到了输入流或者bytes数组我们可以用来手动解码一张图片,或者下载一个文件。
2.2 OKHttpClient
2.2.1 OkHttpClient源码构造
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { int callTimeout; int connectTimeout; int readTimeout; int writeTimeout; ... public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { ... } ... public static final class Builder { int callTimeout; int connectTimeout; int readTimeout; int writeTimeout; ... public Builder() { //超时时间 callTimeout = 0; connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; ... } public OkHttpClient build() { return new OkHttpClient(this); } } }
2.2.2 实例创建方式
从上面的源码可以看出,OkHttpClient有两种创建方式,如下所示:
//方式1: OkHttpClient client= new OkHttpClient(); //方式2 OkHttpClient.Builder builder = new OkHttpClient.Builder(); OkHttpClient client = builder.build();
2.2.3 配置管理
1)配置公共请求超时时间
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) //连接超时时间 .writeTimeout(10, TimeUnit.SECONDS) //数据发送到服务端超时时间 .readTimeout(30, TimeUnit.SECONDS) //从服务端下载数据到本地超时时间 .build(); //需要注意的是,如果上传或者下载文件则需要将时间调长一点
2)配置单个请求超时时间
//这里创建了一个默认的Okhttp private final OkHttpClient client = new OkHttpClient(); public void run() throws Exception { //这个请求使用默认配置 Request request = new Request.Builder() .url("http://xxx") // This URL is served with a 1 second delay .build(); try { // 从client上创建一个浅拷贝,然后在更改配置,他只影响使用它发送的请求 OkHttpClient copy = client.newBuilder() .readTimeout(500, TimeUnit.MILLISECONDS) .build(); Response response = copy.newCall(request).execute(); System.out.println("Response 1 succeeded: " + response); } catch (IOException e) { System.out.println("Response 1 failed: " + e); } try { // 这里又创建一个okhttp的拷贝,并配置他的超时时间为3秒 OkHttpClient copy = client.newBuilder() .readTimeout(3000, TimeUnit.MILLISECONDS) .build(); Response response = copy.newCall(request).execute(); System.out.println("Response 2 succeeded: " + response); } catch (IOException e) { System.out.println("Response 2 failed: " + e); } }
2.3 Request静态内部类Builder
创建Builder如下所示:
//创建Builder对象 Request.Builder builder = new Request.Builder;
2.4 Request
2.4.1 组成
如果想要发送一条Http请求,则需要创建一个Request对象。一个Request包含如下内容:
URL method headers body 2.4.2 Get请求
Get方式如下所示:
//创建Builder对象 Request.Builder builder = new Request.Builder(); //传入URL地址 builder.url("http://www.baidu.com"); //创建Request对象 Request request = builder.build();
2.4.3 Post 请求
Post请求用于向服务器提交数据,并得到返回的结果数据。Post请求会比Get请求复杂一点,需要先构建出一个RequestBody对象来存放待提交的参数,如下所示:具体的要参照服务端给出的接口样式
//创建RequestBody 对象 RequestBody requestBody = new FormBody.Builder() .add("username","admin") .add("password","23456") .build(); //创建Builder对象 Request.Builder builder = new Request.Builder(); //传入URL地址 builder.url("http://www.baidu.com"); //将requestBody传入进去 builder.post(requestBody); //创建Request对象 Request request = builder.build();
2.4.4 源码
源码如下:
public final class Request { ...... //静态内部类 public static class Builder { @Nullable HttpUrl url; String method; Headers.Builder headers; @Nullable RequestBody body; public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); } ...... } }
上述代码只是创建了一个空的Request对象,并没有什么实际作用
2.5 Call
2.5.1 创建
有了Request,就需要OkHttpClient的newCall()方法来创建一个Call对象,如下所示:
//创建Call对象 Call call = okHttpClient.newCall(request);
源码如下:
/** * Prepares the {@code request} to be executed at some point in the future. */ @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); }
2.5.2 请求类型
Call有两种类型:同步请求与异步请求。同步请求需要在子线程里面进行。
同步请求 通过call.execute();请求会阻塞在这里,直到服务器返回数据。
异步请求 通过call.enqueue(Callback callback)方法。
void enqueue(Callback responseCallback);
2.5.3 同步请求
1)源码
public interface Call extends Cloneable { ...... Response execute() throws IOException; ...... }
2)使用
//开启子线程 new Thread(new Runnable() { @Override public void run() { try { //开启同步请求 Response response = okHttpClient.newCall(request).execute(); ...... } catch (IOException e) { e.printStackTrace(); } } }).start();
2.5.4 Callback异步请求
1)源码
public interface Call extends Cloneable { ...... void enqueue(Callback responseCallback); ...... }
2)使用
okHttpClient.newCall(request).enqueue(new Callback(){ //请求失败调用 @Override public void onFailure(Call call, IOException e) { } //其他情况下调用 @Override public void onResponse(Call call, Response response) throws IOException { } });
3)注意
onFailure()和onResponse()方法不是在主线程里面,不能直接操作UI。
2.6 Response
2.6.1 创建
有了Call对象,然后调用Call对象的execute()方法,来发送请求并获取服务器返回的数据,即Response。如下所示:
//获取到服务端返回的数据 try { Response response = call.execute(); } catch (IOException e) { e.printStackTrace(); }
2.6.2 组成
Response的组成如下所示:
1.code 表示返回码,可通过response.code()得到 2.headers 头部文件,可通过response.headers()得到 3.body 响应体,可通过response.body()得到 2.6.3 获取Response具体内容
通过如下代码获取服务端返回的数据的具体内容:
//获取Response的具体内容: String responseData = response.body().string();
注意:别使用toString()方法了。
1)string()方法源码
/** * Returns the response as a string decoded with the charset of the Content-Type header. If that * header is either absent or lacks a charset, this will attempt to decode the response body as * UTF-8. */ public final String string() throws IOException { return new String(bytes(), charset().name()); }
2)toString()方法源码
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
2.7 实现在子线程中更新UI的多种方法
通常有如下三种方法:其原理都是向Android的主线程消息队列插入一条消息
1.使用Handler 2.如果是在Activity类里面中,使用runOnUiThread(Runnable action);方法 3.使用View上面的post方法 2.7.1 Handler
首先在Activity中创建一个Handler:
//这样直接使用内部类的方式创建,会有内存泄漏 private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); //更新UI tv.setText(msg.obj.toString()); } };
然后在异步回调中发送一个消息即可:
client.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, final Response response) throws IOException { String content = response.body().string(); //注意一定要sendToTarget(),不然接收不到 handler.obtainMessage(0, content).sendToTarget(); //其实这里直接使用runOnUiThread()也可以,不用Handler,如后面所示 } });
2.7.2 runOnUiThread(Runnable action)
使用这个方式的前提是:在Activity类中或者将context转为Activity
client.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, final Response response) throws IOException { final String string = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { tv.setText(string); } }); } });
2.7.3 View的post方法
client.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, final Response response) throws IOException { final String string = response.body().string(); //post()方法 textView.post(new Runnable() { @Override public void run() { textView.setText(content); } }); } });
2.8 传递请求参数
2.8.1 Get传递参数
Get请求方式传递参数只能通过拼接URL来完成,如要请求如下地址:http://me.woblog.cn/?s=android&order=0
//将要传递的参数添加到Map中,比如:用户登录名,密码 HashMap
params = new HashMap<>(); params.put("s","Android"); params.put("order",0); //然后调用一个方法格式化参数 String url= formatParams("http://me.woblog.cn/",params); Request request = new Request.Builder() .url(url) .build(); /** * 将Map的key和value拼接成key=value的格式 * @param url * @param params * @return 参考值:http://me.woblog.cn/?order=0&s=Android& */ private String formatParams(String url, HashMap params) { StringBuilder sb = new StringBuilder(); sb.append(url); sb.append("?"); for (Map.Entry p : params.entrySet()) { sb.append(p.getKey()); sb.append("="); try { sb.append(URLEncoder.encode(p.getValue().toString(),"utf-8")); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } sb.append("&"); //这个&符号,最好在最后移除,因为末尾多一个 } return sb.toString(); } 2.8.2 Post传递参数
1)Post传递String
//这个类型要和服务端协定,不然他不知道怎么取该类型的数据 public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); //新建一段markdown文本 String postBody = "" + "Releases\\n" + "--------\\n" + "\\n" + " * 3333\\n" + " * 11111\\n" + " * 99999999\\n"; //通过RequestBody.create方法创建一个RequestBody RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody); //通过post方法传入requestBody Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(requestBody) .build();
2)Post传递文件
如下所示传递一个图片文件
//这个类型要和服务端协定,不然他不知道怎么取该类型的数据 public static final MediaType MEDIA_TYPE_JPG = MediaType.parse("image/jpeg; charset=utf-8"); //根据File创建一个一个请求体 RequestBody requestBody = RequestBody.create(MEDIA_TYPE_JPG, new File("/sdcard/a.jpg")); Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(requestBody) .build();
3)提交表单如上面所示
2.9 取消请求
2.10 OkHttp的优势
1.允许连接到同一个主机地址的所有请求 提高请求效率 2.共享Socket 减少了对服务器的请求次数 3.连接池 减少了请求延时 4.缓存响应数据 来减少重复的网络请求
3.OkHttp的高级使用
3.1 下载文件
3.2 拦截器
拦截器是Okhttp中很强大的机制,可以用来监视,重写,重试调用请求。
3.3 缓存策略
3.4 设置代理
3.5 Cookie
4.OkHttp的封装
5.案例
5.1 通过同步请求方式
注意:要开启子线程
5.1.1 效果图
1)未请求之前
2)请求之后
5.1.2 创建布局
5.1.3 处理业务
public class MainActivity extends AppCompatActivity { private final OkHttpClient okHttpClient = new OkHttpClient(); private TextView contentTextView; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); contentTextView = findViewById(R.id.tv_content); button = findViewById(R.id.button); //按钮点击事件 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { synchronize(); } }); } /** * 同步请求 */ private void synchronize(){ //开启子线程 new Thread(new Runnable() { @Override public void run() { try { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.baidu.com") .build(); Response response = client.newCall(request).execute(); final String responseData = response.body().string(); //在主线程中更新UI runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this,responseData,Toast.LENGTH_LONG); contentTextView.setText(responseData); } }); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }
5.1.4 添加权限
5.2 通过异步请求方式
5.2.1 效果图
1)未请求前
2)请求后
5.2.2 布局
布局跟同步请求方式一样
5.2.3 业务逻辑
public class MainActivity extends AppCompatActivity { private final OkHttpClient okHttpClient = new OkHttpClient(); private TextView contentTextView; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); contentTextView = findViewById(R.id.tv_content); button = findViewById(R.id.button); //按钮点击事件 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { asynchronous(); } }); } /** * 异步请求 */ public void asynchronous(){ OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.baidu.com") .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 { //获取返回码 int code = response.code(); //获取头部信息 Headers headers = response.headers(); //获取内容信息类型 String contentType = headers.get("Content-Type"); //获取内容信息 String content = response.body().string(); final StringBuilder buf = new StringBuilder(); buf.append("\ncode: " + code); buf.append("\nheaders: " + headers); buf.append("\ncontentType: " + contentType); buf.append("\ncontent: " + content); //主线程中更新UI runOnUiThread(new Runnable() { @Override public void run() { contentTextView.setText(buf.toString()); } }); } }); } }
5.3.3 添加权限
如下:
/** * 获取OkHttpClient,去掉校验证书 * * @return OkHttpClient */ public static OkHttpClient getUnsafeOkHttpClient() { try { final TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } } }; final SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.sslSocketFactory(sslSocketFactory); builder.hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); return builder.build(); } catch (Exception e) { throw new RuntimeException(e); } }