网络请求-Android篇(Okhttp和Retrofit)

一.OkHttp的介绍和基本用法

  OkHttp是一个流行的开源Java和Android应用程序的HTTP客户端。它由Square Inc.开发,提供了一种简单高效的方式来进行应用程序中的HTTP请求。要在Java或Android项目中使用OkHttp,您需要将OkHttp依赖项添加到您的build.gradle文件中。然后,您可以创建一个OkHttpClient实例,并使用它来进行HTTP请求。OkHttp提供了各种类和方法,用于构建和执行请求、处理响应。使用OkHttp的时候,需要引入:implementation 'com.squareup.okhttp3:okhttp:4.10.0',别忘了添加网络权限!

  由于在进行网络请求的时候,我们主要用到get和post两种方式,下面就以这两个为例进行代码展示。

  1.Get方式:GET请求将参数附加在URL的查询字符串中,即在URL后面使用?符号连接参数键值对。get方式中又可以分为两种情况,分别是同步请求和异步请求;同步请求在进行请求的时候,当前线程会阻塞住,直到得到服务器的响应后,后面的代码才会执行;而异步请求不会阻塞当前线程,它采用了回调的方式,请求是在另一个线程中执行的,不会影响当前的线程。下面给出代码:

代码语言:javascript
复制
public void getSync(){//同步请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient okHttpClient=new OkHttpClient();
                Request request=new Request.Builder()
                        .url("https://www.httpbin.org/get?a=1&b=2")
                        .build();
                //准备好请求的Call对象
                Call call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("getSync",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    public void getAsync(){//异步请求
        OkHttpClient okHttpClient=new OkHttpClient();
        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) {
        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if(response.isSuccessful()){
                Log.i("getAsync",response.body().string());
            }
        }
    });
}</code></pre></div></div><p>  2.Post方式:POST请求将参数放在请求的主体中,不会直接显示在URL中。Post请求也分为同步和异步方式,和get方式用法相同,代码如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">public void postSync(){//同步请求
    new Thread(new Runnable() {
        @Override
        public void run() {
            OkHttpClient okHttpClient=new OkHttpClient();
            FormBody formBody=new FormBody.Builder()
                    .add(&#34;a&#34;,&#34;1&#34;)
                    .add(&#34;b&#34;,&#34;2&#34;)
                    .build();
            Request request=new Request.Builder()
                    .post(formBody)
                    .url(&#34;https://www.httpbin.org/post&#34;)
                    .build();
            //准备好请求的Call对象
            Call call = okHttpClient.newCall(request);
            try {
                Response response = call.execute();
                Log.i(&#34;postSync&#34;,response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}
public void postAsync(){//异步请求
    OkHttpClient okHttpClient=new OkHttpClient();
    FormBody formBody=new FormBody.Builder()
            .add(&#34;a&#34;,&#34;1&#34;)
            .add(&#34;b&#34;,&#34;2&#34;)
            .build();
    Request request=new Request.Builder()
            .post(formBody)
            .url(&#34;https://www.httpbin.org/post&#34;)
            .build();
    //准备好请求的Call对象
    Call call = okHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {

        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if(response.isSuccessful()){
                Log.i(&#34;postAsync&#34;,response.body().string());
            }
        }
    });
}</code></pre></div></div><p>  上面是通过表单的方式将数据提交给服务器,那如果要上传文件给服务器呢?使用Multipart。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">//提交多个文件给服务器
public void postFiles(){
    OkHttpClient okHttpClient=new OkHttpClient();
    File file1=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+File.separator+&#34;a.jpg&#34;);
    File file2=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+File.separator+&#34;b.jpg&#34;);
    RequestBody requestBody1=RequestBody.create(file1, MediaType.parse(&#34;application/x-jpg&#34;));
    RequestBody requestBody2=RequestBody.create(file2, MediaType.parse(&#34;application/x-jpg&#34;));
    MultipartBody multipartBody=new MultipartBody.Builder()
            .addFormDataPart(&#34;a.jpg&#34;,file1.getName(),requestBody1)
            .addFormDataPart(&#34;b.jpg&#34;,file2.getName(),requestBody2)
            .build();
    Request request=new Request.Builder()
            .post(multipartBody)
            .url(&#34;https://www.httpbin.org/post&#34;)
            .build();
    //准备好请求的Call对象
    Call call = okHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {

        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if(response.isSuccessful()){
                Log.i(&#34;postFiles&#34;,response.body().string());
            }
        }
    });
}</code></pre></div></div><p>  如果要查看各个文件类型所对应的Content-type字符串,可以访问以下这个网址:https://www.runoob.com/http/http-content-type.html</p><p>  提交Json字符串给服务器:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">//提交json数据
public void postJson(){
    OkHttpClient okHttpClient=new OkHttpClient();
    RequestBody requestBody=RequestBody.create(&#34;{\&#34;a\&#34;:1,\&#34;b\&#34;:2}&#34;,MediaType.parse(&#34;application/json&#34;));//记得使用转义字符处理内部的双引号
    Request request=new Request.Builder()
            .post(requestBody)
            .url(&#34;https://www.httpbin.org/post&#34;)
            .build();
    //准备好请求的Call对象
    Call call = okHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {

        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if(response.isSuccessful()){
                Log.i(&#34;postJson&#34;,response.body().string());
            }
        }
    });
}</code></pre></div></div><p>  3.拦截器的使用:OkHttp的拦截器(Interceptors)提供了强大的自定义和修改HTTP请求和响应的能力。拦截器允许在发送请求前、收到响应后以及其他阶段对HTTP流量进行拦截和处理。例如:拦截器可以修改请求的URL、请求方法、请求头部、请求体等。这对于添加身份验证头、设置缓存控制头等场景很有用。用法如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">public void interceptor(){
    OkHttpClient okHttpClient=new OkHttpClient.Builder()//添加拦截器的使用OkHttpClient的内部类Builder
            .addInterceptor(new Interceptor() {//使用拦截器可以对所有的请求进行统一处理,而不必每个request单独去处理
                @NonNull
                @Override
                public Response intercept(@NonNull Chain chain) throws IOException {
                    //前置处理,以proceed方法为分割线:提交请求前
                    Request request = chain.request().newBuilder()
                            .addHeader(&#34;id&#34;, &#34;first request&#34;)
                            .build();
                    Response response = chain.proceed(request);
                    //后置处理:收到响应后
                    return response;
                }
            })
            .addNetworkInterceptor(new Interceptor() {//这个在Interceptor的后面执行,无论添加顺序如何
                @NonNull
                @Override
                public Response intercept(@NonNull Chain chain) throws IOException {
                    Log.i(&#34;id&#34;,chain.request().header(&#34;id&#34;));
                    return chain.proceed(chain.request());
                }
            })
            .cache(new Cache(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+&#34;/cache&#34;),1024*1024))//添加缓存
            .build();
    Request request=new Request.Builder()
            .url(&#34;https://www.httpbin.org/get?a=1&amp;b=2&#34;)
            .build();
    //准备好请求的Call对象
    Call call = okHttpClient.newCall(request);
    //异步请求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {

        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if(response.isSuccessful()){
                Log.i(&#34;interceptor&#34;,response.body().string());
            }
        }
    });
}</code></pre></div></div><p>  4.Cookie的使用:大家应该有这样的经历,就是有些网站的好多功能都需要用户登录之后才能访问,而这个功能可以用cookie实现,在客户端登录之后,服务器给客户端发送一个cookie,由客户端保存;然后客服端在访问需要登录之后才能访问的功能时,只要携带这个cookie,服务器就可以识别该用户是否登录。用法如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">public void cookie(){
    Map&lt;String,List&lt;Cookie&gt;&gt; cookies=new HashMap&lt;&gt;();
    new Thread(new Runnable() {
        @Override
        public void run() {
            OkHttpClient okHttpClient=new OkHttpClient.Builder()
                    .cookieJar(new CookieJar() {
                        @Override
                        public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List&lt;Cookie&gt; list) {//保存服务器发送过来的cookie
                            cookies.put(&#34;cookies&#34;,list);
                        }

                        @NonNull
                        @Override
                        public List&lt;Cookie&gt; loadForRequest(@NonNull HttpUrl httpUrl) {//请求的时候携带cookie
                            if(httpUrl.equals(&#34;www.wanandroid.com&#34;)){
                                return cookies.get(&#34;cookies&#34;);
                            }
                            return new ArrayList&lt;&gt;();
                        }
                    })
                    .build();
            FormBody formBody=new FormBody.Builder()
                    .add(&#34;username&#34;,&#34;ibiubiubiu&#34;)
                    .add(&#34;password&#34;,&#34;Lhh823924.&#34;)
                    .build();
            Request request=new Request.Builder()  //模拟登录
                    .url(&#34;https://wanandroid.com/user/lg&#34;)
                    .post(formBody)
                    .build();
            //准备好请求的Call对象
            Call call = okHttpClient.newCall(request);
            try {
                Response response = call.execute();
                Log.i(&#34;login&#34;,response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
            //请求收藏页面,必须登录之后才能访问到
            request=new Request.Builder()
                    .url(&#34;https://wanandroid.com/lg/collect&#34;)
                    .build();
            //准备好请求的Call对象
            call = okHttpClient.newCall(request);
            try {
                Response response = call.execute();
                Log.i(&#34;collect&#34;,response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}</code></pre></div></div><p>二.Retrofit的介绍和基本使用</p><p>  Retrofit是一个基于OkHttp的强大且易于使用的网络请求库,用于在Android和Java应用程序中进行网络通信。它有以下的优点:</p><p>  1.简化的API: Retrofit提供了一个简洁、直观的API,使得定义和执行网络请求变得非常容易。您可以使用注解来描述请求方法、URL路径、请求参数以及响应类型等信息,从而减少了样板代码的编写。</p><p>  2.拦截器支持: Retrofit完全兼容OkHttp拦截器,这使得您可以使用OkHttp的拦截器来自定义和修改请求和响应。这为您提供了更大的灵活性和定制能力。</p><p>  3.文件上传和下载: Retrofit支持文件上传和下载,并提供了进度回调机制,方便跟踪上传和下载进度。</p><p>  Retrofit的基本用法如下:</p><p>  1.添加依赖项:在您的Android或Java项目中的build.gradle文件中添加Retrofit的依赖项</p><p>    implementation &#39;com.squareup.retrofit2:retrofit:2.9.0&#39;</p><p>  2.创建API接口:定义一个包含请求方法的接口,该接口描述了请求的类型、URL路径、请求参数和响应类型。使用注解来配置请求方法的特性。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">/**
  • 服务器域名:https://www.httpbin.org/

  • 接口:post,参数username,password

  • 接口:get,参数username,password
    */
    //第一步,根据http接口创建java接口
    public interface HttpbinService {
    @GET("get") //这是GET请求的相对路径。它指定了在基本URL之后所附加的路径,以构建完整的请求URL。例如,如果基本URL为https://api.example.com/,那么最终的请求URL将是https://api.example.com/get
    Call<ResponseBody> get(@Query("username") String username, @Query("password") String password);//注意get请求用@Query注解标注请求参数
    @POST("post")
    @FormUrlEncoded
    Call<ResponseBody> post(@Field("username") String username,@Field("password") String password);//post请求用@Field注解
    @GET
    Call<ResponseBody> download(@Url String url);//使用Url注解需要提供完整的资源路径,这时设置的baseUrl就不起作用了
    @POST("post")
    @Multipart
    Call<ResponseBody> upload(@Part MultipartBody.Part file);
    }

  3.创建Retrofit实例:使用Builder模式创建Retrofit实例,并配置基本的URL以及其他可选的设置,如转换器、拦截器等。  

代码语言:javascript
复制
    private Retrofit retrofit;
private HttpbinService httpbinService;
retrofit=new Retrofit.Builder()
.baseUrl("https://httpbin.org/")
.build();
httpbinService=retrofit.create(HttpbinService.class);  

  4.创建API实现:通过Retrofit创建接口的实现,并使用它来执行网络请求。

代码语言:javascript
复制
public void post(){
Call<ResponseBody> call = httpbinService.post("jack", "123456");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.i("post",response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

     @Override
     public void onFailure(Call&lt;ResponseBody&gt; call, Throwable t) {

     }
 });

}
public void get(){
new Thread(new Runnable() {
@Override
public void run() {
Call<ResponseBody> call = httpbinService.get("jack", "password");
try {
Response<ResponseBody> response = call.execute();
Log.i("get",response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

  文件的上传和下载:

代码语言:javascript
复制
public void download(){
Call<ResponseBody> call = httpbinService.download("https://nginx.org/download/nginx-1.20.2.tar.gz");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response.isSuccessful()){
try {
InputStream inputStream = response.body().byteStream();
              //context.getExternalFilesDir(null)是一个用于获取本应用程序的外部存储目录的方法,需要注意的是从Android11开始,应用程序不能直接访问SD卡的根目录,Android应用程序只能在应用的私有目录或特定的公共目录中存储文件
FileOutputStream out=new FileOutputStream(context.getExternalFilesDir(null).getAbsolutePath()+"/nginx.tar.gz");
byte[] data=new byte[4096];
int len=0;
while((len=inputStream.read(data))!=-1){
out.write(data,0,len);
}
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

     @Override
     public void onFailure(Call&lt;ResponseBody&gt; call, Throwable t) {

     }
 });

}
public void upload(){
MultipartBody.Part video = MultipartBody.Part.createFormData("video", "video.mp4", RequestBody.create(MediaType.parse("video/mpeg4"), new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/video.mp4")));
Call<ResponseBody> call = httpbinService.upload(video);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.i("video",response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

     @Override
     public void onFailure(Call&lt;ResponseBody&gt; call, Throwable t) {

     }
 });

}

  转换器的使用:在以上的例子中,服务器返回给我们的结果要么是字符串形式,要么是输入流的形式;那如果服务器给我们返回Json格式的数据,并且我们要求程序将Json自动转换成对应的javaBean呢,那么这时就可以用到转换器了。

  比如,服务器给我们返回的Json字符串如下:

代码语言:javascript
复制
{
"code": 0,
"msg": "ok",
"message": "ok",
"data": []
}

  那么,首先我们编写对应的javaBean,可以自己手写,也可以找网上的一些转换工具。

代码语言:javascript
复制
public class Bean{

private Integer code;
private String msg;
private String message;
private List<String> data;

public Integer getCode() {
return this.code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMsg() {
return this.msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public String getMessage() {
return this.message;
}

public void setMessage(String message) {
this.message = message;
}

public List<String> getData() {
return this.data;
}

public void setData(List<String> data) {
this.data = data;
}

@Override
public String toString() {
return "Bean{" +
"code=" + code +
", msg='" + msg + '&#39;' +
", message='" + message + '&#39;' +
", data=" + data +
'}';
}
}

  创建Api接口:

代码语言:javascript
复制
public interface Bilibili {
@GET("api/tooltip/query.list.do")
Call<Bean> get(); //将Call当中的泛型类型改为想要返回的javaBean类型
}

 进行网络请求并得到响应结果:

代码语言:javascript
复制
public void converter(){
Retrofit retrofit=new Retrofit.Builder()
.baseUrl("https://message.bilibili.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
Bilibili bilibili = retrofit.create(Bilibili.class);
Call<Bean> call = bilibili.get();
call.enqueue(new Callback<Bean>() {
@Override
public void onResponse(Call<Bean> call, Response<Bean> response) {
Bean bean = response.body();//响应结果自动转成了javaBean类型
Log.i("bean",bean.toString());
}

     @Override
     public void onFailure(Call&lt;Bean&gt; call, Throwable t) {

     }
 });

}

  以上就是OkHttp和Retrofit的基本用法了。