在 Android 中使用 Retrofit 2 HTTP Client 發送數據

你將要創造什麼

原文:https://code.tutsplus.com/zh-...
原做:Chike Mgbemena
翻譯:Shen Yun Courtjava

什麼是Retrofit?

Retrofit 是一個用於Android 和Java 的類型安全的HTTP 客戶端。它經過將REST web service 的API轉換成Java的接口, 以簡化HTTP 鏈接的處理。在這裏, 我將告訴你怎麼使用這個在Android 中最經常使用而且最被推薦的HTTP 庫。react

這個強大的庫能夠很容易地處理JSON 或者XML 數據, 而後轉換成POJO。GET, POST, PUT, PATCH, 和DELETE這些請求均可以執行。android

和大多數開源軟件同樣, Retrofit 也是構建在其餘強大的庫之上。在底層, Retrofit 使用OKHttp (來自同一個開發者) 來處理網絡請求。一樣, Retrofit 也並無本身構建JSON 轉換器來轉換JSON 數據, 相反, 它經過支持下面一些JSON 轉換庫來將JSON 數據轉換成Java 對象:git

  • Gson: com.squareup.retrofit:converter-gsongithub

  • Jackson: com.squareup.retrofit:converter-jacksonweb

  • Moshi: com.squareup.retrofit:converter-moshijson

對於Protocol buffers (Google連接), 則可使用:緩存

  • Protobuf: com.squareup.retrofit2:converter-protobuf安全

  • Wire: com.squareup.retrofit2:converter-wire服務器

而對於XML, 可使用:

  • Simple Framework: com.squareup.retrofit2:converter-simpleframework

因此, 爲何使用Retrofit?

要本身開發一個類型安全的HTTP 庫來映射REST API 是一個痛點: 你必須處理好不少方面, 好比建立鏈接, 緩存, 失敗請求重試, 多線程, 處理響應, 處理錯誤, 等等。而從另外一個方面來講, Retrofit 是一個通過良好規劃, 擁有優質文檔, 通過全面測試的庫, 它將節省你大量的寶貴的時間。

在這裏, 我將經過建立一個簡單的APP 來說解該怎麼使用Retrofit 2 來處理網絡請求。 這個APP 會執行POST, PUT (爲了更新實體), 和DELETE 請求。我也會告訴你如何與 RxJava 集成以及如何取消請求。咱們將使用由JSONPlaceholder 提供的REST API, 這是一個用於測試和原型設計的在線模擬 API。

1. 建立一個Android 項目

啓動Android Studio, 而後建立一個帶有一個空的MainActivity 的項目。

圖片描述

2. 聲明依賴

建立了項目以後, 在build.gradle 裏面添加下面的依賴. 這些依賴包括Retrofit 庫, 以及用來轉換JSON 的Google 的Gson 庫, 另外還包含Retrofit 的Gson 集成庫。

// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
 
// JSON Parsing
compile 'com.google.code.gson:gson:2.6.1'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'

添加了依賴以後, 你還必須同步一下你的項目。

3. 添加網絡權限

要訪問網絡, 必需要在AndroidManifest.xml 中添加INTERNET 權限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.chikeandroid.retrofittutorial2">
 
    <uses-permission android:name="android.permission.INTERNET" />
     
    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
        <activity android:name=".PostActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
 
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
     
</manifest>

4. 自動生成Model

咱們將使用一個很是有用的工具: jsonschema2pojo, 來將響應的JSON 格式自動建立成相應的Model. 咱們會進行一個POST 請求(建立一個新的資源)。 可是在開始以前, 咱們須要知道咱們即將獲取到的JSON 響應是什麼樣的, 這樣Retrofit 纔可以解析JSON, 而後轉換成Java 對象。根據API 說明, 若是咱們用POST 發送下面的數據:

data: {
    title: 'foo',
    body: 'bar',
    userId: 1
}

咱們將獲得下面的響應:

{
  "title": "foo",
  "body": "bar",
  "userId": 1,
  "id": 101
}

