Retrofit结合RxJava使用指南

bdg51847 8年前
   <p>Retrofit是一个当前很流行的网络请求库, 官网的介绍是: "Type-safe HTTP client for Android and Java". 本文介绍Retrofit的使用.</p>    <p>先介绍单独使用Retrofit进行网络请求, 后面主要介绍和RxJava结合的请求, 有实例代码.</p>    <h2><strong>Retrofit单独使用</strong></h2>    <h3><strong>Setup</strong></h3>    <p>首先在manifest中加上网络权限:</p>    <pre>  <code class="language-java"><uses-permission android:name="android.permission.INTERNET" /></code></pre>    <p>然后在 app/build.gradle 中加上依赖:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:retrofit:2.1.0'  compile 'com.google.code.gson:gson:2.8.0'  compile 'com.squareup.retrofit2:converter-gson:2.1.0'</code></pre>    <h3><strong>准备API和model类</strong></h3>    <p>本例子中使用 Github API 做请求.</p>    <p>以Github的Root Endpoint为例:</p>    <p>https://api.github.com .</p>    <p>首先, 我们在命令行发送:</p>    <pre>  <code class="language-java">curl https://api.github.com</code></pre>    <p>或者在Postman发送这个请求, 两种方法都可以得到结果.</p>    <p>这个请求返回的是一个json.</p>    <p>利用这个网站: jsonschema2pojo , 可以用json生成一个java类, 比如上面这个, 我们给它起名字叫 Endpoints.java .</p>    <p>之后例子中的API都是这种方式, 先发送请求得到json, 然后转成java的model类.</p>    <h3><strong>利用Retrofit发送请求并得到结果</strong></h3>    <p>首先写一个 ServiceGenerator 类, 用于生成service:</p>    <pre>  <code class="language-java">public class ServiceGenerator {      public static final String API_BASE_URL = "https://api.github.com";        private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();        private static Retrofit.Builder builder =              new Retrofit.Builder()                      .baseUrl(API_BASE_URL)                      .addConverterFactory(GsonConverterFactory.create());        public static <S> S createService(Class<S> serviceClass) {          Retrofit retrofit = builder.client(httpClient.build()).build();          return retrofit.create(serviceClass);      }  }</code></pre>    <p>这里指定了我们的base url.</p>    <p>createService() 方法返回的是一个泛型.</p>    <p>然后我们创建 GithubService , 注意这是一个 <strong>接口</strong> :</p>    <pre>  <code class="language-java">import com.ddmeng.helloretrofit.data.models.Endpoints;    import retrofit2.Call;  import retrofit2.http.GET;  import retrofit2.http.Url;    public interface GitHubService {      @GET      Call<Endpoints> getAllEndpoints(@Url String url);  }</code></pre>    <p>这里 @GET 指定了是一个GET请求, 因为我们请求的就是base url, 所以是这样写的.</p>    <p>Endpoints 类是这个请求所返回的json转化的java类.</p>    <p>好了, 准备工作做完了, 现在就可以请求并得到结果:</p>    <p>请求github api的root url, 得到所有的endpoints:</p>    <pre>  <code class="language-java">GitHubService gitHubService = ServiceGenerator.createService(GitHubService.class);  Call<Endpoints> endpointsCall = gitHubService.getAllEndpoints("");  endpointsCall.enqueue(new Callback<Endpoints>() {      @Override      public void onResponse(Call<Endpoints> call, Response<Endpoints> response) {          Endpoints endpoints = response.body();          String repositoryUrl = endpoints.getRepositoryUrl();          LogUtils.i(repositoryUrl);          Toast.makeText(MainActivity.this, "repository url: " + repositoryUrl, Toast.LENGTH_LONG).show();      }        @Override      public void onFailure(Call<Endpoints> call, Throwable t) {        }  });</code></pre>    <p>说明:</p>    <p>首先利用前面的ServiceGenerator来创建Service, 然后调用接口中定义的 getAllEndpoints() 方法, 此处传入了空字符串, 因为我请求的就是base url.</p>    <h3><strong>同步和异步</strong></h3>    <p>这里注意用Retrofit请求的返回值是 Call<T> (后面我们还会介绍用RxJava的情形), 泛型T是model类型, 它有两个方法:</p>    <ul>     <li>execute() 是同步方法, 返回 Response<T> ;</li>     <li>enqueue() 是异步方法, 在上面的例子中用的就是这个, 在回调 onResponse() 中返回了 Response<T> .</li>    </ul>    <h3><strong>Converter</strong></h3>    <p>Converter的作用: 如果不指定Converter, 默认情况下Retrofit只能返回 ResponseBody 类型, 加了Converter之后就可以返回我们定义的Model类型了.</p>    <p>所以Converter替我们做了json -> model的工作.</p>    <p>本例子中ConverterFactory指定的是 GsonConverterFactory . 这里我们选的是Gson Converter, 所以依赖的是 com.squareup.retrofit2:converter-gson .</p>    <p>Retrofit支持多种converters:</p>    <ul>     <li>Gson: com.squareup.retrofit2:converter-gson</li>     <li>Jackson: com.squareup.retrofit2:converter-jackson</li>     <li>Moshi: com.squareup.retrofit2:converter-moshi</li>     <li>Protobuf: com.squareup.retrofit2:converter-protobuf</li>     <li>Wire: com.squareup.retrofit2:converter-wire</li>     <li>Simple XML: com.squareup.retrofit2:converter-simplexml</li>     <li>Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars</li>    </ul>    <h3><strong>Path和参数</strong></h3>    <p>从上面返回的endpoints可以看到, user_url是: https://api.github.com/users/{user}</p>    <p>这是一个带path参数的url, 我们发请求的时候在{user}处写一个github用户名, 即可得到该用户的信息, 比如:</p>    <p>https://api.github.com/users/mengdd .</p>    <p>那么用Retrofit如何处理呢?</p>    <p>只需要在 GithubService 中增加一个方法, 这样写:</p>    <pre>  <code class="language-java">public interface GitHubService {      @GET      Call<Endpoints> getAllEndpoints(@Url String url);        @GET("users/{user}")      Call<User> getUser(@Path("user") String user);  }</code></pre>    <p>使用时的方法完全一样, 不再赘述, 同理, 如果要在后面加参数, 可以用 @Query .</p>    <h2><strong>Retrofit + RxJava</strong></h2>    <p>RxJava近年来很流行, 主要优势是流式操作, 可以处理并行发送请求, 使用灵活, 线程切换容易.</p>    <p>当你要处理的逻辑比较复杂时, 就会发现使用RxJava的优势.</p>    <p>以我们的例子来说, 当前我们利用一个请求可以得到一个用户的信息并显示出来.</p>    <p>如果我们想得到这个用户的所有repo的所有者或者其他信息, 所有他follow的人的信息, 以及他们的repo的信息呢?</p>    <p>这就需要发很多个请求, 并且其中有些请求是并行发送的, 如果按照前面的方法, 不断地在callback里面嵌套, 那就太难看了.</p>    <h3><strong>Setup with RxJava</strong></h3>    <p>添加RxJava依赖</p>    <p>首先, 添加RxJava和RxAndroid的依赖:</p>    <pre>  <code class="language-java">compile 'io.reactivex:rxjava:1.2.2'  compile 'io.reactivex:rxandroid:1.2.1'</code></pre>    <p>注: 虽然在我写这篇文章的时候(2016.11.4)RxJava2.0刚刚release, 但是我们还是先用RxJava1来写这个demo.</p>    <p>然后添加retrofit的adapter-rxjava:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'</code></pre>    <p>所以现在我们的依赖总的看起来是这样:</p>    <pre>  <code class="language-java">dependencies {      compile fileTree(dir: 'libs', include: ['*.jar'])      androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {          exclude group: 'com.android.support', module: 'support-annotations'      })      compile "com.android.support:appcompat-v7:${supportLibVersion}"      compile "com.android.support:design:${supportLibVersion}"      compile "com.jakewharton:butterknife:${butterKnifeVersion}"      apt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"      compile 'com.squareup.retrofit2:retrofit:2.1.0'      compile 'com.google.code.gson:gson:2.8.0'      compile 'com.squareup.retrofit2:converter-gson:2.1.0'      compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'      compile 'io.reactivex:rxjava:1.2.2'      compile 'io.reactivex:rxandroid:1.2.1'      testCompile 'junit:junit:4.12'  }</code></pre>    <p>Retrofit结合RxJava</p>    <p>Retrofit.Builder()中加入这一行:</p>    <p>.addCallAdapterFactory(RxJavaCallAdapterFactory.create());</p>    <p>ServiceGenerator变成这样:</p>    <pre>  <code class="language-java">public class ServiceGenerator {      public static final String API_BASE_URL = "https://api.github.com";        private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();        private static Retrofit.Builder builder =              new Retrofit.Builder()                      .baseUrl(API_BASE_URL)                      .addConverterFactory(GsonConverterFactory.create())                      .addCallAdapterFactory(RxJavaCallAdapterFactory.create());        public static <S> S createService(Class<S> serviceClass) {          Retrofit retrofit = builder.client(httpClient.build()).build();          return retrofit.create(serviceClass);      }  }</code></pre>    <p>这样我们在 GithubService 中定义的接口方法, 既可以像原来一样返回 Call , 也可以返回 Observable .</p>    <h3><strong>Retrofit + RxJava请求实例</strong></h3>    <p>以单个请求为例,</p>    <p>不用RxJava的时候:</p>    <pre>  <code class="language-java">@GET("users/{user}/following")  Call<List<User>> getUserFollowing(@Path("user") String user);</code></pre>    <p>请求的时候是这样的:</p>    <p>请求指定用户follow的所有人:</p>    <pre>  <code class="language-java">GitHubService service = ServiceGenerator.createService(GitHubService.class);  Call<List<User>> userFollowing = service.getUserFollowing(inputUserNameView.getText().toString());  userFollowing.enqueue(new Callback<List<User>>() {      @Override      public void onResponse(Call<List<User>> call, Response<List<User>> response) {          List<User> followingUsers = response.body();          peopleListAdapter.setUsers(followingUsers);          peopleListAdapter.notifyDataSetChanged();      }        @Override      public void onFailure(Call<List<User>> call, Throwable t) {        }  });</code></pre>    <p>现在改用RxJava了, 返回的不是Call而是Observable:</p>    <pre>  <code class="language-java">@GET("users/{user}/following")  Observable<List<User>> getUserFollowingObservable(@Path("user") String user);</code></pre>    <p>结合RxJava请求的时候变为这样:</p>    <p>还是请求用户follow的所有人:</p>    <pre>  <code class="language-java">GitHubService service = ServiceGenerator.createService(GitHubService.class);  String username = inputUserNameView.getText().toString();  service.getUserFollowingObservable(username)          .subscribeOn(Schedulers.io())          .observeOn(AndroidSchedulers.mainThread())          .subscribe(new Subscriber<List<User>>() {              @Override              public void onCompleted() {                }                @Override              public void onError(Throwable e) {                }                @Override              public void onNext(List<User> users) {                  LogUtils.i("onNext: " + users.size());                  peopleListAdapter.setUsers(users);                  peopleListAdapter.notifyDataSetChanged();              }          });</code></pre>    <p>用RxJava实现后, 请求返回的是一个Observable, 用 subscribe() 添加一个订阅者, 即它的观察者.</p>    <p>当请求返回后, 回到主线程, 更新UI.</p>    <p>这是单个请求的例子, 所以RxJava的优势不是很明显, 如果我们有多个请求, 用RxJava进行变换组合显然就是更好的选择.</p>    <p>用RxJava进行线程切换</p>    <p>上个例子中 .subscribeOn(Schedulers.io()) 指定Observable的工作, 在我们的例子中Observable的工作即发送请求, 在io线程做, 指定了被观察者的处理线程;</p>    <p>.observeOn(AndroidSchedulers.mainThread()) 指定最后onNext()回调在主线程, 即指定了通知后续观察者的线程.</p>    <h3><strong>RxJava处理多个请求的例子</strong></h3>    <p>设计这样一个场景, 我们现在取到了一个用户follow的所有人, 但是取回的信息中并不包含每个人拥有的repo个数, 只有一个url可用户查看所有repo.</p>    <p>接下来我们要取其中每一个人的详细信息, 就要查询另一个API, 重新查询这个人的完整信息.</p>    <p>查询用户follow的所有人, 然后查询每一个人的详细信息:</p>    <pre>  <code class="language-java">subscription = service.getUserFollowingObservable(username)          .flatMap(new Func1<List<User>, Observable<User>>() {              @Override              public Observable<User> call(List<User> users) {                  return Observable.from(users);              }          })          .flatMap(new Func1<User, Observable<User>>() {              @Override              public Observable<User> call(User user) {                  return service.getUserObservable(user.getLogin());              }          })          .toList()          .subscribeOn(Schedulers.io())          .observeOn(AndroidSchedulers.mainThread())          .subscribe(new Subscriber<List<User>>() {              @Override              public void onCompleted() {                }                @Override              public void onError(Throwable e) {                }                @Override              public void onNext(List<User> users) {                  peopleListAdapter.setUsers(users);                  peopleListAdapter.notifyDataSetChanged();              }          });</code></pre>    <p>可以看到我们加了两个 flatMap() 和一个 toList() 来做这个事情.</p>    <p>首先, 第一步我们用 getUserFollowingObservable() 得到的是一个 Observable<List<User>> ;</p>    <p>我们之后用 .flatMap() , 它的输入是 List<User> , 返回的是 Observable<User> . 我们在其中用了一个 .from() 来生成一个发射一组User的 Observable ;</p>    <p>之后第二个 .flatMap() 里, 输入是前一个 Observable 的输出, 即User, 调用了 getUserObservable() , 返回的结果是 Observable<User> , 之后加一个 .toList() , 把输出的结果从单个的User变为List , 即和我们最初的例子一样.</p>    <p>只不过此时得到的用户信息是更详细的用户信息, 包含了他的repo数据和follow数据. 因为它们是通过单独查询每一个人得到的.</p>    <p>运行, 虽然可以得到我们想要的结果, 但是这个例子仍然是有问题的.</p>    <p>线程问题处理</p>    <p>上面多个请求的例子, 发现虽然实现了我们的需求, 但是结果回来得很慢.</p>    <p>我们加上一个 .map 操作符来加上log:</p>    <p>(这里省略了一些前后的代码, 只是在 .flatMap() 里加了一个 .map() )</p>    <pre>  <code class="language-java">...  subscription = service.getUserFollowingObservable(username)          .flatMap(new Func1<List<User>, Observable<User>>() {              @Override              public Observable<User> call(List<User> users) {                  return Observable.from(users);              }          })          .flatMap(new Func1<User, Observable<User>>() {              @Override              public Observable<User> call(User user) {                  return service.getUserObservable(user.getLogin())                          .map(new Func1<User, User>() {                              @Override                              public User call(User user) {                                  // this .map is used to output log information to check the threads                                  LogUtils.i("getUserObservable: " + user.getLogin());                                  return user;                              }                          });              }          })          .toList()          ...</code></pre>    <p>由Log可以发现(log中的线程号是一样的)单独取每一个用户详细信息的请求都发生在同一个线程, 是顺次进行的.</p>    <p>回头梳理一下我们的需求, 请求一个所有follow的人, 返回一个follow的人的List, 然后对List中的每一个人, 单独请求详细信息.</p>    <p>那么按理来说, 第二个批量的请求是可以同时发送, 并行进行的.</p>    <p>所以我们想要的行为其实是平行发送多个请求, 然后最后统一结果到UI线程.</p>    <p>改动如下:</p>    <pre>  <code class="language-java">subscription = service.getUserFollowingObservable(username)          .subscribeOn(Schedulers.io()) // 从io线程开始, 取用户follow的所有人          .flatMap(new Func1<List<User>, Observable<User>>() {              @Override              public Observable<User> call(List<User> users) {                  LogUtils.i("from");                  return Observable.from(users);              }          })          .flatMap(new Func1<User, Observable<User>>() {              @Override              public Observable<User> call(User user) {                  return service.getUserObservable(user.getLogin()) // 取每个人的详细信息                          .subscribeOn(Schedulers.io()) // 指定取每个人详细信息的工作都在单独的io线程                          .map(new Func1<User, User>() {                              @Override                              public User call(User user) {                                  // this map operation is just used for showing log                                  LogUtils.i("getUserObservable: " + user.getLogin());                                  return user;                              }                          });              }          })          .toList()          .observeOn(AndroidSchedulers.mainThread()) // 最后返回到主线程          .subscribe(new Subscriber<List<User>>() {              @Override              public void onCompleted() {                }                @Override              public void onError(Throwable e) {                }                @Override              public void onNext(List<User> users) {                  LogUtils.i("onNext: " + users.size());                  peopleListAdapter.setUsers(users);                  peopleListAdapter.notifyDataSetChanged();              }          })</code></pre>    <p>给改动的部分加上了注释, 这样更清楚一些.</p>    <p>注意 subscribeOn() 指定的是当前的这个Observable的工作在什么线程进行.</p>    <p>所以在本例子中, subscribeOn(Schedulers.io()) 的位置放在 .flatMap() 里面才会产生多个请求并行的效果.</p>    <p>这样一改, 我们的显示时间不再是所有请求时间之和, 而是只取决于最慢的那个请求时间.</p>    <h3>取消订阅</h3>    <p>正常情况下, 行为结束之后, 到达 onComplete() 或者 onError() , RxJava的订阅会自动取消.</p>    <p>但是在处理网络请求的时候, 很可能会出现请求还没有返回, 界面就已经结束了的情况.</p>    <p>上面的代码中已经出现了, 订阅方法 subscribe() 的返回值是一个 Subscription 对象, 我们保存了这个对象的引用, 然后在 onPause() 的时候取消了请求, 防止内存泄露.</p>    <pre>  <code class="language-java">@Override  public void onPause() {      super.onPause();      if (subscription != null && subscription.isUnsubscribed()) {          subscription.unsubscribe();      }  }</code></pre>    <p>当然也可以选别的生命周期回调, 比如 onDestroyView() 或者 onDestroy() .</p>    <p>如果有多个请求, 可以用:</p>    <pre>  <code class="language-java">private CompositeSubscription compositeSubscription = new CompositeSubscription();    ...  // 在发请求的地方, 返回subscription  compositeSubscription.add(subscription);  ...    // 选一个生命周期注销所有请求  @Override  public void onPause() {      super.onPause();      compositeSubscription.unsubscribe();  }</code></pre>    <h2><strong>References</strong></h2>    <ul>     <li><a href="/misc/goto?guid=4958964956869128717" rel="nofollow,noindex">Retrofit Github</a></li>     <li><a href="/misc/goto?guid=4959659675611292239" rel="nofollow,noindex">Retrofit Website</a></li>     <li><a href="/misc/goto?guid=4959724591687431396" rel="nofollow,noindex">CodePath- Consuming APIs with Retrofit</a></li>     <li><a href="/misc/goto?guid=4959724591779376677" rel="nofollow,noindex">Future Studio 系列教程</a></li>     <li><a href="/misc/goto?guid=4959720672419461902" rel="nofollow,noindex">RxJava 与 Retrofit 结合的最佳实践</a></li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://www.cnblogs.com/mengdd/p/6047948.html</p>    <p> </p>