LoginSignup
8
3

More than 5 years have passed since last update.

Retrofitの内部処理について調べてみました

Posted at

概要

少し前に、Retrofit + Gsonを使ってWeb API(はてなブックマークエントリー情報取得API)を実行する方法に関する記事を作成しました。

インタフェースを作成するだけでWeb APIアクセス処理が可能とする実装方法について、githubからRetrofitのソースコードを取得して調べました。

その結果、reflection APIのProxyを使っていることがわかりました。

処理が冗長に/見通しが悪くなるため、アプリの実装でProxyを使うケースはあまりないかもしれませんが、面白い使い方だと思いました。

Retrofitとは

Retrofitは、Android(Java)アプリで、Web API(jsonデータ)アクセスを簡単に実現するためのオープンソースのライブラリです。
jsonデータ変換ライブラリ(gson, jacksonなど)を組み合わせて利用します。

RetrofitによるWeb API実行処理

Web API実行処理は、以下の手順で実装します。

API用インタフェースを実装

まず、API用インタフェースを実装します。
以下のインタフェースは、はてなブックマークエントリー情報取得APIの例です。

// HatenaApiInterface.java
public interface HatenaApiInterface {
    String END_POINT = "http://b.hatena.ne.jp";
    String TARGET_URL = "http://b.hatena.ne.jp/ctop/it";

    // はてなブックマークエントリー情報取得API
    // http://developer.hatena.ne.jp/ja/documents/bookmark/apis/getinfo
    @GET("/entry/jsonlite/")
    Call<BookmarkEntry> getBookmarkEntry(@Query("url") String target);

}

以下のURLは、テクノロジーに関するはてなブックマークエントリー情報取得API(GET)のURLです。
ここで、urlはカテゴリを示す引数です。
はてなブックマークエントリー情報取得APIの仕様は、ここを参照してください。

http://b.hatena.ne.jp/entry/jsonlite/?url=http%3A%2F%2Fb.hatena.ne.jp%2Fctop%2Fit

インタフェースにはこのURLを生成できる情報を記述します。

エンドポイント

エンドポイントは、Web APIのルートURLです。
Retrofitのインスタンスを生成するときに使います。

メソッド

メソッド名は任意です。
アノテーション(@GET)は、GETメソッドを使うこと、Web APIの相対パスが/entry/jsonlite/であることを示しています。また、アノテーション(@Query)はクエリストリングとして?以下に追加されます。

Web APIアクセス処理の実装

Web APIアクセスは以下の手順で行います。
(Main)Activityなどに実装します。

  1. Retrofitインスタンス作成
  2. Web APIアクセス用インタフェース作成
  3. Web API実行
    // Retrofitインスタンス作成
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(HatenaApiInterface.END_POINT)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    // Web APIアクセス用インタフェース作成
    mApiInterface = retrofit.create(HatenaApiInterface.class);

    // Web API実行用インスタンス(インタフェース)を取得します。
    Call<BookmarkEntry> call = mApiInterface.getBookmarkEntry(targetUrl);

    // Web APIを実行します
    // コールバック呼び出しで処理結果が通知されます 
    call.enqueue(new Callback<BookmarkEntry>() {
        @Override
        public void onResponse(Call<BookmarkEntry> call, Response<BookmarkEntry> response) {
            // Web APIアクセスが成功した場合の処理を記述します
            }

        @Override
        public void onFailure(Call<BookmarkEntry> call, Throwable t) {
            // Web APIアクセスが失敗した場合の処理を記述します
        }
    });

内部処理の解説

以下は、Retrofit.createのソースです。

reflection APIのProxyを使って、InvocationHandlerを作成して返しています。
取得したインタフェース(インスタンス)のgetBookmarkEntryを実行すると、invokeメソッドが実行されます。引数のMethodには、getBookmarkEntryというメソッド名、引数argsには、String targetUrlが格納されます。

このメソッドには関係づけられているWeb APIアクセス情報を、loadServiceMethodで取得します。さらにokHttp呼び出しクラスを追加して、Adapterクラスを返します。これを、Call<BookmarkEntry> call型の変数にセットします

つぎに、enquequeメソッドを実行するとWeb API呼び出しを実行します。結果は、引数のコールバックで取得出来ます。

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

Proxyの使い方(サンプル)

reflection APIのProxyを使い方を説明します。
百聞は一実行にしかず。
TestInterfaceとTestProxyを作成し、TestProxyを実行してみてください。
上記の説明がよくわかると思います。

Proxyの使い方は、こちらを参考にさせていただきました。

$ java TestProxy
$ cat TestInterface.java
public interface TestInterface {
    public void doSomething();
}

$ cat TestProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TestProxy {
    private Object proxy;
    private TestProxy(Class<TestInterface> clazz) {
        this.proxy = Proxy.newProxyInstance(clazz.getClassLoader(),
                new Class[] { clazz },
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // メソッド情報を出力する
                        System.out.println("method: " + method);
                        return null;
                    }
        });
    }

    public static TestInterface createProxy(Class<TestInterface> clazz) {
        TestProxy obj = new TestProxy(clazz);
        return clazz.cast(obj.proxy);
    }

    public static void main(String[] args) {
        TestInterface someInterface = TestProxy.createProxy(TestInterface.class);
        someInterface.doSomething();
    }
}

参考

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3