Android
Twitter

AndroidアプリでNetworkOnMainThreadExceptionを避けつつTwitter4Jを使用したアプリ連携を行う #Twitter4J

More than 5 years have passed since last update.

MarkdownとQiitaの練習として投稿します。この内容は私のブログ記事と同等の内容です。[追記]2か所の更新は大変なので、Qiitaのみ更新としました。


Androidアプリ作成でTwitterを使用する際に欠かせないTwitter4Jですが、アプリの連携(認証認可)処理を適当に処理を書いていくとNetworkOnMainThreadExceptionが発生します。onCreateなどのメインスレッドでHttpURLConnectionなどの通信をする処理がある場合に発生する例外です。

今日はこの例外を避けつつ、アプリ連携を実現する方法の一つをご紹介します。


アプリ連携の方法

今回は、アプリ外のブラウザでPINを入力して連携する方法で作ってみました。他にはWebViewを用いてアプリ内のブラウザで処理する方法(WebViewの脆弱性に注意)や、コールバックURLを用いて処理する方法があります。

PIN
コールバックURL

外部ブラウザ
今回はここ
(intent-filterを使用)

アプリ内のブラウザ
(省略)
(WebViewでURLを監視等)

[追記]マルチユーザー環境でブラウザ類をすべて制限すると、外部ブラウザでの連携ができなくなりますね…


連携のためのコード8行

連携のための一連の処理を見てみます。

// 準備

Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(consumerKey, consumerSecret);
twitter.setOAuthAccessToken(null);
RequestToken requestToken = twitter.getOAuthRequestToken(); // ここで通信
// このURLでブラウザで認証
String url = requestToken.getAuthorizationURL();

ここでユーザーが認証してPINコードを得ます。

// PIN入力

AccessToken accessToken = twitter.getOAuthAccessToken(pin); // ここでも通信
// トークンの取得。これらを保管しておく。
String token = accessToken.getToken();
String tokenSecret = accessToken.getTokenSecret();

これらのコード中に通信が発生する箇所が2つあります。これらをLoaderLoaderManager.LoaderCallbacksを使用して別のスレッドにしてみました。

[追記]Android Support Libraryを使用するようにして、Android2.x系にも対応しました。


解決のためのコード例


リクエストトークンを取得する

まず、リクエストトークンを取得するところ(Twitter#getOAuthRequestToken())を切り出すために、AsyncTaskLoaderを継承したクラスを作成します。バインドするのはRequestTokenです。コンストラクタでTwitterを渡し、loadInBackground()でリクエストトークン取得を実行します。実行の戻り値をそのまま返します。

public class TwitterOAuthRequestTokenLoader extends AsyncTaskLoader<RequestToken> {

private Twitter mTwitter;

public TwitterOAuthRequestTokenLoader(Context context, Twitter twitter) {
super(context);
mTwitter = twitter;
}

@Override
public RequestToken loadInBackground() {
RequestToken requestToken = null;
try {
requestToken = mTwitter.getOAuthRequestToken();
} catch (TwitterException e) {
requestToken = null;
}
return requestToken;
}
}

上のローダークラスを実行し、結果を処理するために、今度はLoaderCallbacksを実装したコールバッククラスを作成します。こちらもコンストラクタでAPIキーをセットしたTwitterを渡し、onCreateLoader()でそのままローダークラスのインスタンス生成時に渡します。onLoadFinished()の第2引数のRequestTokenが処理結果なので、RequestToken#getAuthorizationURL()でTwitter認証のURLを取得し、ブラウザへIntentで渡します。ユーザーはここで認証し、PINコードを得ます。

public class TwitterOAuthRequestTokenCallbacks implements LoaderCallbacks<RequestToken> {

private Context mContext;
private Twitter mTwitter;

public TwitterOAuthRequestTokenCallbacks(Context context, Twitter twitter) {
mContext = context;
mTwitter = twitter;
}

@Override
public Loader<RequestToken> onCreateLoader(int id, Bundle args) {
Loader<RequestToken> loader = new TwitterOAuthRequestTokenLoader(mContext, mTwitter);
loader.startLoading();
return loader;
}

@Override
public void onLoadFinished(Loader<RequestToken> arg0, RequestToken requestToken) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(requestToken.getAuthorizationURL()));
mContext.startActivity(intent);
}
}


アクセストークンを取得する

ブラウザが表示され、ユーザーが認証をすると、PINが表示されます。

PINからアクセストークンを取得するところ(Twitter#getOAuthAccessToken())についても上記のリクエストトークンを取得する場合と同様です。


使う

TwitterはConsumer keyを設定して用意しておきます。

mTwitter = new TwitterFactory().getInstance();

mTwitter.setOAuthConsumer(KEY), KEY_SECRET);
mTwitter.setOAuthAccessToken(null);

あとはコールバッククラスのインスタンスを生成し、

FragmentActivity#getLoaderManager()LoaderManagerを取得し、LoaderManager#initLoader()にコールバッククラスを指定するだけです。

LoaderManager.LoaderCallbacks<RequestToken> requestTokenCallbacks = new TwitterOAuthRequestTokenCallbacks(this, mTwitter);

getSupportLoaderManager().initLoader(0, null, requestTokenCallbacks);

[追記]上記のように2.x系の互換性のためにAndroid Support Library内のLoaderを使用するように変更する場合は、LoaderManagerの不一致があるので、Activityは継承先をFragmentActivityに変更する必要があります。


サンプルプロジェクト

この方法でアプリ連携を行うアプリのプロジェクトをGitHubに置きました。