この記事は主に開発者向けの内容です。

この記事は2014年1月に作成したもので、最新の情報ではありません。特にTwitPaneおよびTwitter4Jは2014年3月15日現在、SPDYだけでなくHTTP/2.0にも対応しています。


Twitter4J を次世代 HTTP の SPDY に対応させてみました。

拙作の Twitter クライアント TwitPane では2013年12月リリースの Ver.3.3.1 より SPDY に対応しました。

本ページでは TwitPane で利用してみた結果も踏まえて、Twitter4J の SPDY 対応方法についてまとめています(作者のブログ で随時書いていた内容のまとめ+αになります)。

Twitter4J-spdy-support

導入方法

Twitter4J を導入済みのアプリに対して、下記2つの jar をクラスパスに(Androidならlibsに)追加してください。

あとは twitter4j が自動的に(この jar に含まれる) "twitter4j.internal.http.alternative.HttpClientImpl" を見つけて使ってくれます。

対応環境

詳細な使い方とTips

SPDY通信の確認方法

Twitter4J の SPDY 拡張(twitter4j-spdy-support)を利用しても本当に SPDY 通信してるか不安ですよね。

一応下記のようなコードで Twitter オブジェクト(twitter) から SPDY のコネクションプール数を取得できます(リフレクションを利用しているので Twitter4J Ver.3.0.5 以降の版では動作しなくなる可能性があります)。

String message = "SPDY : ";
if (!twitter4j.internal.http.alternative.HttpClientImpl.sPreferSpdy) {
    message += "Disabled";
} else {
    message += "Enabled(";
    try {
        final Class<?> clazz = Class.forName("twitter4j.TwitterBaseImpl");
        final Field f1 = clazz.getDeclaredField("http");
        f1.setAccessible(true);

        final HttpClientWrapper wrapper = (HttpClientWrapper) f1.get(twitter);
        final Field f2 = HttpClientWrapper.class.getDeclaredField("http");
        f2.setAccessible(true);

        final twitter4j.internal.http.alternative.HttpClientImpl http =
                (twitter4j.internal.http.alternative.HttpClientImpl) f2.get(wrapper);
        final Field f3 = http.getClass().getDeclaredField("client");
        f3.setAccessible(true);

        final OkHttpClient client = (OkHttpClient) f3.get(http);

        if (client != null) {
            final ConnectionPool p = client.getConnectionPool();
            message += "SPDY[" + p.getSpdyConnectionCount() + "], ";
            message += "HTTP[" + p.getHttpConnectionCount() + "])";
        } else {
            message += "no connections yet)";
        }

        final String lastRequestTransport = http.getLastRequestTransport();
        if (lastRequestTransport != null) {
            message += "\nOkHttp-Selected-Transport:[" + http.getLastRequestTransport() + "]";
        }

    } catch (Exception e) {
        Log.e(TAG, e.getMessage(), e);
    }
}
Log.d(TAG, message);

SPDYをオフにする設定

twitter4j.internal.http.alternative.HttpClientImpl.sPreferSpdyfalse に設定することで SPDY 通信を強制的にオフにすることが出来ます。

OkHttp 単体を利用する場合の SPDY 通信の確認方法

Twitter4J ではなく OkHttp 自体を使う場合は HttpURLConnection からレスポンスヘッダーを見ると下記のようなカスタムヘッダーが取れます。

OkHttp-Received-Millis:[1388254484917]
OkHttp-Response-Source:[NETWORK 200]
OkHttp-Selected-Transport:[http/1.1]
OkHttp-Sent-Millis:[1388254484817]

SPDY 通信をしている場合は OkHttp-Selected-Transportspdy/3 などになります。

Java(非Android)で使うときの設定

Java/Windows環境ではJVMにnpn-bootが必要です。

Jetty/Feature/NPN - Eclipsepedia からダウンロードできます。

java -Xbootclasspath/p:<path_to_npn_boot_jar> ...

簡易ベンチマーク(Androidの場合)

並列実行パターン

showStatus を 100ms ずつずらして11回リクエストした結果です。

GalaxyNexus SC-04D/Android 4.2:

環境試行1回目(平均)2回目
no SPDY, 3G2095ms1980ms
SPDY, 3G1088ms(48%高速)1398ms(29%高速)
no SPDY, WiFi654ms606ms
SPDY, WiFi527ms(19%高速)505ms(17%高速)

測定に使ったコード: https://gist.github.com/takke/8143276

シーケンシャル実行パターン

環境平均所要時間
no SPDY, 3G1409ms
SPDY, 3G780ms(45%高速)
no SPDY, WiFi689ms
SPDY, WiFi452ms(34%高速)

Android版Twitter4Jの注意

Android/Dalvik 環境で Twitter4J を利用する場合、とあるバグ の回避のために HTTP/1.1 の KeepAlive が無効になります。そのため、通信のたびに再接続するというオーバーヘッドがあります。

SPDY 版ではこのロスを回避できるため、比較的大きな改善になっています。

まとめ

TwitterのAPIはいずれも重く、最短でも300ms~500ms程度はかかるので、SPDY による高速性は相対的に低く感じられます。

ただ、iOS向けのTwitter公式アプリでは SPDY がデフォルトで有効になっており、通信遅延を最大30%削減できるとされています。

TwitPaneのようにタブベースで複数のリクエストを同時に投げるシチュエーションでは、1本の TCP セッションを共有できるSPDYの恩恵は多少なりとも得られると考えています。