將JSON 數據映射到Java

複製上面示例的響應數據。 訪問jsonschema2pojo 而後將JSON 響應粘貼到輸入框裏面。 選擇源類型爲JSON, Gson 的annotation, 去掉Allow additional properties 的複選框, 而後將類名從Example 改成Post。

圖片描述

而後點擊Preview 按鈕來生成Java 對象。

圖片描述

你可能會好奇這裏面的@SerializedName 和@Expose 是作什麼用的! 彆着急, 且聽我道來!

@SerializedName 是Gson 用於將JSON 的key 映射到Java 對象的字段的。

@SerializedName("userId")
@Expose
private Integer userId;

好比, 上面這一段, JSON 的key userId 將被映射到類字段userId. 想必你也注意到了, 它們是同樣的, 因此這裏其實能夠不用@SerializedName, Gson 會自動爲咱們作這樣的映射。

而另外一方面, @Expose 則是用來聲明類成員是否須要進行JSON 的序列化或反序列化。

導入數據Model 到Android Studio
如今讓咱們回到Android Studio。在main 包下建立data 包. 在這新建的包下面, 再建立一個model 包. 而後在這個包下面建立Post 類. 而後將jsonschema2pojo 生成的代碼複製到你建立的Post 類裏面。

package com.chikeandroid.retrofittutorial2.data.model;
 
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
 
public class Post {
 
    @SerializedName("title")
    @Expose
    private String title;
    @SerializedName("body")
    @Expose
    private String body;
    @SerializedName("userId")
    @Expose
    private Integer userId;
    @SerializedName("id")
    @Expose
    private Integer id;
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
    
    public String getBody() {
        return body;
    }
 
    public void setBody(String body) {
        this.body = body;
    }
 
    public Integer getUserId() {
        return userId;
    }
 
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
     
    @Override
    public String toString() {
        return "Post{" +
                "title='" + title + '\'' +
                ", body='" + body + '\'' +
                ", userId=" + userId +
                ", id=" + id +
                '}';
    }
}

除了getter 和setter 以外, 我還包含了toString() 方法. (在Intellij, 你可使用Generate 命令來快速生成: 在Windows 的快捷鍵是Alt-Insert, 在macOS 是則是Command-N)。

5. 建立Retrofit 實例

要使用Retrofit 去請求RESTful 的API, 咱們須要先使用Retrofit Builder 來建立一個實例, 並使用一個根URL 來配置它。

data 包下面建立remote 包. 而後, 在新建的包下面, 建立一個RetrofitClient 的Java 類。這個類將經過getClient(String baseUrl) 方法建立一個Retrofit 的單例, 並返回給調用者。

正如我以前提到的, Retrofit 須要一個根URL 來建立實例, 因此我在調用RetrofitClient.getClient(String baseUrl) 的時候傳遞給它。這個URL 將被用於建立實例, 如12 行所示. 另外咱們在13 行還設置了JSON 的轉換器爲Gson。

package com.chikeandroid.retrofittutorial2.data.remote;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
 
public class RetrofitClient {
 
    private static Retrofit retrofit = null;
 
