網絡請求框架 Retrofit 2 使用入門

RobertaBras 7年前發布 | 22K 次閱讀 Retrofit Android開發 移動開發

你將要創造什么

Retrofit 是什么?

Retrofit 是一個用于 Android 和 Java 平臺的類型安全的網絡請求框架。Retrofit 通過將 API 抽象成 Java 接口而讓我們連接到 REST web 服務變得很輕松。在這個教程里,我會向你介紹如何使用這個 Android 上最受歡迎和經常推薦的網絡請求庫之一。

這個強大的庫可以很簡單的把返回的 JSON 或者 XML 數據解析成簡單 Java 對象(POJO)。 GET , POST , PUT , PATCH , 和 DELETE 這些請求都可以執行。

和大多數開源軟件一樣,Retrofit 也是建立在一些強大的庫和工具基礎上的。Retrofit 背后用了同一個開發團隊的 OkHttp 來處理網絡請求。而且 Retrofit 不再內置 JSON 轉換器來將 JSON 裝換為 Java 對象。取而代之的是提供以下 JSON 轉換器來處理:

  • Gson: com.squareup.retrofit:converter-gson
  • Jackson: com.squareup.retrofit:converter-jackson
  • Moshi: com.squareup.retrofit:converter-moshi

對于 Protocol Buffers , Retrofit 提供了:

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

  • Wire: com.squareup.retrofit2:converter-wire

對于 XML 解析, Retrofit 提供了:

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

那么我們為什么要用 Retrofit 呢?

開發一個自己的用于請求 REST API 的類型安全的網絡請求庫是一件很痛苦的事情:你需要處理很多功能,比如建立連接,處理緩存,重連接失敗請求,線程,響應數據的解析,錯誤處理等等。從另一方面來說,Retrofit 是一個有優秀的計劃,文檔和測試并且經過考驗的庫,它會幫你節省你的寶貴時間以及不讓你那么頭痛。

在這個教程里,我會構建一個簡單的應用,根據 Stack Exchange API 查詢上面最近的回答,從而來教你如何使用 Retrofit 2 來處理網絡請求。我們會指明 /answers 這樣一個路徑,然后拼接到 base URL https://api.stackexchange.com/2.2 / 上執行一個 GET 請求——然后我們會得到響應結果并且顯示到 RecyclerView 上。我還會向你展示如何利用 RxJava 來輕松地管理狀態和數據流。

1.創建一個 Android Studio 工程

打開 Android Studio,創建一個新工程,然后創建一個命名為 MainActivity 的空白 Activity。

2. 添加依賴

創建一個新的工程后,在你的 build.gradle 文件里面添加以下依賴。這些依賴包括 RecyclerView,Retrofit 庫,還有 Google 出品的將 JSON 裝換為 POJO(簡單 Java 對象)的 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'

// recyclerview
compile 'com.android.support:recyclerview-v7:25.0.1'

不要忘記同步(sync)工程來下載這些庫。

3. 添加網絡權限

要執行網絡操作,我們需要在應用的清單文件 AndroidManifest.xml 里面聲明網絡權限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.chikeandroid.retrofittutorial">

    <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=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

4.自動生成 Java 對象

我們利用一個非常有用的工具來幫我們將返回的 JSON 數據自動生成 Java 對象: jsonschema2pojo 。

取得示例的 JSON 數據

復制粘貼 https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow 到你的瀏覽器地址欄,或者如果你熟悉的話,你可以使用 Postman 這個工具。然后點擊 Enter —— 它將會根據那個地址執行一個 GET 請求,你會看到返回的是一個 JSON 對象數組,下面的截圖是使用了 Postman 的 JSON 響應結果。

