Skip to content

Instantly share code, notes, and snippets.

@SmartDengg
Last active October 20, 2016 06:33
Show Gist options
  • Save SmartDengg/dafddcc5e990a62a96a0f077860d4340 to your computer and use it in GitHub Desktop.
Save SmartDengg/dafddcc5e990a62a96a0f077860d4340 to your computer and use it in GitHub Desktop.
NetWorkingPractice
**Android生态圈发展步伐之快,网络框架更是层出不穷,本次主讲人将带你回顾网络框架的变迁史,并对比时下流行网络框架,阐述他的经验,以及这些演变背后的原因。**
Android开发生态圈的节奏非常之快。每周都会有新的工具诞生,类库的更新,博客的发表以及技术探讨。
如果你外出度假,当你回来的时候可能已经发布了新版本的
网络框架不同于其他,因为它在一定程度上与业务耦合度极高,甚至从代码的角度来说会遍布于工程的各个角落,很难集中管理,一旦替换甚至还要经过漫长的调试过程。
不同与图像加载,日志打印等框架那样,在短时间内就能够进行重构,而且一直沿用多个迭代版本也是正常的。
这一次我会跟大家一起回顾Android开发中那些常见的网络加载类,还有库。
阐述我的观点,分享我的经验,希望大家能够喜欢。
@SmartDengg
Copy link
Author

SmartDengg commented Oct 18, 2016

volley vs okhttp vs retrofit

repository First release Last release Now Release Contributors Pull request Issues
okhttp on 6 May 2013 on 10 Jul 2016 3.4.1 43 121 1448 1302(closed) 108(open)
retrofit on 14 Jun 2012 on 15 Jun 2016 2.1.0 38 103 687 1323(closed) 41(open)

最显而易见的区别,

volley需要指定完整路径的访问地址,并且是在调用的时候以参数的的形式动态添加,可返回JSONObject或者JSONArray,不过这都依赖于所实现的接口类型,默认没有处理反序列化操作。

retrofit可以指定一个base URL,每一个请求只需要提供相对访问路径即可,请求参数均通过java注解来描述,支持动态添加和修改,让我们不必关注请求项的拼接,支持泛型的返回结果,让开发者不必再手动反序列化响应。

我们终于能够站在云端思考业务,而不必蹲在泥里写实现了。

Volley

  1. Volley的优势在于处理小文件的http请求;
  2. 在Volley中也是可以使用Okhttp作为传输层
  3. Volley在处理高分辨率的图像压缩上有很好的支持;
  4. NetworkImageView在GC的使用模式上更加保守,在请求清理上也更加积极,networkimageview仅仅依赖于强大的内存引用,并当一个新请求是来自ImageView或ImageView离开屏幕时 会清理掉所有的请求数据。

OKHttp

  1. OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和 HTTP 缓存。
  2. 默认情况下,OKHttp会自动处理常见的网络问题,像二次连接、SSL的握手问题。
  3. 如果你的应用程序中集成了OKHttp,Retrofit默认会使用OKHttp处理其他网络层请求。

Retrofit

  1. 性能最好,处理最快
  2. 使用REST API时简单方便
  3. 传输层默认就使用OkHttp
  4. 支持NIO,对IO流的操作更高效
  5. 拥有出色的API文档和社区支持
  6. 速度上比volley更快
  7. 默认使用Gson

@SmartDengg
Copy link
Author

SmartDengg commented Oct 19, 2016

接下来的这一段,我想跟大家分享一下,我在日常开发中,尤其是处理网络操作的时候,遇到的那些棘手问题,并且经常让我感到困惑的事情。

这一段应该插在httpurlconnection和httpclient的对比之后。文字有限,不再赘述,大部分都会来自于口述。

那么现在假设,我们已经有了封装性极好的http客户端,我们不关心它的内部实现,也就是说它可以由HttpUrlconnection实现,也可以由
httpclient实现,只不过这些细节,我们不再关注。假设它的健壮性非常好,合理的线程池调度,超时时间,甚至网络缓存等。

OK,我们有了一个配置如此强大的客户端后,那就开始使用吧,但是需要注意的是,我们还没有处理响应的回调接口,没关系,我们还有一个老朋友AsyncTask,他可以帮助我们来处理这些异步任务,而且我们自己封装的HTTP客户端在绝大多数工程中都是跟AsyncTask搭配使用的。不过AsyncTask并不属于本次演讲的内容,因此不做过多赘述,你只需要记住,它是一个在future-task的基础上,封装了Handler消息机制,通过线程池调度处理异步任务的类

比如,我要加载一些URL,以字符串的形式展示这些地址的返回结果。

public class DownloadWebPageTask extends AsyncTask<String, Voice, String> {

   ...

  @Override protected String doInBackground(String... urls) {

    String response = "";

    for (String url : urls) {

      try {
        InputStream inputStream = EffectiveHttpClient.retrieveService(url);
        BufferedReader buffered = new BufferedReader(new InputStreamReader(inputStream));

        String s = "";
        while ((s = buffered.readLine()) != null) {
          response += s;
        }
      } catch (IOException e) {
        //handle IOException

        //handle JSONException ?

        //handle inner Exception ?

        //handle Exception when nest call ?
      }
    }

    return response;
  }

