我们真的需要使用RxJava+Retrofit吗?

EdEspino 7年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/418231b2ab82a647149861e7e9fd5c23.jpg"></p>    <h2>前言</h2>    <p>可以说RxJava+Retrofit是整个2016年Android 开发圈内最受关注的的组合。各大Android论坛上有大量以RxJava+Retrofit+xxx 为标题的文章,此类文章也备受大家的关注。这个组合仿佛已经成为了Android开发的必备组件,项目里没使用这个组合好像自己都out了似的。</p>    <p>平心而论,RxJava和Retrofit 相较于以往的各种框架(如 AsyncHttpClient,Volley等 )学习和使用起来会有一些难度;RxJava 强大而又庞大的操作符,Retrofit采用注解风格定义接口,都会让初学者花费不少功夫,绕不少圈子,踩大量的坑。既然这样,那么就会有人怀疑,我们真的需要学习RxJava和Retrofit吗?</p>    <p>任意一款需要联网的APP,最典型的套路就是请求后端数据,解析数据进行UI更新;响应用户操作,再次请求数据,更新UI。这里我们就从最基础的网络请求出发,带着疑问,逐步了解一下Retrofit的前生今世,看一看RxJava和Retrofit的价值所在。</p>    <h2>Android Http</h2>    <h3>最基础的实现方式</h3>    <p>初学Android开发时,还在上大学,那会儿还不知有AsyncHttpClient,Volley,OKHttp 这么方便的框架;一个简单的网络请求通常要写一大段代码。</p>    <p>使用HttpURLConnection实现网络请求####</p>    <pre>  <code class="language-java">class MyTask extends AsyncTask<String, Void, String> {            @Override          protected String doInBackground(String... params) {              InputStream mInputStream = null;              HttpURLConnection connection = getHttpUrlConnection(params[0]);              String result = "";              try {                  connection.connect();                  int statusCode = connection.getResponseCode();                  String response = connection.getResponseMessage();                  mInputStream = connection.getInputStream();                  InputStreamReader inputStreamReader = new InputStreamReader(mInputStream);                  BufferedReader reader = new BufferedReader(inputStreamReader);                  StringBuffer sb = new StringBuffer();                  String line;                  while ((line = reader.readLine()) != null) {                      sb.append(line + "\n");                  }                    result = "StatusCode: " + statusCode + "\n"                          + "Response" + response + "\n"                          + sb.toString();              } catch (IOException e) {                  e.printStackTrace();              }              return result;          }            @Override          protected void onPostExecute(String s) {              super.onPostExecute(s);              tv.setText(s);          }      }        private HttpURLConnection getHttpUrlConnection(String url) {          HttpURLConnection connection = null;          try {              URL mUrl = new URL(url);              connection = (HttpURLConnection) mUrl.openConnection();              connection.setConnectTimeout(20000);              connection.setReadTimeout(40000);              connection.setRequestMethod("GET");              connection.setRequestProperty("Content-Type", "application/json");              connection.setRequestProperty("Accept", "application/json");              connection.setRequestProperty("Charset", "utf-8");              connection.setRequestProperty("Content-Length", "0");            } catch (MalformedURLException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          }            return connection;      }</code></pre>    <pre>  <code class="language-java">new MyTask().execute(BASE_URL);</code></pre>    <p>这段代码的逻辑很简单,就是将网络请求的结果显示在一个TextView上。很大一部分的内容都是在执行HttpURLConnection 相关的配置及初始化工作。</p>    <p>记得第一次通过网络请求把数据显示的Android模拟器(那时候还是穷学生,买不起Android手机)的屏幕上时,虽然只是一大堆别人看不懂的json字符串,但是感觉自己就要上天了,现在想想真是。。。。。</p>    <p>即便是这么长的一段代码,还没有包含网络请求异常的内容,如果加上网络请求失败处理的逻辑,将使得整个代码结构更加臃肿庞大。</p>    <h3>网络请求框架的涌现</h3>    <p>一款联网的APP至少会有十几次的网络请求,更多的就无法估计了。因此,每一次的网络请求不可能像上面那样写。因此,我们需要封装,将一些固定的操作统一处理,当然已经有许多大神比我早想到了这个问题,因此便出现了许多对网络请求进行封装的库。</p>    <ul>     <li>AsyncHttpClient(底层基于HttpClient)</li>     <li>afinal(FinalHttp,同样是基于HttpClient封装)</li>     <li>xUtils (基于afinal)</li>     <li>Volley(Google官方出品)</li>     <li>okHttp</li>     <li>NoHttp (个人开发)</li>    </ul>    <p>这里列出的几个库当中,个人使用AsyncHttpClient较多,AsyncHttpClient 的确非常好用,但是后来伴随着Android sdk 23 中HttpClient的废弃也逐渐被遗忘。</p>    <p>afinal和xUtils 都没有在实际项目中没用过,不做评价。</p>    <p>Volley作为Google官方在2013年I/O 大会上推出的库,相较于AsyncHttpClient 更强大。</p>    <p>Volley 简单使用</p>    <p>这里使用的Volley版本:</p>    <pre>  <code class="language-java">compile 'com.mcxiaoke.volley:library:1.0.19'</code></pre>    <pre>  <code class="language-java">protected void onCreate(@Nullable Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          mContext = this;          queue = Volley.newRequestQueue(mContext);          setContentView(R.layout.activity_http_volley_demo);          tv = (TextView) findViewById(R.id.editText);            final StringRequest request = new StringRequest(Request.Method.GET, BASE_URL,                  new ResponseSuccessListener(), new ResponseFailListener());          findViewById(R.id.volley).setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  queue.add(request);              }          });        }        private class ResponseSuccessListener implements com.android.volley.Response.Listener<String> {            @Override          public void onResponse(String response) {              tv.setText(response);          }      }        private class ResponseFailListener implements Response.ErrorListener {            @Override          public void onErrorResponse(VolleyError error) {              Toast.makeText(mContext, error.toString(), Toast.LENGTH_SHORT).show();          }      }</code></pre>    <p>这段代码和上面的功能一样,都是将网络请求的结果显示在TextView。但是通过Volley对http请求进行一次封装后,我们不再关注网络请求的具体实现,而是将重点放在了对请求结果的处理上;网络请求无论成功还是失败,我们都可以很多好的应对。</p>    <p>而且在Volley中,异步网络请求的回调方法已然处于UI线程中,这样我们就可以直接在回调方法中进行UI更新了。</p>    <p>OKHttp 简单介绍</p>    <p>okHttp 是由squire 推出的一个网络请求库,包括Retrofit也是由其开发,这里为squire点个赞。</p>    <p>使用之前加入依赖</p>    <pre>  <code class="language-java">compile 'com.squareup.okhttp3:okhttp:3.4.1'      compile 'com.squareup.okio:okio:1.11.0'</code></pre>    <p>okHttp 网络请求实现</p>    <pre>  <code class="language-java">findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  tv.setText("");                  loading.setVisibility(View.VISIBLE);                  client = new OkHttpClient();                  Request.Builder builder = new Request.Builder()                          .url(BASE_URL)                          .method("GET", null);                    request = builder.build();                  Call mCall = client.newCall(request);                  mCall.enqueue(new MyCallback());              }          });    private class MyCallback implements Callback {            @Override          public void onFailure(Call call, IOException e) {              Message msg = new Message();              msg.what = 100;              msg.obj = e;              handler.sendMessage(msg);          }            @Override          public void onResponse(Call call, Response response) throws IOException {              Message msg = new Message();              msg.what = 200;              msg.obj = response.body().string();              handler.sendMessage(msg);          }      }    class MyHandler extends Handler {          @Override          public void handleMessage(Message msg) {              super.handleMessage(msg);              loading.setVisibility(View.GONE);              switch (msg.what) {                  case 100:                      Object e = msg.obj;                      Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();                      break;                  case 200:                      String response = (String) msg.obj;                      tv.setText(response);                      break;                  case 300:                      int percent = msg.arg1;                      Log.e("llll", "the percent is " + percent);                      if (percent < 100) {                          progressDialog.setProgress(percent);                      } else {                          progressDialog.dismiss();                          Glide.with(mContext).load(FILE_PATH).into(imageView);                      }                      break;                  default:                      break;              }          }      }</code></pre>    <p>从上面的代码可以看到,使用OKHttp做异步请求后,在回调方法中,我们使用了Handler。因为这个回调方法并不处于UI线程当中,因此不能直接对UI 进行操作。</p>    <p>这里并不能说OKHttp不够强大,其实okhttp的封装套路和Volley,AsyncHttp不是一个级别的,不能和后两者作比较,okhttp 和HttpClient、HttpUriConneciton 才是一个级别的产物,相较于这两者,okhttp显然强大了许多。</p>    <p>所以,OKHttp不仅仅可以用于Android开发,Java开发也是OK的。</p>    <h2>Retrofit</h2>    <p>A type-safe HTTP client for Android and Java</p>    <p>一个针对Android和Java类型安全的http客户端</p>    <p>上面这句话,就是Squire对Retrofit的说明,言简意赅。Retrofit其实是对okhttp 做了进一步的封装,有了okhttp 的基础,使用Retrofit会很容易。</p>    <p>使用之前加入依赖:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:retrofit:2.1.0'</code></pre>    <p>定义接口</p>    <pre>  <code class="language-java">public interface GithubService {        @GET("users/{user}")      Call<ResponseBody> getUserString(@Path("user") String user);    }</code></pre>    <p>这里我们使用http中的get 方法获取users这个接口下,当前user的具体信息,参数为当前user名。返回内容为Http请求的ResponseBody。</p>    <p>Retrofit 返回ResponseBody</p>    <pre>  <code class="language-java">private void SimpleRetrofit() {          OkHttpClient.Builder httpClient = new OkHttpClient.Builder();          Retrofit.Builder builder = new Retrofit.Builder()                  .baseUrl(BASE_URL);          Retrofit retrofit = builder.client(httpClient.build()).build();          GithubService simpleService = retrofit.create(GithubService.class);          Call<ResponseBody> call = simpleService.getUserString(name);          call.enqueue(new Callback<ResponseBody>() {              @Override              public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {                  loading.dismiss();                  try {                      String result = response.body().string();                      Gson gson = new Gson();                      GithubUserBean bean = gson.fromJson(result, GithubUserBean.class);                      setUserView(bean);                  } catch (IOException e) {                      e.printStackTrace();                  }              }                @Override              public void onFailure(Call<ResponseBody> call, Throwable t) {                  loading.dismiss();              }          });      }    private void setUserView(GithubUserBean user) {          if (user != null) {              viewShell.removeAllViews();              View view = LayoutInflater.from(mContext).inflate(R.layout.user_item_layout, null);              TextView title = (TextView) view.findViewById(R.id.title);              TextView id = (TextView) view.findViewById(R.id.userId);              TextView creteaTime = (TextView) view.findViewById(R.id.createTime);              TextView updateTime = (TextView) view.findViewById(R.id.updateTime);              TextView bio = (TextView) view.findViewById(R.id.bio);              ImageView avatar = (ImageView) view.findViewById(R.id.avatar);                title.setText("Name: " + user.getLogin());              bio.setText("Bio: " + user.getBio());              id.setText("Id: " + String.valueOf(user.getId()));              creteaTime.setText("createTime: " + user.getCreated_at());              updateTime.setText("updateTime: " + user.getUpdated_at());              Glide.with(mContext).load(user.getAvatar_url()).into(avatar);                viewShell.addView(view);          } else {              Toast.makeText(mContext, "result is null", Toast.LENGTH_SHORT).show();          }      }</code></pre>    <p>这里GitHubUserBean为网络请求结果json数据所对应的实体类。</p>    <p>通过这段代码,我们在最终的回调方法里可以友好的处理请求结果,失败时onFailure方法执行。成功时,onResponse方法执行,我们在这里用Gson解析返回的数据,并进行UI更新操作(setUserView(bean)),</p>    <p>这里我们这样做有些啰嗦,Gson转换的方式都是类似,唯一不同的只是具体的类;因此我们可以借助强大的Retrofit帮助我们完成Gson转换的步骤。</p>    <p>添加依赖:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:converter-gson:2.1.0'</code></pre>    <p>注意这里converter-gson 的版本号,要和之前Retrofit的版本号保持一致。</p>    <p>我们重新定义接口:</p>    <pre>  <code class="language-java">public interface GithubService {      @GET("users/{user}")      Call<GithubUserBean> getUser(@Path("user") String user);    }</code></pre>    <p>这里我们用GithubUserBean取代ResponseBody,直接将其作为返回类型。</p>    <p>Retrofit 返回对象</p>    <pre>  <code class="language-java">private void LazyRetrofit() {          OkHttpClient.Builder httpClient = new OkHttpClient.Builder();          Retrofit.Builder builder = new Retrofit.Builder()                  .baseUrl(BASE_URL)                  .addConverterFactory(GsonConverterFactory.create());          Retrofit retrofit = builder.client(httpClient.build()).build();          GithubService service = retrofit.create(GithubService.class);          Call<GithubUserBean> call = service.getUser(name);          call.enqueue(new Callback<GithubUserBean>() {              @Override              public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {                  GithubUserBean bean = response.body();                  setUserView(bean);                  loading.dismiss();              }                @Override              public void onFailure(Call<GithubUserBean> call, Throwable t) {                  loading.dismiss();              }          });      }</code></pre>    <p>这里的实现方式和上面基本相似,只是多了一行</p>    <pre>  <code class="language-java">.addConverterFactory(GsonConverterFactory.create());</code></pre>    <p>这样,我们在onResponse中获得就是对象,不再需要做额外的转换工作,可以直接使用。</p>    <p>Retrofit 简单封装</p>    <p>这里我们可以看到,Retrofit使用有着一定的套路,所以我们可以将Retrofit初始化相关得内容做一次简单的封装。</p>    <pre>  <code class="language-java">public class GenServiceUtil {      private static final String BASE_URL = "https://api.github.com/";        private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();        private static Retrofit.Builder builder = new Retrofit.Builder()              .baseUrl(BASE_URL)              .addConverterFactory(GsonConverterFactory.create());        private static Retrofit retrofit = builder.client(httpClient.build()).build();        public static <S> S createService(Class<S> serviceClass) {          return retrofit.create(serviceClass);      }    }    private void EasyRetrofit() {          GithubService service = GenServiceUtil.createService(GithubService.class);          Call<GithubUserBean> call = service.getUser(name);          call.enqueue(new Callback<GithubUserBean>() {              @Override              public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {                  GithubUserBean bean = response.body();                  loading.dismiss();                  setUserView(bean);              }                @Override              public void onFailure(Call<GithubUserBean> call, Throwable t) {                  loading.dismiss();              }          });      }</code></pre>    <p>我们只需传入定义好的借口,会使代码简介许多。看到这里可以发现,Retrofit的确很厉害,那为什么又要将他和RxJava结合在一起呢?下面我们就来看看。</p>    <h2>RxJava+Retrofit</h2>    <p>这里我们就看看将RxJava 和我们之前的内容结合在一起会有怎样的效果。</p>    <p>首先,加入依赖</p>    <pre>  <code class="language-java">compile 'io.reactivex:rxjava:1.1.7'      compile 'io.reactivex:rxandroid:1.2.1'</code></pre>    <h3>RxJava+Retrofit 实现</h3>    <pre>  <code class="language-java">private void RxRetrofit() {          GithubService service = GenServiceUtil.createService(GithubService.class);          final Call<GithubUserBean> call = service.getUser(name);          final Observable myObserable = Observable.create(new Observable.OnSubscribe<GithubUserBean>() {              @Override              public void call(Subscriber<? super GithubUserBean> subscriber) {                  Response<GithubUserBean> bean = null;                  try {                      bean = call.execute();                      subscriber.onNext(bean.body());                    } catch (IOException e) {                      e.printStackTrace();                      subscriber.onError(e);                  }                    subscriber.onCompleted();              }          });            myObserable                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .map(new Func1<GithubUserBean, GithubUserBean>() {                      @Override                      public GithubUserBean call(GithubUserBean o) {                          if (TextUtils.isEmpty(o.getBio())) {                              o.setBio("nothing !");                          }                          return o;                      }                  })                  .subscribe(new Subscriber<GithubUserBean>() {                      @Override                      public void onCompleted() {                          loading.dismiss();                      }                        @Override                      public void onError(Throwable e) {                          loading.dismiss();                      }                        @Override                      public void onNext(GithubUserBean o) {                          setUserView(o);                      }                  });        }</code></pre>    <p>这里有几点需要注意:</p>    <ul>     <li>RxJava 本身最大的特定就是异步,因此这里我们Retrofit执行网络请求的时候,使用了execute(同步请求),而不再是enqueue。</li>     <li>RxJava 可以使用subscribeOn和observeOn完美处理Observeable和Subscribe的执行线程问题。</li>     <li>这里使用RxJava中map操作符,对返回内容中的为null或“” 的对象做了简单的处理。</li>    </ul>    <p>我们引入RxJava实现了同样的功能,却使得代码量增加了。RxJava的价值到底在哪里呢?</p>    <h3>RxJava + Retrofit 到底好在哪里</h3>    <p>好了,为了说明为题,我们添加一个接口</p>    <pre>  <code class="language-java">public interface GithubService {        @GET("users/{user}")      Call<GithubUserBean> getUser(@Path("user") String user);        @GET("users/{user}/followers")      Call<List<UserFollowerBean>> getFollowers(@Path("user") String user);    }</code></pre>    <p>新增的getFollowers 方法,会返回当前用户的所有followers list 对象。</p>    <pre>  <code class="language-java">private void RxRetrofitList() {          GithubService service = GenServiceUtil.createService(GithubService.class);          final Call<List<UserFollowerBean>> call = service.getFollowers(name);            Observable<List<UserFollowerBean>> myObserve = Observable.create(new Observable.OnSubscribe<List<UserFollowerBean>>() {              @Override              public void call(Subscriber<? super List<UserFollowerBean>> subscriber) {                  try {                      Response<List<UserFollowerBean>> followers = call.execute();                      subscriber.onNext(followers.body());                      subscriber.onCompleted();                  } catch (IOException e) {                      e.printStackTrace();                      subscriber.onError(e);                  }              }          });            myObserve                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribe(new Subscriber<List<UserFollowerBean>>() {                      @Override                      public void onCompleted() {                          loading.dismiss();                      }                        @Override                      public void onError(Throwable e) {                          loading.dismiss();                          e.printStackTrace();                      }                        @Override                      public void onNext(List<UserFollowerBean> userFollowerBeen) {                          setFollowersView(userFollowerBeen);                      }                  });        }</code></pre>    <p>在onNext 方法中,接收到返回的对象,更新UI。 这里如果我们不使用RxJava,单独使用Retrofit实现这个过程是没有任何问题的; RxJava看似没有价值;但是假设现在出现如下之一的情景</p>    <ul>     <li>需要对返回的userFollowerBeen 这个list 进行按用户名从小到大的排序</li>     <li>需要对返回的userFollowerBeen 这个list 进行按用户ID从小到大的排序</li>     <li>如果返回的userFollowerBeen 这个list 中,某一项的头像地址为空,则不显示该项</li>    </ul>    <p>…..</p>    <p>这种情景在实际开发中太常见了,而每一次需求的变更都意味着我们需要去修改setFollowersView这个方法,需求一旦变更,就去修改这个方法,这样会不可避免的产生各种bug。那有没有办法不去修改这个方法呢?这个时候,就需要强大的RxJava了。</p>    <p>这里我们就看看如何在不修改setFollowersView的前提下,实现对用户名从小到大的排序:</p>    <pre>  <code class="language-java">private void RxRetrofitList() {          GithubService service = GenServiceUtil.createService(GithubService.class);          final Call<List<UserFollowerBean>> call = service.getFollowers(name);            Observable<List<UserFollowerBean>> myObserve = Observable.create(new Observable.OnSubscribe<List<UserFollowerBean>>() {              @Override              public void call(Subscriber<? super List<UserFollowerBean>> subscriber) {                  try {                      Response<List<UserFollowerBean>> followers = call.execute();                      subscriber.onNext(followers.body());                      subscriber.onCompleted();                  } catch (IOException e) {                      e.printStackTrace();                      subscriber.onError(e);                  }              }          });            myObserve                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {                      @Override                      public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBeen) {                          for (UserFollowerBean bean : userFollowerBeen) {                              String name = "";                              name = bean.getLogin().substring(0, 1).toUpperCase() + bean.getLogin().substring(1, bean.getLogin().length());                              bean.setLogin(name);                          }                          return userFollowerBeen;                      }                  })                  .map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {                      @Override                      public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBean) {                          Collections.sort(userFollowerBean, new Comparator<UserFollowerBean>() {                              @Override                              public int compare(UserFollowerBean o1, UserFollowerBean o2) {                                  return o1.getLogin().compareTo(o2.getLogin());                              }                          });                          return userFollowerBean;                      }                  })                  .subscribe(new Subscriber<List<UserFollowerBean>>() {                      @Override                      public void onCompleted() {                          loading.dismiss();                      }                        @Override                      public void onError(Throwable e) {                          loading.dismiss();                          e.printStackTrace();                      }                        @Override                      public void onNext(List<UserFollowerBean> userFollowerBeen) {                          setFollowersView(userFollowerBeen);                      }                  });        }</code></pre>    <p>在代码中我们使用RxJava的map 操作符,对返回数据做了两次处理,首先将所有用户名的首字母转换为大写字母;然后对整个list按照用户名从小到大排序。因为用户名中同时包含以大小写字母打头的内容,所以为了方便,我们进行了一次转换大写的操作。</p>    <p>同样是随着需求变更,修改代码;但是你会发现,使用RxJava的方式,会降低出现bug的概率,而且就算是不同的人去改,也会比较方便维护。</p>    <p>看到了吧,这就是RxJava的优点,当然这个例子也只是冰山一角。RxJava的存在不仅仅在于网络请求,可以用在别的方面;RxJava其实是体现了一种思路,所有对数据的操作都在流上完成,讲最终的结果返回给观察者。 同时,如果返回的followers 列表有任何异常,RxJava的onError 方法会执行,这就方便我们去处理异常数据了。</p>    <h2>总结</h2>    <p>通篇通过对Android 网络请求各种实现的总结,可以看到 相对于Volley,AsyncHttpClient 等库,RxJava+Retrofit 的优势并非特别显著;在执行效率及功能上并无大的亮点;对Volley进行良好的封装同样可以实现类似Retrofit自动转Gson的功能;RxJava+Retrofit 结合会让我们写代码的方式更加有条理,虽然代码量会增多,但逻辑的清晰才是最重要的不是吗?所以,RxJava+Retrofit 组合不失为一种好的选择。</p>    <p> </p>    <p> </p>    <p>来自:http://blog.csdn.net/toyota11/article/details/53454925</p>    <p> </p>