开源库 - 优雅的网络和异步编程(Retrofit/Okhttp, RxAndroid/RxJava)

Posted on By Vivian Sun

前言

项目中网络功能不可少的网络通讯和异常编程。

如何进行优雅的网络和异步编程?

优雅的网络编程:

popular开源的Square公司 出品

  • Okhttp: An HTTP+HTTP/2 client for Android and Java applications.
  • Retrofit: Type-safe HTTP client for Android and Java

优雅的异步编程:

  • RxJava: Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
  • RxAndroid:RxJava bindings for Android

Github Code: WebDemo

优雅的网络编程

1. OkHttp

简介

OkHttp OKHttp是一款高效的HTTP客户端。

  • 支持SPDY, 可以合并多个到同一个主机的请求
  • 使用连接池技术减少请求的延迟(如果SPDY是可用的话),支持连接同一地址的链接共享同一个socket,通过连接池来减小响应延迟
  • 使用GZIP压缩减少传输的数据量
  • 缓存响应避免重复的网络请求

参考文档

官方使用简介

官方基本功能介绍wiki

基本功能完全解析这篇是中文介绍一些基本功能:

  • 一般的get/post请求
  • 基于Http的文件上传
  • 文件下载
  • 加载图片
  • 消息callback,支持请求回调,直接返回对象、对象集合
  • 支持session的保持

核心Code

/**
 * call okhttp sync method
 */
public UrlConfig getConfig() {
    try {
        Request request = new Request.Builder()
                .url(Const.URL_CONFIG)
                .build();

        Response response = mOkHttpClient.newCall(request).execute();
        if (!response.isSuccessful()) {
            Log.w(TAG, "getConfig failed " + response);
            return null;
        }

        UrlConfig config = mGson.fromJson(response.body().charStream(), UrlConfig.class);
        return config;
    } catch (Exception ex) {
        Log.w(TAG, "getConfig ex:", ex);
    }

    return null;
}

2. Retrofit

简介

简单来说就是一个HTTP请求工具,与volley功能类似,都是用于简化HTTP请求的库。但是相比其他工具,Retrofit的解耦更彻底:例如通过注解来配置请求参数,根据需求来选择使用不同的CallAdapter(请求适配器,如:RxJava,Java8, Guava)、Converter(反序列化工具,如json, protobuff, xml, moshi)等。

一般的小型项目okhttp基本够用。

Retrofit是在okhttp上进行封装。使用的是注解。对restful url具有很大的优势。

  • Retrofit和Java领域的ORM概念类似, ORM把结构化数据转换为Java对象,而Retrofit 把REST API返回的数据转化为Java对象方便操作
  • retrofit非常适合于restful url格式的请求,更多使用注解的方式提供功能。

核心Code

/**
 * get config sync through retrofit
 */
public UrlConfig getConfigThroughRetrofit() {
    try {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Const.URL_CONFIG_BASE)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        IWebSiteService service = retrofit.create(IWebSiteService.class);
        Call<UrlConfig> call = service.getUrlConfig();
        return call.execute().body();
    } catch (Exception ex) {
        Log.w(TAG, "getConfigThroughRetrofit ex:", ex);
    }

    return null;
}

/**
 * search sync through retrofit
 * filter :
 * "/1.1/threadview/search?key=" + key + "&start=" + startPosition + "&limit=" + SEARCH_LIMIT + "&sortby=dateline&order=desc";
 */
public ArrayList<SearchEntity> searchThroughRetrofit(String key) {
    try {
        final int pos = 0;
        final int limit = 5;

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Const.URL_SEARCH_BASE)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        IWebSiteService service = retrofit.create(IWebSiteService.class);
        Call<ResponseBody> call = service.search(key, 0, 5, "dateline", "desc");

        // parse array
        JsonParser parser = new JsonParser();
        JsonArray array = parser.parse(call.execute().body().string()).getAsJsonArray();
        ArrayList<SearchEntity> list = new ArrayList<SearchEntity>();
        if (array != null) {
            for (JsonElement ele : array) {
                SearchEntity entity = mGson.fromJson(ele, SearchEntity.class);
                list.add(entity);
            }
        }

        return list;
    } catch (Exception ex) {
        Log.w(TAG, "searchThroughRetrofit ex:", ex);
    }

    return null;
}


