AndroidでTwitter4Jを使うときのノウハウまとめ(後半その2)

先に後編その1をよんでね!

UserStreamを使用する

先に言っておくと、Twitterのガイドラインでは「携帯電話で使うならwifiかなんかに繋がってる時以外はやんないほうがいいよ。」と書かれているんですが、じゃあREST APIの使用回数制限もっと増やせやって話でして、基本的には中指を突き立てるスタンスでいきます。

もしTwitter社の言うことを真に受けてちゃんとwifiに繋がってるかどうかを確認してから接続したい場合はこの辺をご覧ください。

さて、Twitter4JからUserStreamに接続する方法ですが、コード例を読むだけでも大体わかるはずです。まぁそれだと解説にならないんですが…。

Twitter4JのUserStream処理は起動や停止を担うTwitterStreamと実際にUserStreamのイベントを受け取って処理するUserStreamListenerに分離されています。

なので、とりあえずUserStreamListenerを作成するところから始めます。そのままUserStreamListenerを実装したクラスを作るとOverride地獄なので、UserStreamAdapterを継承して必要なものだけOverrideするのがいいと思います…ってかこんなクラスがあったんですね。私も今始めて知りました。

public final class MyUserStreamListener extends UserStreamAdapter {
    
    private Context mContext;
    
    public MyUserStreamListener (Context context) {
        mContext = context;
    }
    
    @Override
    public void onStatus(Status status) {
        Toast.makeText(mContext, status.getText(), Toast.LENGTH_SHORT).show();
    }
    
}

で、これをTwitterStreamにセットします。TwitterStreamを手に入れるためには当然ながらOAuth認証を済ませていなければなりません。先に前回作成したヘルパークラスにメソッドを追加しましょう。

/**
 * 各種Twitterに関するインスタンスを取得するヘルパークラス
 */
public class TwitterInstance {

    public static String CONSUMER_KEY = "";
    public static String CONSUMER_SECRET = "";

    /**
     * Twitterのインスタンス
     * @param context
     * @return
     */
    public static Twitter getTwitter(Context context) {
        Account account = AccountDao.getCurrentAccount(context);
        ConfigurationBuilder conf  = new ConfigurationBuilder()
                                        .setOAuthAccessTokenSecret(account.getAccessSecret())
                                        .setOAuthConsumerKey(CONSUMER_KEY)
                                        .setOAuthConsumerSecret(CONSUMER_SECRET)
                                        .setOAuthAccessToken(account.getAccessToken())
                                        .setUseSSL(true);
        return new TwitterFactory(conf.build()).getInstance();
    }
    
    /**
     * TwitterStreamのインスタンス
     * @param context
     * @return
     */
    public static TwitterStream getTwitterStream(Context context) {
        Account account = AccountDao.getCurrentAccount(context);
        ConfigurationBuilder conf  = new ConfigurationBuilder()
                                        .setOAuthAccessTokenSecret(account.getAccessSecret())
                                        .setOAuthConsumerKey(CONSUMER_KEY)
                                        .setOAuthConsumerSecret(CONSUMER_SECRET)
                                        .setOAuthAccessToken(account.getAccessToken());
        return new TwitterStreamFactory(conf.build()).getInstance();
    }
}

じゃあ改めてセットしてみます。

public class TestStreamActivity extends Activity {

    private TwitterStream mStream;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stream);

        mStream = TwitterInstance.getTwitterStream(getApplicationContext());
        mStream.addListener(new MyUserStreamListener(getApplicationContext()));

        // UserStream開始
        mStream.user();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mStream.cleanUp();
    }
}

どうでしょう。ちゃんとToastが表示されるでしょうか。絶対表示されないと思います。

理由は簡単で、UserStreamのイベントを受け取っているのはUIスレッドではない別のスレッドだからです。

前回も言いましたが、UIに絡む処理は絶対にUIスレッドで行わなければなりません。そこで今回はHandler#postを使います。

public final class MyUserStreamListener extends UserStreamAdapter {
    
    private Context mContext;
    private static Handler mHandler = new Handler();
    
    public MyUserStreamListener (Context context) {
        mContext = context;
    }
    
    @Override
    public void onStatus(Status status) {
        // 別スレッドでの処理
        final String tweet = status.getText();
        
        // UIスレッドでの処理
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(mContext, tweet, Toast.LENGTH_SHORT).show();
            }
        });
    }
    
}

これでちゃんとうざいぐらいにToastが表示されるはずです。

とまぁ、Toastぐらいなら全然問題ないんですが、TextViewにsetTextするとか、そう言うことをしようとすると話が違ってきます。

MyUserStreamListenerにTestStreamActivityを渡してしまうとかすればいいんですが、密結合も甚だしいので、実際には匿名クラスを使うことになるでしょう。

public class TestStreamActivity extends Activity {
    
    private TwitterStream mStream;
    private static Handler mHandler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stream);

        mStream = TwitterInstance.getTwitterStream(getApplicationContext());
        //UserStreamAdapterを継承した匿名クラスを作成する
        mStream.addListener(new UserStreamAdapter() {
            @Override
            public void onStatus(Status status) {
                final String tweet = status.getText();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        TextView textView = (TextView) findViewById(R.id.txvTweet);
                        textView.setText(tweet);
                    }
                });
            }
        });

        // UserStream開始
        mStream.user();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mStream.cleanUp();
    }
    
}

まぁ許容範囲内…ですが、他のイベントも実装していくととんでもなく長くなったりします。また、動かしている途中でイベントをの一部だけを差し替えるなんてことはそのままでは出来ません。嫌気がさしてきたら[Android]Twitter4JのUserStreamのイベント時の動作をクロージャで指定すると言う記事を読んでみてください。

終了に関してはTwitterStream#cleanUpかTwitterStream#shutdownを呼べばokです。違いはjavaDoc読んでください。

タイミングとしては最初に表示されるAcitivtyのonPauseかonDestory(onStopは稀に呼ばれないことがあるのでonPauseの方がベター)になると思うんですが、他画面に移動したり他アプリを起動しても接続したままにしたいならonDestory、そうでないならonStopがいいと思います。再接続はonRestartがオススメです。

いずれにせよ安全面からTwitterStreamをファイナライザで確実に切断するクラスでラップしておくことは絶対にやっておいた方がいいと思います。

まとめ

珍しくAndroid初学者も意識しつつ書いてみました。ほんとに今更ですが…。