    public static Retrofit getClient(String baseUrl) {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

6. 建立API 接口

在remote 包下面, 建立一個APIService 的接口。這個接口包含了將要用到的發送POST, PUTDELETE 請求的方法。讓咱們從POST 請求開始。

package com.chikeandroid.retrofittutorial2.data.remote;
import com.chikeandroid.retrofittutorial2.data.model.Post;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
 
public interface APIService {
 
    @POST("/posts")
    @FormUrlEncoded
    Call<Post> savePost(@Field("title") String title,
                        @Field("body") String body,
                        @Field("userId") long userId);
}

看看APIService 類, 咱們定義了一個savePost() 方法。在這個方法上, 有個@POST 的annotation, 這是用來標識當這個方法執行的時候要發送POST 請求出去。@Post annotation 的參數值, 是請求的地址, 這裏是/posts。因此, 請求的全路徑將會是http://jsonplaceholder.typico...

OK, 那麼@FormUrlEncoded 是用來作什麼的呢? 它是用來標識這個請求的MIME 類型(一個用來標識HTTP 請求或響應的內容格式的HTTP 頭) 須要設置成application/x-www-form-urlencoded, 而且請求的字段和字段值須要在進行URL 編碼以前先進行UTF-8 編碼處理. @Field("key") 裏面的參數值須要和API 指望的參數名相匹配. Retrofit 使用String.valueOf(Object) 將值轉換成字符串, 而後對這些字符串進行URL 編碼處理. null 值則忽略。

例如, 調用APIService.savePost("My Visit To Lagos", "I visited...", 2) 會生成title=My+Visit+To+Lagos&body=I+visited...&userId=2 這樣的請求內容。

使用@Body Annotation
咱們也能夠在請求方法的參數使用@Body Annotation, 而不是像上面那樣每一個字段都單獨指定。這個對象將使用在建立Retrofit 實例時指定的Converter 進行序列化。不過這個只能用於在進行POST 或者PUT 請求的時候。

@POST("/posts")
@FormUrlEncoded
Call<Post> savePost(@Body Post post);

7. 建立API 工具類

咱們還須要建立一個工具類。在data.remote 包下面建立一個ApiUtils 的類. 這個類定義了根URL 的路徑, 而且定義靜態方法getAPIService(), 以方便應用中其餘類對APIService 的調用。

package com.chikeandroid.retrofittutorial2.data.remote;
 
public class ApiUtils {
 
    private ApiUtils() {}
 
    public static final String BASE_URL = "http://jsonplaceholder.typicode.com/";
 
    public static APIService getAPIService() {
 
        return RetrofitClient.getClient(BASE_URL).create(APIService.class);
    }
}

須要注意的是, 根URL 須要以/ 結尾。

8. 建立佈局

MainActivity 對應的佈局文件是activity_main.xml。 這個佈局, 包含了兩個文本框, 一個用於輸入標題, 另外一個則是用於輸入內容. 另外還有個用於提交數據的按鈕。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_post"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context="com.chikeandroid.retrofittutorial2.AddEditPostActivity">
 
    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:textAppearance="@style/TextAppearance.AppCompat.Title"
            android:text="@string/title_enter_post"/>
    <EditText
            android:id="@+id/et_title"
            android:layout_marginTop="18dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_title"/>
 
    <EditText
            android:id="@+id/et_body"
            android:lines="4"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_body"/>
 
    <Button
            android:id="@+id/btn_submit"
            android:layout_marginTop="18dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorAccent"
            android:textColor="@android:color/white"
            android:text="@string/action_submit"/>
 
    <TextView
            android:id="@+id/tv_response"
            android:layout_marginTop="35dp"
            android:visibility="gone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
     
</LinearLayout>

9. 提交POST 請求

MainActivityonCreate() 方法裏面, 咱們進行APIService 接口的初始化(第14 行)。咱們也獲取了文本框EditText, 而且對提交按鈕綁定點擊事件, 使得當它被點擊時, 調用sendPost() 方法。

private TextView mResponseTv;
private APIService mAPIService;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    final EditText titleEt = (EditText) findViewById(R.id.et_title);
    final EditText bodyEt = (EditText) findViewById(R.id.et_body);
    Button submitBtn = (Button) findViewById(R.id.btn_submit);
    mResponseTv = (TextView) findViewById(R.id.tv_response);
 
    mAPIService = ApiUtils.getAPIService();
 
    submitBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String title = titleEt.getText().toString().trim();
            String body = bodyEt.getText().toString().trim();
            if(!TextUtils.isEmpty(title) && !TextUtils.isEmpty(body)) {
                sendPost(title, body);
            }
        }
    });
}

MainActivitysendPost(String, String) 方法裏面, 咱們接收標題和內容參數. 而後在這個方法裏面, 調用API service 接口的savePost(String, String) 方法, 將接收到的標題和內容用POST 請求的方式發送給API。而showResponse(String response) 方法則會將響應數據顯示在屏幕上。