{
      "items": [
        {
          "owner": {
            "reputation": 1,
            "user_id": 6540831,
            "user_type": "registered",
            "profile_image": "https://www.gravatar.com/avatar/6a468ce8a8ff42c17923a6009ab77723?s=128&d=identicon&r=PG&f=1",
            "display_name": "bobolafrite",
            "link": "http://stackoverflow.com/users/6540831/bobolafrite"
          },
          "is_accepted": false,
          "score": 0,
          "last_activity_date": 1480862271,
          "creation_date": 1480862271,
          "answer_id": 40959732,
          "question_id": 35931342
        },
        {
          "owner": {
            "reputation": 629,
            "user_id": 3054722,
            "user_type": "registered",
            "profile_image": "https://www.gravatar.com/avatar/0cf65651ae9a3ba2858ef0d0a7dbf900?s=128&d=identicon&r=PG&f=1",
            "display_name": "jeremy-denis",
            "link": "http://stackoverflow.com/users/3054722/jeremy-denis"
          },
          "is_accepted": false,
          "score": 0,
          "last_activity_date": 1480862260,
          "creation_date": 1480862260,
          "answer_id": 40959731,
          "question_id": 40959661
        },
        ...
      ],
      "has_more": true,
      "backoff": 10,
      "quota_max": 300,
      "quota_remaining": 241
    }

從你的瀏覽器或者 Postman 復制 JSON 響應結果。

將 JSON 數據映射到 Java 對象

現在訪問 jsonschema2pojo ,然后粘貼 JSON 響應結果到輸入框。

選擇 Source Type 為 JSON ,Annotation Style 為 Gson ,然后取消勾選 Allow additional properties

然后點擊 Preview 按鈕來生成 Java 對象。

你可能想知道在生成的代碼里面, @SerializedName 和 @Expose 是干什么的。別著急,我會一一解釋的。

Gson 使用 @SerializedName 注解來將 JSON 的 key 映射到我們類的變量。為了與 Java 對類成員屬性的駝峰命名方法保持一致,不建議在變量中使用下劃線將單詞分開。 @SerializeName 就是兩者的翻譯官。

@SerializedName("quota_remaining")
@Expose
private Integer quotaRemaining;

在上面的示例中,我們告訴 Gson 我們的 JSON 的 key quota_remaining 應該被映射到 Java 變量 quotaRemaining 上。如果兩個值是一樣的,即如果我們的 JSON 的 key 和 Java 變量一樣是 quotaRemaining ,那么就沒有必要為變量設置 @SerializedName 注解,Gson 會自己搞定。

@Expose 注解表明在 JSON 序列化或反序列化的時候,該成員應該暴露給 Gson。

將數據模型導入 Android Studio

現在讓我們回到 Android Studio。新建一個 data 的子包,在 data 里面再新建一個 model 的包。在 model 包里面,新建一個 Owner 的 Java 類。

