朝早く家を出ることができるアラームアプリ MorningBomb (モーニングボム)
趣味でAndroidアプリを作りました!
朝早く家を出ることができるアラームアプリ MorningBomb (モーニングボム)
アラームを止めたあと、制限時間内に家から離れないと、友達や家族にTwitterやSMSで反省文が送信されてしまいます。ヘタレになりたくないので、早く起きられるようになります。
「そこまでしなくても起きれるよ…」という人が多いとは思いますが、必要としてくれる人がいたらいいなぁ。
TortoiseGitで古いバージョンのコミットとの差分を見る方法
TortoiseGitでHEAD(現在のブランチ)や直前のコミットとの差分を調べるには、ファイルやフォルダを右クリック−TortoiseGit−Diff や Diff with previous version を実行すればよいが、古いコミットとの差分を見る方法がわかりにくかったのでメモ。
以下のように、Show logメニューからたどれる。
ファイルやフォルダを右クリック−TortoiseGit−Show log−リストボックスからコミットを選択−Compare with working copy
ちなみに、同等のコマンドは、
git diff コミットID ファイル名
になると思う。
TortoiseGit 1.6.5.0と1.7.2.0で確認。
Androidで使われていないリソースを一覧する
いろいろ試しながら開発したときや、他のアプリのソースコードを流用したときなどに、最終的には使わないリソースがたまってくる。これらの不要なリソースを一覧するツールを見つけた。文字列だけでなく、arrays.xml、colors.xml、styles.xmlの各種リソースにも対応している。
使い方は簡単。Androidプロジェクトのルートディレクトリに、上記のサイトからダウンロードしたAndroidUnusedResources.jarを置いて、コマンドプロンプトで以下を実行する。
java -jar AndroidUnusedResources.jar
すると、標準出力に未使用のリソースの一覧が出てくる。これは便利。
Android Google Maps APIのソースコードはダウンロードできないっぽい。
MapViewクラスの実装が見たくてAndroidのMaps APIのソースコードを探したが、見つからなかった。以下のフォーラムのページによると、公開されていないみたい。
How to get the source code of Google maps for android sdk 1.5
> Do anybody know how can I get the source code of Google maps for
> android sdk1.5You can't, except maybe by some special licensing mechanism if you are a
major device manufacturer.
ソースコードについての記述は見つけられなかったが、以下が公式ページ。
Google Projects for Android: Google APIs
OAuth認証のフローをAndTweetのソースコードで理解する
OAuth認証について勉強するために、オープンソースのTwitterクライアントAndTweetのソースコードを読んでみます。
OAuth認証の仕組みについては、id:yuroyoroさんの記事、OAuthプロトコルの中身をざっくり解説してみるよがメチャメチャわかりやすいです。
以下、上記の記事で解説されている「認証のざっくりした手順」に、AndTweetのコードを照らし合わせることで、OAuth認証への理解を深めます。(1,2,3...などの手順の文言は記事からの引用です。)
なお、AndTweetのダウンロード、デバッグ環境構築の仕方はオープンソースのTwitterクライアントAndTweetのソースコードをダウンロードしてビルドするを参照してください。
1. ユーザは、Consumer(AndTweet)にOAuth認証を行うように指示します。
記事には、「通常は、Consumerのページにあるログインボタンなどをクリックします。」とありますが、Andtweetの場合は、以下のようになります。
(1) Preferenceがタップされたタイミングで、OAuth認証をスタート
// PreferencesActivity.java @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { ... if (preference.getKey().compareTo(KEY_VERIFY_CREDENTIALS) == 0) { verifyCredentials(true); } ... };
(2) まだ認証されていなければ、OAuthAcquireRequestTokenTaskを実行
// PreferencesActivity.java private void verifyCredentials(boolean reVerify) { ... if (reVerify || mUser.getCredentialsVerified() == CredentialsVerified.NEVER) { ... new OAuthAcquireRequestTokenTask().execute(); } }
次は、OAuthAcquireRequestTokenTaskの内容です。
2. Consumer(Andtweet)は、Provider(Twitter)からリクエストトークンを取得します
(1) ConsumerからProviderへHttp通信でリクエストトークンを要求します。
(2) リクエストトークンの要求で、事前に発行されているConsumer Keyと、リクエストパラメータをConsumer Secretで署名した値をパラメータとして付与します。
(3) ProviderはHttpのレスポンスとしてリクエストトークンを返します。この段階では、まだ認証は完了していません。
// PreferencesActivity.java private class OAuthAcquireRequestTokenTask extends AsyncTask<Void, Void, JSONObject> { ... @Override protected JSONObject doInBackground(Void... arg0) { ... // Consumer KeyとConsumer SecretでConsumerインスタンスを作成する。 // CommonsHttpOAuthConsumerクラスは、oauth-signpostというライブラリに含まれている。 mConsumer = new CommonsHttpOAuthConsumer(OAuthKeys.TWITTER_CONSUMER_KEY, OAuthKeys.TWITTER_CONSUMER_SECRET); // Twitterで公開されているOAuth認証用のURLでProviderのインスタンスを作成する。 // CommonsHttpOAuthProviderクラスも、oauth-signpostというライブラリに含まれている。 mProvider = new CommonsHttpOAuthProvider(ConnectionOAuth.TWITTER_REQUEST_TOKEN_URL, // "https://api.twitter.com/oauth/request_token" ConnectionOAuth.TWITTER_ACCESS_TOKEN_URL, ConnectionOAuth.TWITTER_AUTHORIZE_URL); // "https://.../access_token", "https://.../authorize" ... // ProviderにConsumerのインスタンスとコールバックURLを渡してリクエストトークンを要求する。 (1), (2) // 同時に、発行されたリクエストトークンが付加された認証用URLを返す。 String authUrl = mProvider.retrieveRequestToken(mConsumer, CALLBACK_URI.toString()); // Providerはレスポンスとしてリクエストトークンを返して、Consumerにセットする。(3) // SharedPereferenceに保存しておく。 saveRequestInformation(tu.getSharedPreferences(), mConsumer.getToken(), mConsumer.getTokenSecret()); ... } ... }
3. 認証用URLへのリダイレクト・ユーザ承認
(1) Consumerは、発行されたリクエストトークンをURLに付与して、Providerの認証用URLへリダイレクトを行います。 // PreferencesActivity.java private class OAuthAcquireRequestTokenTask extends AsyncTask<Void, Void, JSONObject> { ... @Override protected JSONObject doInBackground(Void... arg0) { ... // 3の続き // 発行されたリクエストトークンが付加された認証用URLをブラウザで開く(1) PreferencesActivity.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl))); ... } ... }
(2) リダイレクト先で、Providerがユーザに対して、Consumerが要求しているOAuth認証によるAPI利用を許可するか選択します。
ブラウザで、以下のような「[アプリ名]があなたのアカウントを利用することを許可しますか?」のURLが開かれます。
ちなみに、このURLではユーザー名やパスワードがブラウザのクッキーに保存されるので、1度認証した後は、「ユーザー名やメールアドレス:」、および「パスワード:」の入力欄は表示されません。
(3) 承認した場合は、Providerは(通常は)Consumer登録時に設定したコールバックURLへリダイクレトします。
[アプリを認証]ボタンが押されると、Twitter(ブラウザ)は、Consumer登録時に設定したコールバックURLへリダイレクトします。リダイレクトはURL付きのIntentとして発行されるので、これをアプリ側で受け取ります。
// PreferencesActivity.java @Override protected void onResume() { ... Uri uri = getIntent().getData(); ... if (uri != null) { ... if (CALLBACK_URI.getScheme().equals(uri.getScheme())) { ... // This activity was started by Twitter ("Service Provider") // so start second step of OAuth Authentication process new OAuthAcquireAccessTokenTask().execute(uri); } } }
// AndroidManifest.xml <activity android:name=".PreferencesActivity" android:launchMode="singleTask"> ... <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="andtweet-oauth" android:host="twitt" /> </intent-filter> </activity>
アプリ側でURL付きのインテントを受け取ったら、次は、OAuthAcquireAccessTokenTaskでアクセストークンを取得します。
【参考】AndroidManifest.xmlにIntentFilterを記述し忘れた場合などで受け手が見つからなければ、僕の環境ではIntentをブラウザが受け取りました。しかし、ブラウザにはこのURLは表示できないとのエラーメッセージが出力されます。このとき、以下のようなエラーログが出力されました。
05-21 21:28:13.255: ERROR/browser(7042): onReceivedError -10 andtweet-oauth://twitt?oauth_token=EO9hB6PN5wUm9rknrfLXXXXXXXXXXXXXXXXXXXX&oauth_verifier=SI2WkipVnVXUspyNZd9ikAXXXXXXXXXXXXXXXXXXXX このプロトコルには対応していません。
4. アクセストークンの取得
(1) コールバックURLへのリダイレクトで、Consumerはリクエストトークンをもとにアクセストークン取得をHttp通信でProviderへ要求します。
(2) アクセストークン取得要求は、Consumer Keyとリクエストトークンなどをパラメータに付与して呼び出します。(通常はAuthorizationヘッダに設定)
(3) アクセストークン取得要求のパラメータも、Consumer Secretで署名した値を付与します。
(4) Providerは、レスポンスとしてアクセストークンを返します。
// PreferencesActivity.java private class OAuthAcquireAccessTokenTask extends AsyncTask<Void, Void, JSONObject> { ... @Override protected JSONObject doInBackground(Uri... uris) { ... TwitterUser tu = TwitterUser.getTwitterUser(PreferencesActivity.this); Uri uri = uris[0]; if (uri != null && CALLBACK_URI.getScheme().equals(uri.getScheme())) { // リクエストトークンとシークレット String token = tu.getSharedPreferences().getString(ConnectionOAuth.REQUEST_TOKEN, null); String secret = tu.getSharedPreferences().getString(ConnectionOAuth.REQUEST_SECRET, null); ... ConnectionOAuth conn = ((ConnectionOAuth) tu.getConnection()); ... mConsumer.setTokenWithSecret(token, secret); // (2), (3) ... // アクセストークン取得をProviderへ要求する (1) mProvider.retrieveAccessToken(mConsumer, verifier); // Providerはアクセストークンとシークレットを返す (4) token = mConsumer.getToken(); secret = mConsumer.getTokenSecret(); ... conn.saveAuthInformation(token, secret); // SharedPreferencesに保存しておく ... } } ... }
アクセストークン取得の流れはこんな感じです。もう少しコードの中身を見てみたいですが、今回はフローを理解することが目的なので省略します。
5. OAuthでのAPI呼び出し
(1) 実際のAPI呼び出しは、取得したアクセストークンをパラメータに付与して呼び出します。
(2) アクセストークンとConsumer Key、およびパラメータをConsumer Secretで署名した値を、Authorizationヘッダに設定に設定してAPIを呼び出すことで、OAuth認証を利用したAPI呼び出しができます。
// ConnectionOAuth.java private JSONObject postRequest(HttpPost post) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException { ... // リクエストヘッダに、取得したアクセストークンを付ける mConsumer.sign(post); String response = mClient.execute(post, new BasicResponseHandler()); ... }
// oauth.signpost.AbstractOAuthConsumer.AbstractOAuthConsumer public HttpRequest sign(HttpRequest request) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException { ... requestParameters = new HttpParameters(); ... collectHeaderParameters(request, requestParameters); collectQueryParameters(request, requestParameters); collectBodyParameters(request, requestParameters); // リクエストヘッダに、oauth_consumer_key, oauth_nonce, oauth_signature_method, oauth_timestamp, // oauth_token, oauth_versionを付ける completeOAuthParameters(requestParameters); ... }
以上です。
オープンソースのTwitterクライアントAndTweetのソースコードをダウンロードしてビルドする
AndroidでTwitterクライアントアプリを作る参考にするために、AndTweetというオープンソースのTwitterクライアントをダウンロードして、ビルドする。
AndTweet(オープンソースのTwitterクライアント)
1.AndTweetのソースコードをダウンロードする
(1) 以下のサイトからソースコードをチェックアウトする。
Source Checkout - andtweet - Light-weight Android Twitter client - Google Project Hosting
(2) Mercurialでクローンを作成
hg clone https://andtweet.googlecode.com/hg/ andtweet
Mercurialは使ったことがなかったので、以下のサイトからダウンロードした。
Windows版のMercurial-1.8.3 (32-bit msi)
すべてデフォルトの設定でインストール終了。コマンドプロンプトで「hg」と入力して、ヘルプ画面が出てきたら成功。上記のhgコマンドを実行する。
2.TwitterでOAuth認証用のカスタマーキー、カスタマーシークレットを申請する
OAuth認証というのは、パスワードがアプリ作者に漏れないようにするための仕組み。パスワードをアプリ内に保持しておいてTwitterにPOSTするのではなく、Twitter側で用意したURLにIDとパスワードを入力して認証する。アプリがTwitterにアクセスする際は、認証のときに発行された認証コードを使用する。OAuthの概念を理解するには、以下のサイトが参考になる。
OAuth 2.0でWebサービスの利用方法はどう変わるか @IT
Twitterの認証コード(カスタマーキーとカスタマーシークレット)を取得するために、http://twitter.com/oauth_clientsにアクセスして必要事項を入力する。アプリケーションのURLの欄は、まだ取得していないURL(実在しないURL)でも大丈夫だった。
3.AndTweetをeclipseでビルドする
eclipseでプロジェクトをインポートしてビルドすると、OAuthKeysクラスでビルドエラーが出る。OAuthKeys.javaを開いてみると、以下のようにコメントに、カスタマーキーとカスタマーシークレットをフィールドに持つMyOAuthKeysクラスを作成するように書かれている。
// OAuthKeys.java package com.xorcode.andtweet.net; /** * In order to compile the application you either: * 1. Create your own MyOAuthKeys class with your application's * TWITTER_CONSUMER_KEY and TWITTER_CONSUMER_SECRET * 2. Or remove "extends MyOAuthKeys" below and un-comment the values here * replacing values with your own token and key. **/ public class OAuthKeys extends MyOAuthKeys { // public static final String TWITTER_CONSUMER_KEY = "Your Twitter Application Token"; // public static final String TWITTER_CONSUMER_SECRET = "Your Twitter Application Secret"; }
なので、MyOAuthKeysクラスを作成する。これでビルドは成功する。
package com.xorcode.andtweet.net; public class MyOAuthKeys { public static final String TWITTER_CONSUMER_KEY = "XXXXXXXXXXXXXXXXXXXXXX"; public static final String TWITTER_CONSUMER_SECRET = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; }