網路請求-Android篇(Okhttp和Retrofit)

2023-08-25 21:00:30

一.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方式中又可以分為兩種情況,分別是同步請求和非同步請求;同步請求在進行請求的時候,當前執行緒會阻塞住,直到得到伺服器的響應後,後面的程式碼才會執行;而非同步請求不會阻塞當前執行緒,它採用了回撥的方式,請求是在另一個執行緒中執行的,不會影響當前的執行緒。下面給出程式碼:

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

  2.Post方式:POST請求將引數放在請求的主體中,不會直接顯示在URL中。Post請求也分為同步和非同步方式,和get方式用法相同,程式碼如下:

public void postSync(){//同步請求
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient okHttpClient=new OkHttpClient();
                FormBody formBody=new FormBody.Builder()
                        .add("a","1")
                        .add("b","2")
                        .build();
                Request request=new Request.Builder()
                        .post(formBody)
                        .url("https://www.httpbin.org/post")
                        .build();
                //準備好請求的Call物件
                Call call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("postSync",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    public void postAsync(){//非同步請求
        OkHttpClient okHttpClient=new OkHttpClient();
        FormBody formBody=new FormBody.Builder()
                .add("a","1")
                .add("b","2")
                .build();
        Request request=new Request.Builder()
                .post(formBody)
                .url("https://www.httpbin.org/post")
                .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("postAsync",response.body().string());
                }
            }
        });
    }

  上面是通過表單的方式將資料提交給伺服器,那如果要上傳檔案給伺服器呢?使用Multipart。

//提交多個檔案給伺服器
    public void postFiles(){
        OkHttpClient okHttpClient=new OkHttpClient();
        File file1=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+File.separator+"a.jpg");
        File file2=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+File.separator+"b.jpg");
        RequestBody requestBody1=RequestBody.create(file1, MediaType.parse("application/x-jpg"));
        RequestBody requestBody2=RequestBody.create(file2, MediaType.parse("application/x-jpg"));
        MultipartBody multipartBody=new MultipartBody.Builder()
                .addFormDataPart("a.jpg",file1.getName(),requestBody1)
                .addFormDataPart("b.jpg",file2.getName(),requestBody2)
                .build();
        Request request=new Request.Builder()
                .post(multipartBody)
                .url("https://www.httpbin.org/post")
                .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("postFiles",response.body().string());
                }
            }
        });
    }

  如果要檢視各個檔案型別所對應的Content-type字串,可以存取以下這個網址:https://www.runoob.com/http/http-content-type.html

  提交Json字串給伺服器:

//提交json資料
    public void postJson(){
        OkHttpClient okHttpClient=new OkHttpClient();
        RequestBody requestBody=RequestBody.create("{\"a\":1,\"b\":2}",MediaType.parse("application/json"));//記得使用跳脫字元處理內部的雙引號
        Request request=new Request.Builder()
                .post(requestBody)
                .url("https://www.httpbin.org/post")
                .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("postJson",response.body().string());
                }
            }
        });
    }

  3.攔截器的使用:OkHttp的攔截器(Interceptors)提供了強大的自定義和修改HTTP請求和響應的能力。攔截器允許在傳送請求前、收到響應後以及其他階段對HTTP流量進行攔截和處理。例如:攔截器可以修改請求的URL、請求方法、請求頭部、請求體等。這對於新增身份驗證頭、設定快取控制頭等場景很有用。用法如下:

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("id", "first request")
                                .build();
                        Response response = chain.proceed(request);
                        //後置處理:收到響應後
                        return response;
                    }
                })
                .addNetworkInterceptor(new Interceptor() {//這個在Interceptor的後面執行,無論新增順序如何
                    @NonNull
                    @Override
                    public Response intercept(@NonNull Chain chain) throws IOException {
                        Log.i("id",chain.request().header("id"));
                        return chain.proceed(chain.request());
                    }
                })
                .cache(new Cache(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+"/cache"),1024*1024))//新增快取
                .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) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                if(response.isSuccessful()){
                    Log.i("interceptor",response.body().string());
                }
            }
        });
    }

  4.Cookie的使用:大家應該有這樣的經歷,就是有些網站的好多功能都需要使用者登入之後才能存取,而這個功能可以用cookie實現,在使用者端登入之後,伺服器給使用者端傳送一個cookie,由使用者端儲存;然後客服端在存取需要登入之後才能存取的功能時,只要攜帶這個cookie,伺服器就可以識別該使用者是否登入。用法如下:

public void cookie(){
        Map<String,List<Cookie>> cookies=new HashMap<>();
        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<Cookie> list) {//儲存伺服器傳送過來的cookie
                                cookies.put("cookies",list);
                            }

                            @NonNull
                            @Override
                            public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {//請求的時候攜帶cookie
                                if(httpUrl.equals("www.wanandroid.com")){
                                    return cookies.get("cookies");
                                }
                                return new ArrayList<>();
                            }
                        })
                        .build();
                FormBody formBody=new FormBody.Builder()
                        .add("username","ibiubiubiu")
                        .add("password","Lhh823924.")
                        .build();
                Request request=new Request.Builder()  //模擬登入
                        .url("https://wanandroid.com/user/lg")
                        .post(formBody)
                        .build();
                //準備好請求的Call物件
                Call call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("login",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //請求收藏頁面,必須登入之後才能存取到
                request=new Request.Builder()
                        .url("https://wanandroid.com/lg/collect")
                        .build();
                //準備好請求的Call物件
                call = okHttpClient.newCall(request);
                try {
                    Response response = call.execute();
                    Log.i("collect",response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

二.Retrofit的介紹和基本使用

  Retrofit是一個基於OkHttp的強大且易於使用的網路請求庫,用於在Android和Java應用程式中進行網路通訊。它有以下的優點:

  1.簡化的API: Retrofit提供了一個簡潔、直觀的API,使得定義和執行網路請求變得非常容易。您可以使用註解來描述請求方法、URL路徑、請求引數以及響應型別等資訊,從而減少了樣板程式碼的編寫。

  2.攔截器支援: Retrofit完全相容OkHttp攔截器,這使得您可以使用OkHttp的攔截器來自定義和修改請求和響應。這為您提供了更大的靈活性和客製化能力。

  3.檔案上傳和下載: Retrofit支援檔案上傳和下載,並提供了進度回撥機制,方便跟蹤上傳和下載進度。
  Retrofit的基本用法如下:
  1.新增依賴項:在您的Android或Java專案中的build.gradle檔案中新增Retrofit的依賴項
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
  2.建立API介面:定義一個包含請求方法的介面,該介面描述了請求的型別、URL路徑、請求引數和響應型別。使用註解來設定請求方法的特性。
/**
 * 伺服器域名: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以及其他可選的設定,如轉換器、攔截器等。  

    private Retrofit retrofit;
    private HttpbinService httpbinService;
    retrofit=new Retrofit.Builder()
                .baseUrl("https://httpbin.org/")
                .build();
    httpbinService=retrofit.create(HttpbinService.class);  

  4.建立API實現:通過Retrofit建立介面的實現,並使用它來執行網路請求。

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

  檔案的上傳和下載:

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<ResponseBody> 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<ResponseBody> call, Throwable t) { } }); }

  轉換器的使用:在以上的例子中,伺服器返回給我們的結果要麼是字串形式,要麼是輸入流的形式;那如果伺服器給我們返回Json格式的資料,並且我們要求程式將Json自動轉換成對應的javaBean呢,那麼這時就可以用到轉換器了。

  比如,伺服器給我們返回的Json字串如下:

{
            "code": 0,
            "msg": "ok",
            "message": "ok",
            "data": []
}

  那麼,首先我們編寫對應的javaBean,可以自己手寫,也可以找網上的一些轉換工具。

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 + '\'' +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

  建立Api介面:

public interface Bilibili {
    @GET("api/tooltip/query.list.do")
    Call<Bean> get();   //將Call當中的泛型型別改為想要返回的javaBean型別
}

 進行網路請求並得到響應結果:

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<Bean> call, Throwable t) {

            }
        });
    }

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