public interface IWebSiteService {
    @GET("search")
    Call<ResponseBody> search(@Query("key") String key,
                              @Query("start") int start,
                              @Query("limit") int limit,
                              @Query("sortby") String sortby, @Query("order") String order);

    @GET(Const.URL_CONFIG_EXTENDS)
    Call<UrlConfig> getUrlConfig();
}         

参考文档

Retrofit2使用简介

Retrofit2 官方Doc

注意事项

Gson解析时,要注意对JsonArray 的解析

示例

        Response response = mOkHttpClient.newCall(request).execute();
        if (!response.isSuccessful()) {
            Log.w(TAG, "search ex:", new IOException("Unexpected code " + response));
        }

        // parse array
        JsonParser parser = new JsonParser();
        JsonArray array = parser.parse(response.body().string()).getAsJsonArray();
        ArrayList<SearchEntity> list = new ArrayList<SearchEntity>();
        if (array != null) {
            for (JsonElement ele : array) {
                SearchEntity entity = mGson.fromJson(ele, SearchEntity.class);
                list.add(entity);
            }
        }

        return list;

优雅的异步编程 RxJava & RxAndroid

简介

ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的API。

它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

  • 两个主要的类 : Observable(观察者) 和 Subscriber(订阅者)
  • Observable是发出数据流或者事件;Subscriber对发出的items进行处理
  • 事件可以是任何东西:触摸事件,web接口调用返回的数据等

RxAndroid是RxJava在Android上的一个扩展和Retorfit、OkHttp组合起来使用,效果惊人的简洁。似乎可以完全替代eventBus和OTTO(事件总线框架浅析)。

核心Code

public void okhttpConfigClick(View view) {
    Disposable config = Observable.fromCallable(() -> WebsiteEngine.getInstance().getConfig())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(urlConfig -> {
                        final String result = WebsiteEngine.getGson().toJson(urlConfig.Debug).toString();
                        Log.d(TAG, "" + result);
                        mTVShow.setText(result);
                    },
                    error -> Log.w(TAG, "okhttpConfigClick ex:", error),
                    () -> Log.d(TAG, "okhttpConfigClick complete"));
    compositeDisposable.add(config);
}

public void okhttpSearchClick(View view) {
    final String search_key = "zenfone3";
    Disposable search = Single.fromCallable(() -> WebsiteEngine.getInstance().search(search_key))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(list -> {
                        final String result = WebsiteEngine.getGson().toJson(list).toString();
                        Log.d(TAG, "" + result);
                        mTVShow.setText(result);
                    },
                    error -> Log.w(TAG, "okhttpSearchClick ex:", error));
    compositeDisposable.add(search);
}

参考文档

简单的使用可以参看

RxJava 入门

RxJava 使用场景举例

更深入的使用:

RxJava 和 RxAndroid系列介绍

深入浅出RxJava系列

内存泄漏

使用 RxJava 不会魔术般的缓解内存泄露危机。为了防止可能的内存泄露,在Activity或Fragment的onDestroy 里,释放资源。

rxjava2 释放如下:

private Disposable mConfigDisposable;


@Override
protected void onDestroy() {
    super.onDestroy();

    if (mConfigDisposable != null && !mConfigDisposable.isDisposed()) {
        mConfigDisposable.dispose();
    }
}

这样将会停止通知,并允许垃圾回收机制释放对象,防止任何 RxJava 造成内存泄露。

如果你正在处理多个,可以用CompositeDisposable在同一时间进行释放

private CompositeDisposable compositeDisposable;

@Override
protected void onDestroy() {
    super.onDestroy();

    if (compositeDisposable != null && !compositeDisposable.isDisposed()) {
        compositeDisposable.dispose();
    }
}

Reference

Android OkHttp完全解析

Retrofit与okhttp之间的关系

如何用RXJava替代EventBus进行事件的分发

如何用RXjava替代EventBus进行事件的分发 GitHub上的Demo

给 Android 开发者的 RxJava 详解