由Square公司开发并共享开源的高效网络访问框架,使用简单,替代了HttpUrlConnection、HttpClient。
Overview - OkHttp (square.github.io)
默认的OkHttp具备以下特性:
支持HTTP/2,允许所有同一个主机地址的请求共享一个socket连接。
连接池减少请求延迟。
透明的GZIP压缩减少响应数据的大小。
缓存响应内容,避免一些完全重复的请求。
网络出现问题后,OkHttp会保持不变,自动从问题中恢复。
在build.gradle文件的dependencies中添加以下代码:
implementation "com.squareup.okhttp3:okhttp:4.9.3"
在AndroidMainFest.xml文件中添加网络请求权限:
用到的四个类:
基本使用步骤:
Http请求需要放在开启线程来完成。而同步请求时放在子线程中完成的,所以会阻塞主线程的执行,一般不适用。
同步请求(Get)需要另外开一个线程(注:更改界面UI需要在主线程中更改)
OkHttpClient client = new OkHttpClient();
// OkHttpClient client = new OkHttpClient.Builder().build();
new Thread() {
@Override
public void run() {
Request request = new Request.Builder()
.url(url)
.get()
.build();
//准备请求的Call对象
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
异步请求(Get)
OkHttpClient client = new OkHttpClient();
// OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/get?a=1&b=2")
.build();
//准备请求的Call对象
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
//响应失败
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.e(TAG, "onFailure: " + "与服务器建立连接失败!");
}
//成功响应
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful()){
Log.e(TAG, "getAsync: " + response.body().string());
}
}
});
Post也可传送requestBody的格式
同步请求(Post)
new Thread(){
@Override
public void run() {
FormBody formBody = new FormBody.Builder()
.add("a","1")
.add("b","2")
.build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(formBody)
.build();
//准备请求的Call对象
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
Log.e(TAG, "postSync: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
异步请求(Post)
FormBody formBody = new FormBody.Builder()
.add("string 1","value 1")
.add("str 2","val 2")
.build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(formBody)
.build();
//准备请求的Call对象
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.e(TAG, "onFailure: " + "与服务器建立连接失败!");
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful()){
Log.e(TAG, "postAsync: " + response.body().string());
// showResponse("postAsync", response.body().string());
}
}
});
文件上传
public void fileUpload(){
OkHttpClient okHttpClient = new OkHttpClient();
File file1 = new File(path1);
File file2 = new File(path2);
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart("file1", file1.getName(), RequestBody.create(file1, MediaType.parse("text/plain")))
.addFormDataPart("file2", file2.getName(), RequestBody.create(file2, MediaType.parse("text/plain")))
.addFormDataPart("a", "1")
.build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(multipartBody)
.build();
Call call = okHttpClient.newCall(request);
// call.enqueue(new Callback() {
// @Override
// public void onFailure(@NonNull Call call, @NonNull IOException e) {
// System.out.println("onFailure: " + "服务器建立连接失败");
// }
//
// @Override
// public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
// if (response.isSuccessful()){
// String msg = response.body().string();
// System.out.println("onResponse: " + response.body().string());
// }
// }
// });
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
Json格式
public void jsonTest(){
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = RequestBody.create("{\"a\":1,\"b\":2}", MediaType.parse("application/json"));
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
是通过责任链模式来实现的!
参考博客:OKhttp拦截器_静待岁月的博客-CSDN博客_okhttp的拦截器
拦截器可以一次性对所有的请求和返回值进行修改
拦截器可以一次性对请求的参数和返回的结果进行编码
拦截器可以对所有的请求做统一的日志记录,不需要在每个请求开始或者结束的位置都添加一个日志操作
其他需要对请求和返回进行统一处理的需求
OkHttp内置拦截器:
addInterceptor(Interceptor)
,这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
RetryAndFollowUpInterceptor
,这里会对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
BridgeInterceptor
,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
CacheInterceptor
,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
ConnectInterceptor
,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec。
networkInterceptors
,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
CallServerInterceptor
,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。
APP层面的拦截器(Application Interception)
网络请求层面的拦截器(Network Interception)
Application Interceptor是在请求执行刚开始,还没有执行OkHttp的核心代码前进行拦截,Application拦截器的作用:
1、不需要担心是否影响OKHttp的请求策略和请求速度
2、即使是从缓存中取数据,也会执行Application拦截器
3、允许重试,即Chain.proceed()可以执行多次(尽量不要盲目执行多次,需要加入自己的逻辑判断)
Network Interception是在连接网络之前
1、可以修改OkHttp框架自动添加的一些属性(一般不修改)
2、可以观察最终完整的请求参数
使用方法:
//实例化一个拦截器
Interceptor appInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//---------请求之前------------
Response response = chain.proceed(request1);
//---------请求之后------------
return response;
}
};
//配置拦截器
okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(appInterceptor)//Application拦截器
.addNetworkInterceptor(networkInterceptor)//Network拦截器
.build();
OkHttp实现异步请求采用了双任务队列机制,通过Dispatcher来调度任务
新加入的异步任务AsyncCall进入等待队列readyAsyncCalls
遍历readyAsyncCalls判断当前情况:是否超过最大并发数?是否超过同个主机最大请求数?
满足条件直接把AsyncCall加入到正在执行的队列RunningAsyncCalls,并且使用线程池执行新加入的异步任务AsyncCall
AsyncCall执行结束,再次回到Dispatcher的promoteAndExecute()
Http中的KeepAlive机制
OkHttp默认支持5个并发的KeepAlive,链路默认存活时间为5分钟
Retrofit地址:square/retrofit: A type-safe HTTP client for Android and the JVM (github.com)
Retrofit 是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求。本文使用Retrofit2进行实例演示。
本质过程:App应用程序通过Retrofit请求网络,实质上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由okhttp来完成后续的请求工作。在服务端返回数据后,okhttp将原始数据交给Retrofit,Retrofit根据用户需求解析。
超级解耦 ,接口定义、接口参数、接口回调不在耦合在一起
可以配置不同的httpClient来实现网络请求,如okhttp、httpclient
支持同步、异步、Rxjava
可以配置不同反序列化工具类来解析不同的数据,如json、xml
请求速度快,使用方便灵活简洁
支持RESTful API设计风格(什么是RESTful API?_折海棠赠晩宁.的博客-CSDN博客 什么是RESTful API?_Evan Wang的博客-CSDN博客_api restful)
通过注释配置请求
1. 创建工程,引入Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
由于Retrofit封装了OkHttp,所以使用到OkHttp时并不需要再引入OkHttp
2.创建业务请求接口HttpRetrofit
public interface HttpRetrofit {
@GET("get") //请求方式,"get"以参数名的形式拼接
Call get(@Query("username") String username, @Query("password") String password);
@POST("post") //请求方式,"get"以参数名的形式拼接
@FormUrlEncoded //参数的提交格式
Call post(@Field("username") String username, @Field("password") String password);
}
@GET注解表示GET请求,@Query表示请求参数,将会以key=value(@Query注解参数名称为key,调用传进来的值为value)的方式拼接在url后面.
@POST注解表示POST请求,@FormUrlEncoded将会自动将请求参数的类型设置为application/x-www-form-urlencoded,@FormUrlEncoded注解不能用于Get请求。@Field注解将每一个请求参数都存放至请求体中,还可以添加encoded参数,该参数为boolean型,具体的用法为: @Field(value = “password”, encoded = true) String pwd encoded参数为true的话,key-value-pair将会被编码,即将中文和特殊字符进行编码转换.
3.创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("服务器地址").build();
HttpRetrofit httpRetrofit = retrofit.create(HttpRetrofit.class);
4.调用请求方法得到Call实例,然后用Call实例完成请求
public void getAsync(View view) {
Call call = httpRetrofit.get("zhangsan", "123456");
//同步
new Thread(){
@Override
public void run() {
try {
Log.e(TAG, "postSync: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
//异步
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()){
try {
Log.e(TAG, "getAsync: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e(TAG, "onFailure: " + "与服务器建立连接失败!");
}
});
}
@GET get请求
@POST post请求
@PUT put请求
@DELETE delete请求
@PATCH patch请求,该请求是对put请求的补充,用于更新局部资源
@HEAD head请求
@OPTIONS options请求
@HTTP 通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody
@Body 多用于Post请求发送非表达数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用Post发送Json数据,添加GsonConverterFactory则是将body转化为json字符串进行传递
@Filed 多用于Post方式传递参数,需要结合@FromUrlEncoded使用,即以表单的形式传递参数
@FiledMap 多用于Post请求中的表单字段,需要结合@FromUrlEncoded使用
@Part 用于表单字段,Part和PartMap与@multipart注解结合使用,适合文件上传的情况
@PartMap 用于表单字段,默认接受类型是Map
@Path 用于Url中的占位符,作用于方法
@Query 用于Get请求中的参数
@QueryMap 与Query类似,用于不确定表单参数
@Url 指定请求路径
@FromUrlCoded 表示请求发送编码表单数据,每个键值对需要使用@Filed注解
@Multipart 表示请求发送form_encoded数据(使用于有文件上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值
@Streaming 表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,该注解在下载大文件时特别有用
@GET("get") //请求方式,"get"以参数名的形式拼接
Call get(@Query("username") String username, @Query("password") String password, Map map);
向build.gradle中添加以下代码:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
手动解析
//JavaBeanJSON 可以为自定义的某种类型
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl("https://www.httpbin.org/")
.build();
HttpRetrofit httpRetrofit1 = retrofit1.create(HttpRetrofit.class);
Call call = httpRetrofit1.get("zhangsan", "123456");
Response response = call.execute();
String stringJSON = response.body().string();
JavaBeanJSON javaBeanJSON= new Gson().fromJson(stringJSON, JavaBeanJSON.class);
自动解析
//JavaBeanJSON 可以为自定义的某种类型
Retrofit retrofit2 = new Retrofit.Builder()
.baseUrl("https://www.httpbin.org/")
.addConverterFactory(GsonConverterFactory.create()) //添加自动解析
.build();
HttpRetrofit httpRetrofit2 = retrofit2.create(HttpRetrofit.class);
Call call = httpRetrofit1.get("zhangsan", "123456");
Response response = call.execute();
JavaBeanJSON javaBeanJSON = response.body();
Hilt 简单使用篇_chan_yang的博客-CSDN博客_hilt使用
Hilt的使用_吴唐人的博客-CSDN博客_hilt使用
Retrofit基于OkHttp具有高效的特性,但由于其高度封装的原因,导致其扩展性稍差。
由于解析数据都是使用统一的Converter,如果服务器不能给出统一的API形式,将很难处理
配合使用Hilt进行依赖注入,使得Retrofit的灵活性提高
RxJava支持了线程切换,Retrofit对于RxJava无缝支持,几乎可以满足所有网络请求业务的实现,可以利用这一点搭建一个完善的网络访问模块
使用:
将Call换为observer
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
public void rxjavaTest(View view){
HashMap> cookiesMap = new HashMap<>();
Retrofit retrofit3 = new Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
.callFactory(new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
cookiesMap.put(url.host(),cookies);
}
@Override
public List loadForRequest(HttpUrl url) {
List cookie = cookiesMap.get(url.host());
return cookie==null?new ArrayList():cookie;
}
})
.build()) //定制自己的OkHttp
.addConverterFactory(GsonConverterFactory.create()) //添加转换器
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //添加适配器
.build();
HttpRetrofit httpRetrofit3 = retrofit3.create(HttpRetrofit.class);
httpRetrofit3.login("zhangsan","123456")
.flatMap(new Function>() {
@Override
public Publisher apply(ResponseBody responseBody) throws Throwable {
return httpRetrofit3.getArticle(0);
}
}) //第一次请求
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread()) //切换主线程,Java中需要使用Schedulers.newThread()
.subscribe(new Consumer() {
@Override
public void accept(ResponseBody responseBody) throws Throwable {
Log.e(TAG, responseBody.string());
}
}); //第二次请求
}
添加权限
添加依赖
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.retrofit2:adapter-rxjava3:2.9.0"
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
severvice
public interface FileService {
@POST("post")
@Multipart
Call upload(@Part MultipartBody.Part file);
@GET
@Streaming //防止文件过大而发生内存溢出
Call download(@Url String url);
@GET
@Streaming //防止文件过大而发生内存溢出
Flowable downloadRxJava(@Url String url);
}
应用
public class FileUnitTest {
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.httpbin.org/")
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //添加适配器
.build();
private FileService fileService = retrofit.create(FileService.class);
@Test
public void uploadFileTest() throws IOException {
File file1 = new File("D:\\Android\\Works\\AndroidCommon\\myretrofit\\files\\1.txt");
MultipartBody.Part part = MultipartBody.Part
.createFormData("file1", file1.getName(), RequestBody.create(MediaType.parse("text/plain"), file1));
Call call = fileService.upload(part);
Response response = call.execute();
System.out.println(response.body().string());
}
@Test
public void downloadFileTest() throws IOException {
Call call = fileService.download("https://repo1.maven.org/maven2/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.jar");
Response response = call.execute();
InputStream inputStream = response.body().byteStream();
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Android\\Works\\AndroidCommon\\myretrofit\\libs\\retrofit-2.9.0.jar");
int len;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
inputStream.close();
fileOutputStream.close();
}
@Test
public void downloadRxJavaFileTest() throws InterruptedException {
Flowable flowable = fileService.downloadRxJava("https://repo1.maven.org/maven2/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.jar");
flowable.subscribe(new Consumer() {
@Override
public void accept(ResponseBody responseBody) throws Throwable {
InputStream inputStream = responseBody.byteStream();
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Android\\Works\\AndroidCommon\\myretrofit\\libs\\retrofit-2.9.0(1).jar");
int len;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
System.out.println("下载成功");
inputStream.close();
fileOutputStream.close();
}
});
}
}