通信処理を実装する
通信は下図に示すように、okhttp、Retrofit、google-gson、RxJavaを連携させて利用します。
build.gradleは下記のように記載します。adapter-rxjava2はRetrofit2のレスポンスをRxJava2に連携するためのライブラリ、logging-interceptorはokhttpの通信ログを出力するためのライブラリです。
dependencies { ...略... compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' compile 'com.squareup.okhttp3:logging-interceptor:3.9.0' ...略... }
対応するProGuard設定は以下のとおりです。
-dontwarn javax.annotation.** # okhttp -dontwarn okhttp3.** -dontwarn okio.** # Retrofit -dontnote retrofit2.Platform -dontwarn retrofit2.Platform$Java8 -keepattributes Signature -keepattributes Exceptions
APIインターフェースを定義
今回はユーザーリスト取得APIとしてrandomuser.meを利用させてもらいます。randomuser.meは、ランダムにダミーのユーザー情報を生成するサービスです。
APIインターフェースはRetrofitライブラリが提供する各種のアノテーションを用いて定義します。@GETアノテーションにリクエストパスを指定し、@Queryパラメータでリクエストパラメータを指定します。そして、レスポンスはio.reactivex.Observableを指定することでRxJavaを用いたハンドリングができるようにします。
/** * Api Interface */ public interface Api { /** * ユーザー一覧を取得します。 * * @param page ページ番号 * @return {@link RandomUserResponse} */ @GET("api?results=20&seed=hoge&nat=us") Observable<RandomUserResponse> list(@Query("page") final Integer page); }
上記の例は、https://randomuser.me/apiに対して、取得件数を20(result=20)、リスト固定(seed=hoge)、英語圏のユーザー(nat=us)を固定条件、ページ番号(page)を可変条件として付与し、GETリクエストを送信する定義です。
HTTPクライアントビルダーを用意
HTTPクライアントはHTTP2.0にも対応しているokhttpライブラリを利用します。アプリ内におけるHTTP通信の基本設定を統一するため、Applicationクラスにokhttpクライアントビルダー生成処理を実装するのが良いと思います。
public class ChatApplication extends Application { ... /** * {@link OkHttpClient.Builder}を生成します。 * * @return {@link OkHttpClient.Builder} */ public static OkHttpClient.Builder httpClientBuilder() { // HTTP通信ログをTimber経由で出力 final HttpLoggingInterceptor logging = new HttpLoggingInterceptor(Timber::d); logging.setLevel(HttpLoggingInterceptor.Level.BASIC); return new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .addInterceptor(logging); } ... }
Retrofitビルダーを用意
APIアクセスを行うためのRetrofitビルダー生成処理も同様にApplicationクラスに実装しておきます。
public class ChatApplication extends Application { // AsyncTaskの実装を参考にスレッドプールサイズを定義 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private Executor mThreadPoolExecutor; /** * Get thread pool executor. * * @return {@link Executor} */ public Executor getThreadPoolExecutor() { if (mThreadPoolExecutor == null) { mThreadPoolExecutor = Executors.newFixedThreadPool(CORE_POOL_SIZE); } return mThreadPoolExecutor; } ... /** * {@link Retrofit.Builder}を生成します。 * * @return {@link Retrofit.Builder} */ public static Retrofit.Builder retrofitBuilder() { final Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss").create(); return new Retrofit.Builder() .callbackExecutor(getInstance().getThreadPoolExecutor()) // レスポンスをgoogle-gsonでパース .addConverterFactory(GsonConverterFactory.create(gson)) // RetrofitとRxJavaを連携 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); } ... }
APIアクセサの実装
前述のAPIインターフェース定義、HTTPクライアントビルダー、Retrofitビルダーを用いてAPIアクセスの準備を行うためのクラスを実装します。ここをシングルトン実装にしておくことで、APIエンドポイントごとにHTTPクライアントインスタンスを使いまわすようにしています。okhttp3はインスタンスごとにコネクションプールを持つため、インスタンスを生成し過ぎるとメモリ消費に悪影響を与えてしまいます。
/** * randomuser.me APIアクセサクラス */ public class RandomUserApi { private static final String ORIGIN = "https://randomuser.me"; private static RandomUserApi instance; private Api mInterface; /** * randomuser.me Apiインターフェースを取得します。 * * @return {@link Api} */ public static Api get() { synchronized (RandomUserApi.class) { if (instance == null) { instance = new RandomUserApi(); } } return instance.mInterface; } /** * コンストラクタ */ private RandomUserApi() { mInterface = ChatApplication.retrofitBuilder() .client(ChatApplication.httpClientBuilder().build()) .baseUrl(ORIGIN) .build() .create(Api.class); } ... }