Skip to content

Latest commit

 

History

History
386 lines (288 loc) · 14 KB

Retrofit详解(上).md

File metadata and controls

386 lines (288 loc) · 14 KB

Retrofit详解(上)

之前写过一篇文章volley-retrofit-okhttp之我们该如何选择网路框架来分析VolleyRetrofit之间的区别。之前一直用Volley比较多。但是随着Rx系列的走红,目前越来越多的项目使用RxJava+Retrofit这一黄金组合。而且Retrofit使用注解的方式比较方便以及2.x版本的提示让Retrofit更加完善,今天简单的来学习记录下。

简介

Retrofit

A type-safe HTTP client for Android and Java

type-safe是什么鬼?
类型安全代码指访问被授权可以访问的内存位置。例如,类型安全代码不能从其他对象的私有字段读取值。它只从定义完善的允许方式访问类型才能读取。

使用

Gradle中集成:

compile 'com.squareup.retrofit2:retrofit:2.1.0'

Retrofit 2底层默认使用自家兄弟OKHttp作为网络层,并且在它上面进行构建。所以不需要在想1.x版本那样在 项目中显式的定义OkHttp依赖。所以RetrofitOkHttp的关系是后者专注与网络请求的高效优化,而前者专注于接口的封装和调用管理等。

image

当然你还需要在清单文件中添加网络请求的权限:

<uses-permission android:name="android.permission.INTERNET"/>

我们就以Github获取个人仓库的api来进行举例测试:

https://api.github.com/users/{user}/repos
  • Retrofit会将你的api封装成Java接口

    public interface GitHubService {
      @GET("users/{user}/repos")
      Call<List<Repo>> listRepos(@Path("user") String user);
    }
  • Retrofit类会生成一个GitHubService接口的实现类:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();
    
    GitHubService service = retrofit.create(GitHubService.class);
  • 从创建的GithubService类返回的每个Call对象调用后都可以创建一个同步或异步的网络请求:

    Call<List<Repo>> repos = service.listRepos("CharonChui");
  • 上面返回的Call其实并不是真正的数据结果,它更像一条指令,你需要执行它:

    // 同步调用
    List<Repo> data = repos.execute(); 
     
    // 异步调用
    repos.enqueue(new Callback<List<Repo>>() {
      @Override
      public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
          List<Repo> data = response.body();
          Log.i("@@@", "data size : " + (data == null ? "null" : data.size() + ""));
      }
    
      @Override
      public void onFailure(Call<List<Repo>> call, Throwable t) {
    
      }
    });

    那如何取消请求呢?

    repos.cancel();

上面这一部分代码,你要是拷贝运行后是运行不了的。 当然了,因为木有Repo对象。但是添加Repo对象也是运行不了的。会报错。

Process: com.charon.retrofitdemo, PID: 7229
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.charon.retrofitdemo/com.charon.retrofitdemo.MainActivity}: java.lang.IllegalArgumentException: Unable to create converter for java.util.List<com.charon.retrofitdemo.Repo>
   for method GitHubService.listRepos
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2281)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2331)
   at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3974)
   at android.app.ActivityThread.access$1100(ActivityThread.java:143)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1250)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:136)
   at android.app.ActivityThread.main(ActivityThread.java:5291)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:515)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
   at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IllegalArgumentException: Unable to create converter for java.util.List<com.charon.retrofitdemo.Repo>
   for method GitHubService.listRepos
   at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)
   at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:706)
   at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:167)
   at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
   at retrofit2.Retrofit$1.invoke(Retrofit.java:145)
   at $Proxy0.listRepos(Native Method)
   at com.charon.retrofitdemo.MainActivity$override.onCreate(MainActivity.java:29)
   at com.charon.retrofitdemo.MainActivity$override.access$dispatch(MainActivity.java)
   at com.charon.retrofitdemo.MainActivity.onCreate(MainActivity.java:0)
   at android.app.Activity.performCreate(Activity.java:5304)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1090)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2245)
    ... 12 more
Caused by: java.lang.IllegalArgumentException: Could not locate ResponseBody converter for java.util.List<com.charon.retrofitdemo.Repo>.
 Tried:

这是什么鬼?难道官方给的示例代码有问题? 从log上面我们能看出来是返回的数据结构不匹配导致的,返回的是ResponseBody的转换器无法转换为List<Repo>

