在 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。

相关文章
相关标签/搜索