public void sendPost(String title, String body) {
mAPIService.savePost(title, body, 1).enqueue(new Callback<Post>() {
    @Override
    public void onResponse(Call<Post> call, Response<Post> response) {
 
        if(response.isSuccessful()) {
            showResponse(response.body().toString());
            Log.i(TAG, "post submitted to API." + response.body().toString());
        }
    }
 
    @Override
    public void onFailure(Call<Post> call, Throwable t) {
        Log.e(TAG, "Unable to submit post to API.");
    }
});
}
 
public void showResponse(String response) {
    if(mResponseTv.getVisibility() == View.GONE) {
        mResponseTv.setVisibility(View.VISIBLE);
    }
    mResponseTv.setText(response);
}

對象mAPIService (APIService 接口的實例) 的savePost(String, String) 方法會返回一個Call 對象, 這個對象有個enqueue(Callback callback) 方法。

理解enqueue()
enqueue() 異步地發送請求, 而後當響應回來的時候, 使用回調的方式通知你的APP。由於這個請求是異步的, Retrofit 使用一個另外的線程去執行它, 這樣UI 線程就不會被阻塞了。

要使用enqueue() 方法, 你須要實現兩個回調方法: onResponse()onFailure()。對於一個請求, 當響應回來的時候, 只有一個方法會被執行。

  • onResponse(): 在收到HTTP 響應的時候被調用。這個方法在服務器能夠處理請求的狀況下調用, 即便服務器返回的是一個錯誤的信息。例如你獲取到的響應狀態是404 或500。你可使用response.code() 來獲取狀態碼, 以便進行不一樣的處理. 固然你也能夠直接用isSuccessful() 方法來判斷響應的狀態碼是否是在200-300 之間(在這個範圍內標識是成功的)。

  • onFailure(): 當和服務器通訊出現網絡異常時, 或者在處理請求出現不可預測的錯誤時, 會調用這個方法。

同步請求
要執行同步的請求, 你能夠直接使用Call 對象的execute() 方法。可是要注意, 若是在UI 線程執行同步的請求, 將會阻塞用戶的操做。因此, 不要在Android 的UI 線程去執行同步的方法。而是應該將它們放在後臺線程去運行.

使用RxJava
在Retrofit 1 中, RxJava 是默認集成進去了的, 可是在Retrofit 2 中, 咱們還須要添加一些額外的依賴。Retrofit 在執行Call 對象的方法時, 帶有一個默認的Adapter. 因此你可以經過引入RxJava (帶有RxJava 的CallAdapter), 改變Retrofit 的運行機制。按照下面的步驟:

步驟1
添加依賴。

compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

步驟2
在建立Retrofit 實例的時候添加新的CallAdapter RxJavaCallAdapterFactory.create() (第5 行)。

public static Retrofit getClient(String baseUrl) {
    if (retrofit==null) {
        retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    return retrofit;
}

步驟3
更新APIServicesavePost(String title, String body, String userId) 方法, 將返回值改成Observable。

@POST("/posts")
@FormUrlEncoded
Observable<Post> savePost(@Field("title") String title,
                          @Field("body") String body,
                          @Field("userId") long userId);

步驟4
當進行請求時, 添加一個匿名的subscriber 去監聽observable 的流. 當subscriber 接收到事件時, 將調用onNext 方法, 而後在這個方法裏面調用showResponse(String response) 方法。

public void sendPost(String title, String body) {
 
    // RxJava
    mAPIService.savePost(title, body, 1).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<Post>() {
                @Override
                public void onCompleted() {
 
                }
 
                @Override
                public void onError(Throwable e) {
 
                }
 
                @Override
                public void onNext(Post post) {
                    showResponse(post.toString());
                }
            });
}

10. 測試APP

到這裏, 你就能夠容許你的APP, 輸入標題和內容, 而後點擊提交按鈕。從API 返回的結果將顯示在提交按鈕下面。

圖片描述