然后將 jsonschema2pojo 生成的 Owner 類復制粘貼到剛才新建的 Owner 類文件里面。

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Owner {

    @SerializedName("reputation")
    @Expose
    private Integer reputation;
    @SerializedName("user_id")
    @Expose
    private Integer userId;
    @SerializedName("user_type")
    @Expose
    private String userType;
    @SerializedName("profile_image")
    @Expose
    private String profileImage;
    @SerializedName("display_name")
    @Expose
    private String displayName;
    @SerializedName("link")
    @Expose
    private String link;
    @SerializedName("accept_rate")
    @Expose
    private Integer acceptRate;


    public Integer getReputation() {
        return reputation;
    }

    public void setReputation(Integer reputation) {
        this.reputation = reputation;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserType() {
        return userType;
    }

    public void setUserType(String userType) {
        this.userType = userType;
    }

    public String getProfileImage() {
        return profileImage;
    }

    public void setProfileImage(String profileImage) {
        this.profileImage = profileImage;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public Integer getAcceptRate() {
        return acceptRate;
    }

    public void setAcceptRate(Integer acceptRate) {
        this.acceptRate = acceptRate;
    }
}

利用同樣的方法從 jsonschema2pojo 復制過來,新建一個 Item 類。

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Item {

    @SerializedName("owner")
    @Expose
    private Owner owner;
    @SerializedName("is_accepted")
    @Expose
    private Boolean isAccepted;
    @SerializedName("score")
    @Expose
    private Integer score;
    @SerializedName("last_activity_date")
    @Expose
    private Integer lastActivityDate;
    @SerializedName("creation_date")
    @Expose
    private Integer creationDate;
    @SerializedName("answer_id")
    @Expose
    private Integer answerId;
    @SerializedName("question_id")
    @Expose
    private Integer questionId;
    @SerializedName("last_edit_date")
    @Expose
    private Integer lastEditDate;

    public Owner getOwner() {
        return owner;
    }

    public void setOwner(Owner owner) {
        this.owner = owner;
    }

    public Boolean getIsAccepted() {
        return isAccepted;
    }

    public void setIsAccepted(Boolean isAccepted) {
        this.isAccepted = isAccepted;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    public Integer getLastActivityDate() {
        return lastActivityDate;
    }

    public void setLastActivityDate(Integer lastActivityDate) {
        this.lastActivityDate = lastActivityDate;
    }

    public Integer getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Integer creationDate) {
        this.creationDate = creationDate;
    }

    public Integer getAnswerId() {
        return answerId;
    }

    public void setAnswerId(Integer answerId) {
        this.answerId = answerId;
    }

    public Integer getQuestionId() {
        return questionId;
    }

    public void setQuestionId(Integer questionId) {
        this.questionId = questionId;
    }

    public Integer getLastEditDate() {
        return lastEditDate;
    }

    public void setLastEditDate(Integer lastEditDate) {
        this.lastEditDate = lastEditDate;
    }
}

最后,為返回的 StackOverflow 回答新建一個 SOAnswersResponse 類。注意在 jsonschema2pojo 里面類名是 Example ,別忘記把類名改成 SOAnswersResponse 。

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class SOAnswersResponse {

    @SerializedName("items")
    @Expose
    private List<Item> items = null;
    @SerializedName("has_more")
    @Expose
    private Boolean hasMore;
    @SerializedName("backoff")
    @Expose
    private Integer backoff;
    @SerializedName("quota_max")
    @Expose
    private Integer quotaMax;
    @SerializedName("quota_remaining")
    @Expose
    private Integer quotaRemaining;

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public Boolean getHasMore() {
        return hasMore;
    }

    public void setHasMore(Boolean hasMore) {
        this.hasMore = hasMore;
    }

    public Integer getBackoff() {
        return backoff;
    }

    public void setBackoff(Integer backoff) {
        this.backoff = backoff;
    }

    public Integer getQuotaMax() {
        return quotaMax;
    }

    public void setQuotaMax(Integer quotaMax) {
        this.quotaMax = quotaMax;
    }

    public Integer getQuotaRemaining() {
        return quotaRemaining;
    }

    public void setQuotaRemaining(Integer quotaRemaining) {
        this.quotaRemaining = quotaRemaining;
    }
}

5. 創建 Retrofit 實例

為了使用 Retrofit 向 REST API 發送一個網絡請求,我們需要用 Retrofit.Builder 類來創建一個實例,并且配置一個 base URL。

在 data 包里面新建一個 remote 的包,然后在 remote 包里面新建一個 RetrofitClient 類。這個類會創建一個 Retrofit 的單例。Retrofit 需要一個 base URL 來創建實例。所以我們在調用 RetrofitClient.getClient(String baseUrl) 時會傳入一個 URL 參數。參見 13 行,這個 URL 用于構建 Retrofit 的實例。參見 14 行,我們也需要指明一個我們需要的 JSON converter(Gson)。

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 包里面,創建一個 SOService 接口,這個接口包含了我們將會用到用于執行網絡請求的方法,比如 GET , POST , PUT , PATCH , 以及 DELETE 。在該教程里面,我們將執行一個 GET 請求。

import com.chikeandroid.retrofittutorial.data.model.SOAnswersResponse;

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;

public interface SOService {

   @GET("/answers?order=desc&sort=activity&site=stackoverflow")
   Call<List<SOAnswersResponse>> getAnswers();

   @GET("/answers?order=desc&sort=activity&site=stackoverflow")
   Call<List<SOAnswersResponse>> getAnswers(@Query("tagged") String tags);
}

GET 注解明確的定義了當該方法調用的時候會執行一個 GET 請求。接口里每一個方法都必須有一個 HTTP 注解,用于提供請求方法和相對的 URL 。Retrofit 內置了 5 種注解: @GET , @POST , @PUT , @DELETE , 和 @HEAD 。

在第二個方法定義中,我們添加一個 query 參數用于從服務端過濾數據。Retrofit 提供了 @Query("key") 注解,這樣就不用在地址里面直接寫了。key 的值代表了 URL 里參數的名字。Retrofit 會把他們添加到 URL 里面。比如說,如果我們把 android 作為參數傳遞給 getAnswers(String tags) 方法,完整的 URL 將會是:

https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow&tagged=android

接口方法的參數有以下注解:

     
@Path 替換 API 地址中的變量
@Query 通過注解的名字指明 query 參數的名字
@Body POST 請求的請求體
@Header 通過注解的參數值指明 header

7.創建 API 工具類

現在我們要新建一個工具類。我們命名為 ApiUtils 。該類設置了一個 base URL 常量,并且通過靜態方法 getSOService() 為應用提供 SOService 接口。

public class ApiUtils {

    public static final String BASE_URL = "https://api.stackexchange.com/2.2/";

    public static SOService getSOService() {
        return RetrofitClient.getClient(BASE_URL).create(SOService.class);
    }
}

8.顯示到 RecyclerView

既然結果要顯示到 RecyclerView 上面,我們需要一個 adpter。以下是 AnswersAdapter 類的代碼片段。

public class AnswersAdapter extends RecyclerView.Adapter<AnswersAdapter.ViewHolder> {

    private List<Item> mItems;
    private Context mContext;
    private PostItemListener mItemListener;

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        public TextView titleTv;
        PostItemListener mItemListener;

        public ViewHolder(View itemView, PostItemListener postItemListener) {
            super(itemView);
            titleTv = (TextView) itemView.findViewById(android.R.id.text1);

            this.mItemListener = postItemListener;
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            Item item = getItem(getAdapterPosition());
            this.mItemListener.onPostClick(item.getAnswerId());

            notifyDataSetChanged();
        }
    }

    public AnswersAdapter(Context context, List<Item> posts, PostItemListener itemListener) {
        mItems = posts;
        mContext = context;
        mItemListener = itemListener;
    }

    @Override
    public AnswersAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        View postView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);

        ViewHolder viewHolder = new ViewHolder(postView, this.mItemListener);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(AnswersAdapter.ViewHolder holder, int position) {

        Item item = mItems.get(position);
        TextView textView = holder.titleTv;
        textView.setText(item.getOwner().getDisplayName());
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    public void updateAnswers(List<Item> items) {
        mItems = items;
        notifyDataSetChanged();
    }

    private Item getItem(int adapterPosition) {
        return mItems.get(adapterPosition);
    }

    public interface PostItemListener {
        void onPostClick(long id);
    }
}

9.執行請求

在 MainActivity 的 onCreate() 方法內部,我們初始化 SOService 的實例(參見第 9 行),RecyclerView 以及 adapter。最后我們調用 loadAnswers() 方法。

private AnswersAdapter mAdapter;
    private RecyclerView mRecyclerView;
    private SOService mService;

    @Override
    protected void onCreate (Bundle savedInstanceState)  {
        super.onCreate( savedInstanceState );
        setContentView(R.layout.activity_main );
        mService = ApiUtils.getSOService();
        mRecyclerView = (RecyclerView) findViewById(R.id.rv_answers);
        mAdapter = new AnswersAdapter(this, new ArrayList<Item>(0), new AnswersAdapter.PostItemListener() {

            @Override
            public void onPostClick(long id) {
                Toast.makeText(MainActivity.this, "Post id is" + id, Toast.LENGTH_SHORT).show();
            }
        });

        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setHasFixedSize(true);
        RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
        mRecyclerView.addItemDecoration(itemDecoration);

        loadAnswers();
    }

loadAnswers() 方法通過調用 enqueue() 方法來進行網絡請求。當響應結果返回的時候,Retrofit 會幫我們把 JSON 數據解析成一個包含 Java 對象的 list(這是通過 GsonConverter 實現的)。

public void loadAnswers() {
    mService.getAnswers().enqueue(new Callback<SOAnswersResponse>() {
    @Override
    public void onResponse(Call<SOAnswersResponse> call, Response<SOAnswersResponse> response) {

        if(response.isSuccessful()) {
            mAdapter.updateAnswers(response.body().getItems());
            Log.d("MainActivity", "posts loaded from API");
        }else {
            int statusCode  = response.code();
            // handle request errors depending on status code
        }
    }

    @Override
    public void onFailure(Call<SOAnswersResponse> call, Throwable t) {
       showErrorMessage();
        Log.d("MainActivity", "error loading from API");

    }
});
}

10. 理解 enqueue()

enqueue() 會發送一個異步請求,當響應結果返回的時候通過回調通知應用。因為是異步請求,所以 Retrofit 將在后臺線程處理,這樣就不會讓 UI 主線程堵塞或者受到影響。

要使用 enqueue() ,你必須實現這兩個回調方法:

  • onResponse()
  • onFailure()

只有在請求有響應結果的時候才會調用其中一個方法。

  • onResponse() :接收到 HTTP 響應時調用。該方法會在響應結果能夠被正確地處理的時候調用,即使服務器返回了一個錯誤信息。所以如果你收到了一個 404 或者 500 的狀態碼,這個方法還是會調用。為了拿到狀態碼以便后續的處理,你可以使用 response.code() 方法。你也可以使用 isSuccessful() 來確定返回的狀態碼是否在 200-300 范圍內,該范圍的狀態碼也表示響應成功。
  • onFailure() :在與服務器通信的時候發生網絡異常或者在處理請求或響應的時候發生異常的時候調用。

要執行同步請求,你可以使用 execute() 方法。要注意同步請求在主線程會阻塞用戶的任何操作。所以不要在主線程執行同步請求,要在后臺線程執行。

11.測試應用

現在你可以運行應用了。

12. 結合 RxJava

如果你是 RxJava 的粉絲,你可以通過 RxJava 很簡單的實現 Retrofit。RxJava 在 Retrofit 1 中是默認整合的,但是在 Retrofit 2 中需要額外添加依賴。Retrofit 附帶了一個默認的 adapter 用于執行 Call 實例,所以你可以通過 RxJava 的 CallAdapter 來改變 Retrofit 的執行流程。

第一步

添加依賴。

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

第二步

在創建新的 Retrofit 實例的時候添加一個新的 CallAdapter RxJavaCallAdapterFactory.create() 。

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

第三步

當我們執行請求時,我們的匿名 subscriber 會響應 observable 發射的事件流,在本例中,就是 SOAnswersResponse 。當 subscriber 收到任何發射事件的時候,就會調用 onNext() 方法,然后傳遞到我們的 adapter。

@Override
public void loadAnswers() {
    mService.getAnswers().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<SOAnswersResponse>() {
                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onNext(SOAnswersResponse soAnswersResponse) {
                    mAdapter.updateAnswers(soAnswersResponse.getItems());
                }
            });
}

查看 Ashraff Hathibelagal 的 Getting Started With ReactiveX on Android 以了解更多關于 RxJava 和 RxAndroid 的內容。

總結

在該教程里,你已經了解了使用 Retrofit 的理由以及方法。我也解釋了如何將 RxJava 結合 Retrofit 使用。在我的下一篇文章中,我將為你展示如何執行 POST , PUT , 和 DELETE 請求,如何發送 Form-Urlencoded 數據,以及如何取消請求。

 

 

來自:http://www.jianshu.com/p/d3fdf84ead4b

 

 本文由用戶 RobertaBras 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!