Android开发—OkHttp&Retrofit

1.OkHttp框架简介

由Square公司开发并共享开源的高效网络访问框架,使用简单,替代了HttpUrlConnection、HttpClient。

Overview - OkHttp (square.github.io)

默认的OkHttp具备以下特性:

支持HTTP/2,允许所有同一个主机地址的请求共享一个socket连接。

连接池减少请求延迟。

透明的GZIP压缩减少响应数据的大小。

缓存响应内容,避免一些完全重复的请求。

网络出现问题后,OkHttp会保持不变,自动从问题中恢复。

1.1引入依赖

在build.gradle文件的dependencies中添加以下代码:

implementation "com.squareup.okhttp3:okhttp:4.9.3"

在AndroidMainFest.xml文件中添加网络请求权限:

1.2 OkHttp的使用

用到的四个类:

  • OkHttpClient:OkHttp客户端类,用来构建请求发起类Call,设置超时,设置缓存,设置拦截器等;
  • Request:OkHttp请求包装类,用来封装网络请求信息及请求体,比如请求方式(GET、POST、DELETE、PUT等),url,headers,请求参数等,服务器端最终接受并解析的请求消息,就是由该类提供。OkHttpClient获取Call对象时,需要传入Request作为参数;
  • Call:发送请求类,该类用来发起OkHttp的网络请求(同步、异步),并将请求结果返回给调用者,该类初始化系统拦截器,并启动拦截器链;
  • Response:OkHttp响应包装类,用来封装OkHttp网络请求的结果,包括Http请求响应码,以及服务器端返回的数据(响应体),以及此次请求的Request信息。客户端最终接受并解析的由服务器返回的消息,就是该类提供的。同步请求可以直接获取该类的对象,异步请求通过接口回调获取该类对象。

基本使用步骤:

  1. 构建客户端对象OkHttpClient(一般单例创建)
  2. 构建请求对象Request
  3. 生成Call对象
  4. Call发起请求(同步/异步) 

1.3 OkHttp请求

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();
        }
    }

1.4 拦截器

是通过责任链模式来实现的!

参考博客: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)

Android开发—OkHttp&Retrofit_第1张图片

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();

1.5 OkHttp双任务队列机制

OkHttp实现异步请求采用了双任务队列机制,通过Dispatcher来调度任务

新加入的异步任务AsyncCall进入等待队列readyAsyncCalls

遍历readyAsyncCalls判断当前情况:是否超过最大并发数?是否超过同个主机最大请求数?

满足条件直接把AsyncCall加入到正在执行的队列RunningAsyncCalls,并且使用线程池执行新加入的异步任务AsyncCall

AsyncCall执行结束,再次回到Dispatcher的promoteAndExecute()

Android开发—OkHttp&Retrofit_第2张图片

1.6 Socket连接池复用

Android开发—OkHttp&Retrofit_第3张图片

 Http中的KeepAlive机制

OkHttp默认支持5个并发的KeepAlive,链路默认存活时间为5分钟

2. Retrofit

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)

  • 通过注释配置请求

2.1 Retrofit的使用

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: " + "与服务器建立连接失败!");
            }
        });
    }

2.2 注解

@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);

2.3 Gson解析

向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'
  1. 创建JSON对应的JavaBeanJSON对象和data对应的JavaBean对象
  2. 创建业务请求接口RetrofitService,请求方法的返回值为Call
  3. 创建Retrofit对象(添加JSON的自动解析器)
  4. 调用请求方法得到Call实例
  5. 使用Call实例完成请求,得到请求结果Response response
  6. 通过response对象获得请求的字符串stringJSON

手动解析

//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();

2.4 Hilt&Rxjava

Hilt 简单使用篇_chan_yang的博客-CSDN博客_hilt使用

Hilt的使用_吴唐人的博客-CSDN博客_hilt使用

Retrofit基于OkHttp具有高效的特性,但由于其高度封装的原因,导致其扩展性稍差。

由于解析数据都是使用统一的Converter,如果服务器不能给出统一的API形式,将很难处理

配合使用Hilt进行依赖注入,使得Retrofit的灵活性提高

RxJava支持了线程切换,Retrofit对于RxJava无缝支持,几乎可以满足所有网络请求业务的实现,可以利用这一点搭建一个完善的网络访问模块

使用:

  1. 引入RxJava
  2. 修改请求接口HttpRetrofit的方法
  3. 创建Retrofit对象
  4. 拿到HttpRetrofit对象
  5. 执行请求方法

将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());
                    }
                }); //第二次请求
    }

2.5文件上传与下载

添加权限

添加依赖

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();
            }
        });
    }
}

你可能感兴趣的:(android,android,studio)