11. 執行PUT 請求

如今你已經指定怎麼執行POST 請求了, 讓咱們看看該怎麼執行PUT 請求來更新實體。 在APIService 類中添加下面的新方法。

@PUT("/posts/{id}")
@FormUrlEncoded
Call<Post> updatePost(@Path("id") long id,
                      @Field("title") String title,
                      @Field("body") String body,
                      @Field("userId") long userId);

要更新一個post, 咱們使用/posts/{id} 這樣的請求地址, 在這裏{id} 是用來填充咱們要更新的post 的id 的佔位符。而參數中的@Path 註解是用來填充URL 路徑中的{id} 片斷的值的。要注意的是這些值都將使用[String.valueOf(Object)][15] 轉換成字符串, 而後進行URL 編碼。若是這個值已是編碼過了的, 你也能夠用這樣的方式禁止再進行URL 編碼: @Path(value="name", encoded=true)

12. 執行DELETE 請求

讓咱們再來看看怎麼執行DELETE 請求。要使用JSONPlaceholder API 去刪除一個post 資源, 要請求的地址和更新是同樣的/posts/{id}, 所不一樣的只是使用的HTTP 方法是DELETE。回到APIService 接口, 只須要添加下面的deletePost() 方法。咱們傳入方法的參數是要刪除的post id, 這個id 同樣是用來替換URL 中的{id} 片斷。

@DELETE("/posts/{id}")
Call<Post> deletePost(@Path("id") long id);

13. 取消請求

若是你想讓你的用戶可以取消或放棄一個請求。在Retrofit 能夠很容易作到這一點。Retrofit 的Call 類有個cancel() 方法就是用來作這個的(下面的第30 行)。這個方法將觸發onFailure() 回調方法。

若是在沒有網絡鏈接, 或者在建立請求或處理響應時出現意外異常, 能夠調用這個方法來取消請求。若是你想要知道你的請求是否被取消了, 可使用Call 類裏面的isCanceled() 方法(第18 行)。

private Call<Post> mCall;
 
...
 
public sendPost(String title, String body) {
    mCall = mAPIService.savePost(title, body, 1);
    mCall.enqueue(new Callback<Post>() {
        @Override
        public void onResponse(Call<Post> call, Response<Post> response) {
 
            if(response.isSuccessful()) {
                showResponse(response.body().toString());
                Log.i(TAG, "post submitted to API." + response.body().toString());
            }
        }
 
        @Override
        public void onFailure(Call<Post> call, Throwable t) {
 
            if(call.isCanceled()) {
                Log.e(TAG, "request was aborted");
            }else {
                Log.e(TAG, "Unable to submit post to API.");
            }
            showErrorMessage();
 
        }
    });
}
 
public void cancelRequest() {
    mCall.cancel();
}

總結

在這篇教程, 你學習了Retrofit 一些內容: 爲啥要用它, 怎麼在項目中集成, 怎麼進行POST, PUT, DELETE 請求, 怎麼取消請求. 你還了解到該怎麼讓Retrofit 和RxJava 進行集成. 在下一篇文章中, 我將講講該怎麼上傳文件。

想要了解更多Retrofit 的內容, 請務必去看看官方的文檔。而後看看Envato Tuts+ 裏面關於Android 開發的其餘教程和課程。

關於Envato藝雲臺

圖片描述

Envato藝雲臺是數據資產和創造性人才匯聚的全球領先市場平臺。全球數百萬人都選擇經過咱們的市場平臺、工做室和課程來購買文件、選聘自由職業者,或者學習建立網站、製做視頻、應用、製圖等所需的技能。咱們的子網站包括Envato藝雲臺Tuts+ 網絡,全球最大的H五、PS、插圖、代碼和攝影教程資源庫,以及Envato藝雲臺市場,其中的900多萬類數字資產均經過如下七大平臺進行銷售 - CodeCanyon、ThemeForest、GraphicRiver、VideoHive、PhotoDune、AudioJungle和3DOcean。

相關文章
相關標籤/搜索