Retrofit是一个将API接口转换成回调对象的类,默认情况下Retrofit会根据平台提供一些默认的配置,但是它是支持配置的。

Converters(解析数据)

默认情况下,Retrofit只能将HTTP响应体反序列化为OkHttpResponseBody类型。并且通过@Body也只能接受RequestBody类型。

可以通过添加转换器来支持其他类型。它提供了6中类似的序列化类库来方便进行使用:

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

我们这里通过Gson为例,讲解一下如何使用,首先需要在gradle文件中配置对应的支持模块。

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'

下面是一个通过使用GsonConverterFactory类来指定GitHubService接口使用Gson来解析结果的配置。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

经过这些改造,就可以了,贴一下完整的代码:

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        GitHubService service = retrofit.create(GitHubService.class);

        Call<List<Repo>> call= service.listRepos("CharonChui");

        call.enqueue(new Callback<List<Repo>>() {
            @Override
            public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                List<Repo> body = response.body();
                Log.i("@@@", "call " + body.size());
            }

            @Override
            public void onFailure(Call<List<Repo>> call, Throwable t) {

            }
        });
    }
}

运行上面的代码,log会打印出12-14 14:43:38.900 21509-21509/com.charon.retrofitdemo I/@@@: call 26

到这里,我们入门的Hello World就完成了。

Retrofit支持的协议包括GET/POST/PUT/DELETE/HEAD/PATCH,当然你也可以直接用HTTP 来自定义请求。这些协议均以注解的形式进行配置,比如我们已经见过GET的用法.

我们发现在Retrofit创建的时候需要传入一个baseUrl(https://api.github.com/),在GitHubService中会通过注解设置@GET("users/{user}/repos"),请求的完整Url就是通过baseUrl与注解的value也就是path路径结合起来组成。虽然提供了多种规则,但是统一使用下面这一种是最好的。

通过注解的value指定的是相对路径,baseUrl是目录形式:
path = "users/CharonChui/repos"baseUrl = "https://api.github.com/"
Url = "https://api.github.com/users/CharonChui/repos"

上面介绍的例子中使用的是@Path

@Query@QueryMap

假设我们有一个分页查询的功能:

GET("/list")
Call<ResponseBody> list(@Query("page") int page);

这就相当于https://api.github.com/list?page=1这种。Query其实就是Url?后面的k-v。而QueryMap就是多个查询条件。

@Field@FieldMap

Post的场景相对比较多,绝大多数的服务端接口要做加密、校验等。所以使用Post提交表单的场景就会很多。

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

当然FieldMap就是多个的版本了。

@Part@PartMap

上传文件时使用。

public interface FileUploadService {  
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
}

@Headers

可以通过@Headers来设置静态的请求头

@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

请求头信息可以通过@Header注解来动态更新

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

如果传的值是null,该请求头将会被忽略,否则将会使用该值得toString

RxJava+Retrofit

Retrofit如何与RxJava结合使用呢?

  • 添加依赖

    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'// 支持gson
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'// 支持rxjava
    
    // rxjava part
    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'io.reactivex:rxjava:1.2.3'
  • 修改Retrofit的配置,让其支持RxJava

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava
        .build();
  • 修改GitHubService,将返回值改为Observable,而不是Call

    public interface GitHubService {
        @GET("users/{user}/repos")
        Observable<List<Repo>> listRepos(@Path("user") String user);
    }
  • 执行部分

    GitHubService service = retrofit.create(GitHubService.class);
    
    service.listRepos("CharonChui")
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<List<Repo>>() {
            @Override
            public void onCompleted() {
                Log.i("@@@", "onCompleted");
            }
    
            @Override
            public void onError(Throwable e) {
                Log.i("@@@", "onError : " + e.toString());
            }
    
            @Override
            public void onNext(List<Repo> repos) {
                Log.i("@@@", "onNext : " + repos.size());
                Toast.makeText(MainActivity.this, "size : " + repos.size(), Toast.LENGTH_SHORT).show();
            }
        });

    RxJava+Retrofit形式的时候,Retrofit把请求封装进Observable在请求结束后调用 onNext()或在请求失败后调用onError()

Proguard配置

如果项目中使用了Proguard,你需要添加如下配置:

# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on RoboVM on iOS. Will not be used at runtime.
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions