Retrofit 2 HTTP客户端入门

最终产品图片
您将要创造的

什么是翻新?

Retrofit是适用于Android和Java的类型安全的HTTP客户端。 通过将API转换为Java接口,翻新可以轻松连接到REST Web服务。 在本教程中,我将向您展示如何使用可用于Android的最受欢迎和最经常推荐的HTTP库之一。

这个功能强大的库使您可以轻松使用JSON或XML数据,然后将其解析为纯旧Java对象(PO​​JO)。 GETPOSTPUTPATCHDELETE请求都可以执行。

像大多数开源软件一样,Retrofit建立在其他一些强大的库和工具之上。 在幕后,Retrofit利用OkHttp (来自同一开发人员)来处理网络请求。 此外,Retrofit没有内置的任何JSON转换器可将JSON解析为Java对象。 相反,它附带了对以下JSON转换器库的支持以处理该问题:

  • Gson: com.squareup.retrofit:converter-gson
  • 杰克逊: com.squareup.retrofit:converter-jackson
  • Moshi: com.squareup.retrofit:converter-moshi

对于协议缓冲区 ,翻新支持:

  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • 连线: com.squareup.retrofit2:converter-wire

对于XML,翻新支持:

  • 简单框架: com.squareup.retrofit2:converter-simpleframework

那么为什么要使用改造?

开发自己的类型安全的HTTP库以与REST API接口可能是一个真正的痛苦:您必须处理许多功能,例如进行连接,缓存,重试失败的请求,线程,响应解析,错误处理等等。 另一方面,翻新计划,计划文件和测试工作都经过精心设计,这是一个经过实践检验的库,可为您节省很多宝贵的时间和麻烦。

在本教程中,我将通过构建一个简单的应用程序以查询来自Stack Exchange API的最新答案,来说明如何使用Retrofit 2处理网络请求。 我们将通过指定附加到基本URL https://api.stackexchange.com/2.2 /的端点/answers来执行GET请求,然后获取结果并将其显示在回收者视图中。 我还将向您展示如何使用RxJava轻松实现状态和数据流的管理。

1.创建一个Android Studio项目

启动Android Studio并创建一个名为MainActivity的空活动的新项目。

创建一个新的空活动

2.声明依赖关系

创建新项目后,在build.gradle声明以下依赖build.gradle 依赖项包括一个回收站视图,Retrofit库,以及Google的Gson库(用于将JSON转换为POJO(普通的旧Java对象))以及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'

不要忘记同步项目以下载这些库。

3.添加Internet权限

要执行网络操作,我们需要包括INTERNET   应用程序清单中的权限: 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.自动生成模型

我们将利用一个非常有用的工具jsonschema2pojo从JSON响应数据自动创建模型。

获取样本JSON数据

https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow复制并粘贴到浏览器的地址栏中(如果您熟悉该工具,也可以使用Postman )。 然后按Enter键 -这将在给定的端点上执行GET请求。 作为响应,您将看到一个JSON对象数组。 下面的屏幕截图是使用Postman的JSON响应。

API对GET请求的响应
{
  "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响应粘贴到输入框中。

选择JSON的源类型, Gson的注释样式,然后取消选中“ 允许其他属性”

jsonschema2pojo接口

然后单击“ 预览”按钮以生成Java对象。

jsonschema2pojo输出

您可能想知道@SerializedName@Expose批注在此生成的代码中的作用。 不用担心,我会解释一切!

Gson需要@SerializedName批注,才能将JSON键映射到我们的字段。 为了与Java关于类成员属性的camelCase命名约定保持一致,建议不要使用下划线来分隔变量中的单词。 @SerializedName有助于在两者之间进行翻译。

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

在上面的示例中,我们告诉Gson,我们的JSON密钥quota_remaining应该映射到Java字段quotaRemaining 。 如果这两个值都相同,即我们的JSON键就像Java字段一样是quotaRemaining ,那么该字段就不需要@SerializedName注释,因为Gson会自动将它们映射。

@Expose批注指示应公开此成员以进行JSON序列化或反序列化。

将数据模型导入Android Studio

现在让我们回到Android Studio。 在主包内创建一个新的子包,并将其命名为data 。 在新创建的数据包中,创建另一个包并将其命名为model 。 在模型包中,创建一个新的Java类,并将其命名为Owner 。 现在,复制由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向REST API发出网络请求,我们需要使用Retrofit.Builder类创建一个实例,并使用基本URL对其进行配置。

data包内创建一个新的子包包,并将其命名为remote 。 现在,在remote内部,创建一个Java类并将其命名为RetrofitClient 。 此类将创建单例的Retrofit。 Retrofit需要一个基本URL来构建其实例,因此在调用RetrofitClient.getClient(String baseUrl)时,我们将传递一个URL。 然后,该URL将在第13行中用于构建实例。我们还在第14行中指定所需的JSON转换器(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接口

在远程包中,创建一个接口,并将其SOService 。 该接口包含我们将用于执行HTTP请求的方法,例如GETPOSTPUTPATCHDELETE 。 对于本教程,我们将执行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<SOAnswersResponse> getAnswers(); 
    
   @GET("/answers?order=desc&sort=activity&site=stackoverflow")
   Call<SOAnswersResponse> getAnswers(@Query("tagged") String tags);
}

@GET注释显式定义了GET请求,该请求将在调用方法后执行。 此接口中的每个方法都必须具有HTTP注释,以提供请求方法和相对URL。 有五个内置可获得的注释: @GET@POST@PUT@DELETE ,和@HEAD

在第二个方法定义中,我们添加了一个查询参数供我们过滤来自服务器的数据。 Retrofit具有@Query("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 用带注释的参数的值指定查询键名称
@Body POST呼叫的有效负载
@Header 用带注释的参数的值指定标题

7.创建API实用程序

现在将要创建一个实用程序类。 我们将其命名为ApiUtils 。 此类将基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

由于结果将显示在回收站视图中 ,因此我们需要一个适配器。 以下代码段显示了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.执行请求

MainActivityonCreate()方法中,我们初始化SOService接口的实例(第9行),回收器视图以及适配器。 最后,我们调用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对象列表。 (这可以通过使用GsonConverterGsonConverter 。)

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()方法。 请注意,主/ UI线程上的同步方法将阻止任何用户操作。 因此,请勿在Android的main / UI线程上执行同步方法! 而是在后台线程上运行它们。

11.测试应用

您现在可以运行该应用程序。

来自StackOverflow的样本结果

12. RxJava集成

如果您是RxJava的粉丝,则可以轻松地使用RxJava实施Retrofit。 在Retrofit 1中,它是默认集成的,但是在Retrofit 2中,您需要包括一些额外的依赖项。 Retrofit附带有用于执行Call实例的默认适配器。 因此,您可以通过包含RxJava CallAdapter来将Retrofit的执行机制更改为包含CallAdapter

第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步

构建改造实例时,添加新的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;
}

第三步

现在,更新getAnswers()方法以返回Observable

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

第4步

发出请求时,我们的匿名订阅者响应可观察对象的流,该流发出事件,在我们的案例中为SOAnswersResponse 。 当我们的订户收到任何发出的事件,然后将其传递给我们的适配器时,将调用onNext方法。

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

结论

在本教程中,您了解了Retrofit:为什么要使用它以及如何使用。 我还解释了如何在Retrofit中添加RxJava集成。 在我的下一篇文章中,我将向您展示如何执行POSTPUTDELETE ,如何发送Form-Urlencoded数据以及如何取消请求。

翻译自: https://code.tutsplus.com/tutorials/getting-started-with-retrofit-2--cms-27792

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值