  @Override protected void onPostExecute(String result) {
    mTextView.setText(result);
  }

  @Override protected void onCancelled(String result) {
    // handle cancel with result may be null
  }
}

假设EffectiveHttpClient就是我在上面提到的,自定义的高效HTTP客户端,再所一遍,我们不考虑它的内部细节实现。

那么回到这个例子中,看一看,哪些地方使我们不能接受的,或者说,这样会带来什么隐患。

我们先从代码的角度来考虑问题:

  1. 这种写法,迫使我们不得不添加丑陋的try-catch代码块,捕获异常并处理,这虽然看起来还不错。但是,当我们需要调用多个请求,后一个请求依赖于前一个请求的结果的时候,我们可能要分别处理这些异常,甚至还要校验返回的数据,判断它们的合法性,很难抽离归纳出一个健全的error handler。而且嵌套调用通常很难调试,ugly code!
  2. 虽然我们能够提供“key-value”的请求参数的拼接,但是我们很难搞定参数替换,为特殊的请求设定特殊的请求头信息,也是个麻烦事,很难动态添加和修改。没有人愿意写重复,枯燥的代码。
  3. 解析数据POJO?过滤?缓存?序列化数据至preference或者SQLite?这都是难题
  4. 重试机制和退避策略?
  5. 很难在Android开发中控制线程,进行优雅的线程切换。
  6. AsyncTask自身的设计缺陷,如果开启AsyncTask的ACT/Fragment已经被销毁了,也就是说执行到了其生命周期的onDestroy()。而AsyncTask运行,由于非静态内部类会持有外部类的引用,这就造成了ACT/Fragment的泄漏,如果继续在onPostExecute()中更新UI,可能引起NPE,或者其他崩溃隐患,而且我们很难真正取消一个正在运行的线程。
  7. CALL BACK HELL!

@SmartDengg
Copy link
Author

SmartDengg commented Oct 19, 2016

ALL RXJAVA WITH RETROFIT

优雅线程切换,统一的错误处理,回调天堂,统统给你。

比如说现在有一个需求,通过webservice获取城市列表 -> 根据返回的结果,通过城市ID获取电影列表 -> 根绝返回的结果,通过电影ID获取电影详情 -> 进行展示。

首先定义java接口

interface Service {

    //获取城市列表集合
    @GET("movie/citys") Observable<List<CityListResponse>> getCityList();

    //根据城市ID获取电影列表集合
    @GET("movie/movies.today") Observable<List<MovieListResponse>> getMovies(
        @Query("cityid") String cityId);

    //根据电影ID获取电影详情
    @GET("movie/query") Observable<MovieDetailResponse> getMovieDetail(
        @Query("movieid") String movieId);
  }

处理网络操作的逻辑如下:

private void retrieveMovies() {

    final Service service = ServiceGenerator.createService(Service.class);

    //1. 获取城市列表(网络操作)
    service.getCityList()
        .concatMap(new Func1<List<CityListResponse>, Observable<CityListResponse>>() {
          @Override
          public Observable<CityListResponse> call(List<CityListResponse> cityListResponses) {
            //2.依次取出"城市列表"集合中的元素
            return Observable.from(cityListResponses);
          }
        })
        .concatMap(new Func1<CityListResponse, Observable<List<MovieListResponse>>>() {
          @Override
          public Observable<List<MovieListResponse>> call(CityListResponse cityListResponse) {
            //3.根据每一个城市的id,获取该城市的电影列表(网络操作)
            return service.getMovies(cityListResponse.cityId);
          }
        })
        .concatMap(new Func1<List<MovieListResponse>, Observable<MovieListResponse>>() {
          @Override
          public Observable<MovieListResponse> call(List<MovieListResponse> movieListResponses) {
            //4.依次取出"电影列表"集合中的元素
            return Observable.from(movieListResponses);
          }
        })
        .concatMap(new Func1<MovieListResponse, Observable<MovieDetailResponse>>() {
          @Override
          public Observable<MovieDetailResponse> call(MovieListResponse movieListResponse) {
            //5.根据每一个电影的ID,获取该电影详情(网络操作)
            return service.getMovieDetail(movieListResponse.movieId);
          }
        })
        .subscribeOn(Schedulers.newThread())// 6.线程切换,上游的所有操作执行在工作线程
        .observeOn(AndroidSchedulers.mainThread())//7.线程切换,下游的所有将操作执行在UI线程
        .subscribe(new Subscriber<MovieDetailResponse>() {
          @Override public void onCompleted() {
          // finishCompletion
          }

          @Override public void onError(Throwable e) {
          //error handler
          }

          @Override public void onNext(MovieDetailResponse movieDetailResponse) {
            //8.根据电影ID所获取到的电影详情,该函数会被调用多次。
            textView.append(movieDetailResponse.movieName);
          }
        });
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment