Picasso、Otto、Retrofitのソースコードを読んだ

最近はAndroidアプリを作っているのですが、うどん職人以降まともにAndroidアプリを作っていなかったので勉強のためにライブラリのソースコードを読みました。この記事は各ライブラリのファイルを読んで知ったことをまとめています。

ソースコードを読んだライブラリはSquare製のPicasso、Otto、Retrofitの3つです。

ライブラリについて

Picasso

画像をダウンロードしたりキャッシングしてくれる強力なライブラリです。
次のような一文だけでネットワークの画像を取得して表示してくれます。

// 画像を読み込んで画像を表示する
Picasso.with(context).load(url).into(imageView);

Otto

アプリケーションの異なる部分を分離しつつ通信を行うためのイベントバスに特化したライブラリです。
次のように書くことでイベントを発行したり、ハンドリングすることができます。

// イベントの発行
BusHolder.get().post("イベントを発行しました");
// イベントのハンドリング
@Override
protected void onResume() {
    super.onResume();
    
    BusHolder.register(this);
}

@Subscribe
public void subscribe(String str) {
    Log.d(TAG, str);
}

Retrofit

JavaでHTTP APIのインターフェースを書くことでその実装を生成してくれるライブラリです。
次のようにインターフェースを書くだけで実装をしてくれるのでとても分かりやすいです。

// APIインターフェースの定義
public interface APIService {
    @GET("/group/{id}/users")
    Call<List<User>> groupList(@Path("id") int groupId);
}
// APIの実装
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://example.com")
    .build();

APIService service = retrofit.create(APIService.class);
Call<List<User>> list = service.groupList(1);

1系と2系があるのですが、これから主流になってくる2系βのソースコードを読みました。

それではざっくりですが、ここから1つずつライブラリを見ていきます。
3つのライブラリとも基本的なフォルダやファイルの構成は同じなので重複する点は飛ばしています。

ライブラリのソースコードを読む

Picasso

deploy_website.sh
  • 最新のバージョンをデプロイしたい時はこのスクリプトを実行するだけで簡単にできるようになっている
    • 最新のjavadocの取得や日付け入りのcommit、Githubページの更新までやってくれる
    • websiteもJavaScriptmavenからバージョンをとってくるようになっていてすごく良い
PollexorRequestTransformer.java
  • Thumbor(画像を変換してくれるサービス)で使うためのプラグイン
  • 次のように書くことで画像をそのサービスに渡して変換してくれる
// Thumborで画像を変換する
RequestTransformer transformer =
    new PollexorRequestTransformer("http://example.com", "secretpassword");

Picasso p = new Picasso.Builder(context)
                .setRequestTransformer(transformer)
                .build();
RequestHandler.java
  • 画像の読み込みの基本的なインターフェースのクラス
  • このクラスを実装することでネットワークやAssetから読み込むためのクラスを作ることができる
  • 次はこのクラスを実装した画像を読み込むためのクラスの一覧です
    • ResourceRequestHandler.java: リソースファイルを読み込む
    • AssetRequestHandler.java: アセットファイルを読み込む
    • ContactsPhotoRequestHandler.java: 連絡帳にある写真を読み込む
    • ContentStreamRequestHandler.java: スキーマがcontentの画像を読み込む
    • FileRequestHandler.java: スキーマがfileの画像を読み込む
    • MediaStoreRequestHandler.java: スキーマがcontentかつAuthorityがmediaの画像を読み込む
    • NetworkRequestHandler.java: ネットワーク上からファイルを読み込むためのクラス
BitmapHunter.java
  • キャッシュなどのポリシーに沿ってBitmapの読み込みを制御するクラス
Action.java
  • どのようなことをするかのアクションの情報を持つ抽象クラス
  • 次のようにアクションの内容によってクラスが分かれている
    • FetchAction.java: 画像をコールバックに渡す
    • GetAction.java: 画像をキャッシングしない
    • ImageViewAction.java: 画像データをImageViewに設定する
    • RemoteViewsAction.java: 画像をRemoteView経由で通知やウィジェットに表示する
Dispatcher.java
  • アクションの状態に応じて次のクラスに渡す仲介を行っているクラス
Cache.java
  • キャッシュの基本的なインターフェースを持つクラス
  • このクラスを実装したメモリでキャッシュを行う`LruCache.java`がある
    • キャッシュの上限を越えた時は過去のモノから削除していく
Picasso.java
  • Picassoを使う時に主に使うクラス
  • Bitmapの読み込みの設定やデバッグの設定などPicassoの基本設定を行うことができる
  • メモリキャッシュは利用可能な15%、ディスクキャッシュは5MB以上50MB以下でディクス容量の2%を使う
  • 画像を読み込むためのスレッドは3つまで生成される
Downloader.java
  • 画像をダウンロードするための基本的なインターフェースのクラス
  • 次のようにどの通信クライアントを使うかで実装がそれぞれ用意してある
    • OkHttpDownloader.java: OkHttpClientを利用する
    • UrlConnectionDownloader.java: HttpURLConnectionを利用する
  • どちらの通信クライアントを使うかはOkHttpがインストールされているかで判定している
// 通信クライアントを取得する
static Downloader createDefaultDownloader(Context context) {
  if (SDK_INT >= GINGERBREAD) {
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpLoaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
  }
  return new UrlConnectionDownloader(context);
}

Otto

AnnotatedHandlerFinder.java
// クラスに定義されたメソッド一覧を取得する
for (Method method : class.getDeclaredMethods) {
//メソッドにアノテーションがついているかの判定をする
method.isAnnotationPresent(Subscribe.class)
  • またメソッドに定義されているパラメータは次のように取得できる

// メソッドに定義されたパラメータを取得する
method.getParameterTypes();
Bus.java
  • イベントを発行したりハンドラーの登録をするためにOttoで主に使うクラス
  • イベントのハンドリングがない時はDeadEventクラスでラップしてイベントが再発行される
    • デバッグの時にはこのDeadEventをハンドリングしてハンドリング漏れを見つける
  • ハンドリングするメソッドを探す時は次のように親のクラスも辿っていく
// 親のクラスを辿っていく方法
Set<Class<?>> classes = new HashSet<Class<?>>();

parents.add(startClass);

while (!parents.isEmpty()) {
  Class<?> clazz = parents.remove(0);
  classes.add(clazz);

  Class<?> parent = clazz.getSuperclass();
  if (parent != null) {
    parents.add(parent);
  }
}
return classes;
  • clazzは予約語のclassを避けるためにsを逆にした変数名
Subscribe.java

// アノーテションを定義する
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
}

Retrofit

http/*.java
package-info.java
  • パッケージにJavadocのコメントを書ける
Call.java
  • 同期・非同期通信のメソッドを持つリクエストの基本的なインターフェースクラス
  • OkHttpを使う実装であるOkHttpCallクラスがあるので基本的にこのクラスを使う
OkHttpCall.java
  • 通信クライアントにOkHttpを用いてリクエストを行うクラス
  • レスポンスコードが200より小さいか300以上の時はエラーのレスポンスになる
  • NoContentResponseBodyクラスを使って先にレスポンスコードだけを判定している
CallAdapter.java
  • Callクラスの実行を行うインターフェースのクラス
  • プラットフォームによってどのスレッドでリクエストのコールバックを実行するかが変わる
Platform.java
  • プラットフォームによって変わる処理を書くためのクラス
  • 次のように特定のクラスがあるかどうかでプラットフォームの判定をしている
// プラットフォームの判定をする
private static Platform findPlatform() {
  try {
    Class.forName("android.os.Build");
    if (Build.VERSION.SDK_INT != 0) {
      return new Android();
    }
  } catch (ClassNotFoundException ignored) {
  }
  try {
    Class.forName("java.util.Optional");
    return new Java8();
  } catch (ClassNotFoundException ignored) {
  }
  return new Platform();
}
Converter.java
  • レスポンスをオブジェクトに変換するコンバータの基本的なインターフェースクラス
  • 次のようなよく使われる形式のコンバータの実装はすでに用意されている
Retrofit.java
  • APIインターフェースの定義を受け取って設定を行うRetrofitで主に使うクラス
  • インターフェースの定義からメソッド一覧を取得してハンドラーを生成する
番外編
  • OkHttpを使うことで指定のレスポンスを返すモックサーバを次のように起動できる
// モックサーバを起動してみよう
MockWebServer server = new MockWebServer();
server.start();
server.enqueue(new MockResponse()
      .setResponseCode(404)
      .setBody("{\"message\":\"Unable to locate resource\"}"));

ライブラリを読んで

3つのライブラリのソースコードを読みました。なぜこの3つのライブラリかというと既に作っているアプリで採用していて、最近のアプリで採用している事例が多いなと感じたからです。しかし、ソースコードを読むとライブラリは魔法ではなく努力の結晶であることを知ったり、ブラックボックスだった部分を知ることができライブラリの偉大さを知ることができました。また、今まで知らなかったAPIがあったり、拡張性が高いコードを書くことで複雑な処理を簡潔にまとめていたりして凄さを身に感じました。

こういう立派なライブラリを目指しつつ、地道にAndroidについてハマっていきたいと思います。
今年中に1つでもAndroidに関するライブラリを作ってみます!