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 实例创建方式
两种创建方式,如下所示:
//方式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请求会比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 创建
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.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 创建
execute()方法,来发送请求并获取服务器返回的数据,即Response。如下所示:
//获取到服务端返回的数据
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
2.6.2 组成
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.使用Handler2.如果是在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<String, Object> 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<String, Object> params) {
StringBuilder sb = new StringBuilder();
sb.append(url);
sb.append("?");
for (Map.Entry<String, Object> 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 创建布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get"
android:gravity="center"
android:textSize="20sp"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
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 添加权限
<uses-permission android:name="android.permission.INTERNET"/>
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 添加权限
<uses-permission android:name="android.permission.INTERNET"/>
6.去掉校验证书
如下:
/**
* 获取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);
}
}