2011年12月28日

Amazon S3で保存期間を過ぎると自動削除する設定が可能になった

朝起きるとAmazonからお知らせのメールが届いていた。
Today we're excited to announce Object Expiration, a new feature to help you efficiently manage data stored in Amazon S3. Object Expiration enables you to schedule the removal of objects after a defined time period.

You can define Object Expiration rules for a set of objects in your bucket. Each expiration rule allows you to specify a prefix and an expiration period in days.  (...)

(以下意訳)
「Object Expiration」機能のお知らせです! Amazon S3に保存されたデータを効果的に管理する事を可能にする新機能です。これによって一定期間が過ぎるとオブジェクトが削除される様にスケジュールする事が出来ます。

お客様のBucket内の任意のオブジェクトのセットに対してObject Expirationルールを設定して頂けます。それぞれのルールにはプリフィックスと保存期間(日数)を指定する事が出来ます。(...)


早速AWSの管理コンソールを開いて確認してみた。

Bucketのプロパティを表示して、「Lifecylcle」というタブを開くと、ルール名、プリフィックス、保存期間の組み合わせを複数設定出来る様になっている。


プリフィックスに「foldername/」という様に指定すればフォルダ単位で保存期間を設定出来る。

 これは便利! 

これで自前でバックアップのローテーション機能を作り込まなくても大丈夫になりそうだ。

というかちょうど先週「ローテーション機能付きS3バックアップツール for .NET」を作った所だったのだけど。。。








2011年12月22日

VB.NETでAmazon EC2のEBSスナップショットを自動化する

Amazon EC2のEBSスナップショットをWindows環境で自動化する必要に迫られた。

スナップショットを取るだけならEC2のコマンドラインツールですぐに出来るけれども、際限なくスナップショットが増えてしまうのは困る。

そこで、古いスナップショットを自動で削除しつつ新しいスナップショットを作成してくれるツールが必要になる。

Rubyスクリプトはいくつか見つけた。
code.rock: Amazon EBSとRightAwsを使って自動バックアップ環境を構築する

実践で使えるEBSスナップショット取得スクリプト - よかろうもん!

EBSのスナップショットを定期的に作成するスクリプト - こせきの技術日記

oko_changのインフラ日誌: EBSボリュームのスナップショットを自動作成する(right_aws)

シェルスクリプトもあった。
Amazon EBSのスナップショット(バックアップ)を取得しつつ世代管理も行うスクリプト - RX-7乗りの適当な日々

Pythonスクリプトもあった。
AmazonEC2/S3の便利スクリプトのまとめ | スターワン::ブログ


でもそれだけのためにRubyやPythonをインストールしたり、Cygwinをセットアップしたりするのはちょっと避けたかった。


クラウド管理用のWebサービスも試して見た。

RightScaleCloudWorks など。

RightScaleはUIが複雑過ぎて断念。CloudWorksは無料版だと世代管理は出来なさそう。


要件を満たすものがなかなか見つからないので、結局 AWS SDK for .NET を使って自分で作ってみる事にした。
Ec2EbsSnap - GitHub

Ec2EbsSnap - Source Code (Zip file)

実行可能ファイルはこちら (.NET Framework 3.5以上が必要)












2011年12月16日

次期ASP.NETのBundling and Minification機能が良さそうだ

次のASP.NET(4.5)には「Bundling and Minification」機能というものがあるそうだ。

New Bundling and Minification Support (ASP.NET 4.5 Series) - ScottGu's Blog

サーバー側で、複数のスタイルシートやJavaScriptを一つにまとめてさらに「Minification」までして返してくれるという機能らしい。

通常だとファイルが例えば3つに分かれていればクライアントのブラウザからは3回のHTTPリクエストが発生する。でもこの機能を使うと「〇〇フォルダのCSSファイルを全部取って来て」という指定をするとサーバー側でそのフォルダ内のCSSファイルを一つにまとめてからクライアントに返してくれるそうだ。

サーバー側でまとめる時に、当然順序が正しくないとおかしな事になる。そこで、基本的にはアルファベット順になるけれども、JavaScript なら jQuery、スタイルシートなら reset.css など一般的に最初に読み込んだ方が良さそうなものはちゃんと最初に持って来てくれる。

またBundle処理の詳細やMinificationの詳細な動きを自分で制御したい場合は、それなりのクラスを継承したクラスを書いてコードから呼び出せば可能だそうだ。

ASP.NET 4.5、この他にも多くの新機能があってなかなか面白そうだ。
ASP.NET 4.5 Series - ScottGu's Blog









2011年12月8日

App Engineで課金を出来るだけ少なくする方法

下のブログを見て、ほぉーと思ったのでメモ。
 Google Developer Day 2011 Japan: 「App Engine 最新機能」 | Google Japan Developer Relations Blog 

48分もある動画だったので、飛ばしながら観ていたらGoogleの松尾さんが「このセッションで最も重要な事」と言われている部分(20:00辺り)があった。

「App Engineで課金額を気にする人は今すぐこの設定をして下さい。」と言われているので、早速やっておいた。

管理画面に入って、「Application Settings」を開くと下の2つのスライダーがある。


  • 「Max Idle Instances」を最小(1)にする。 
  • 「Min Pending Latency」を最大(15s)にする。 


これで課金が最小になるとの事。

その後それぞれのスライダーの意味の説明を聞いて、確かになるほどなと思った。 

ただし、プロダクションで使っている重要なアプリケーションの場合はこの設定を適用するのは慎重にした方が良いらしい。

趣味で作っているサイトであればこれで特に問題は無いだろうと思う。











2011年11月22日

Android: Theme.Dialogを適用したアクティビティを最大化して表示したい時は

マニフェストファイルに下の様に書くだけでアクティビティがダイアログ風に表示されるのはとても便利。
<activity android:label="Help" android:name=".WebViewActivity"   android:theme="@android:style/Theme.Dialog">
</activity>
ただ、これで表示されるダイアログの大きさは、その中に表示する内容によって変わる。 

たまたま、WebViewとButtonをFrameLayout上に配置して幅・高さともにfill_parentを指定した時に、内容が見えないくらいにダイアログが小さくなってしまって困った。

 こんな感じになってしまう。


検索したらやっぱり同様に困っていた人がいた。
android - How can I get a Dialog style activity window to fill the screen? - Stack Overflow
ここにある通り、onCreate内でsetLayoutをすれば意図した通りダイアログが最大化されて表示された。


こんな感じで一件落着。

ついでに、ダイアログのタイトルバーが不要なので消したい。

まず、res/values/styles.xmlファイルを作る。

あとはマニフェストで @android:style/Theme.Dialog の代わりに @style/Theme.MyDialog を指定すればOK。













2011年11月4日

IIS 6.0でASP.NET MVCアプリケーションを動かす方法

ASP.NET MVCのアプリケーションでは基本的にURLは拡張子無しになる。

http://www.example.com/myapplication/Home/

ところが、AS.NET MVCアプリケーションをただサーバーにデプロイしただけだとIIS6では上のURLは404エラーになってしまう。

そこで、IISの設定を少し変更する必要がある。

Deploying ASP.NET MVC to IIS 6
 http://blog.stevensanderson.com/2008/07/04/options-for-deploying-aspnet-mvc-to-iis-6/

このブログには4つの方法があると書かれている。

方法1.aspnet_isapi.dllへのワイルドカードマッピングを追加する。

方法2.全てのURLに.aspxの拡張子を付け、ルーティングで制御する。

方法3.全てのURLに独自の拡張子を付け、ルーティングで制御する。

方法4.URLリライトを行う。



2と3はURLが格好悪くなるので避けたい。4の方法はちょっと設定が面倒そうだ。という事で1の方法を使う事にした。

ただし1の方法だと画像など静的なファイルも全てASP.NETが処理してしまうので、パフォーマンス的に不利になるとの事。

そこで次の対策も必要になる。
Overriding IIS6 wildcard maps on individual directories
http://blog.codeville.net/2008/07/07/overriding-iis6-wildcard-maps-on-individual-directories/
ここに書かれてある方法でサブディレクトリ単位でワイルドカードマッピングを適用しない様に設定出来るとの事。

これでとりあえずやって見ようと思う。


最後におまけで方法5も載っていた。

それは、

「Windows Server 2008に移行してII7を使う。」

確かに、それが一番!(笑)












2011年10月21日

Android 4.0で反応の良い楽器アプリは可能になるのか

以前こんな記事を書いた。

AndroidでiPhoneに匹敵する楽器アプリの作成が困難な理由

先日Android 4.0のAPIが公開されたので、Audio関係が何か改善されたのかな、と思ってざっと検索して見た。

Issue 3434 - android - need NDK support for real-time low latency audio; synchronous play and record - Android - An Open Handset Alliance Project - Google Project Hosting http://code.google.com/p/android/issues/detail?id=3434

このスレッドが開始されたのが2009年の7月。それから2年以上も経つのにほぼ何の進展も無い事に驚かされる。

今日の時点での一番最新のコメント(#214)がこれだ。
I'm looking at the 4.0 API diffs now - I don't see any promising audio changes. 
4.0 APIの差分を見てるけど、Audio関係で期待出来そうな変更は何も無いよ。。。

他のコメントにもたくさん書かれている通り、まともなAudio APIが無いせいでAndroid用の楽器アプリの開発をあきらめざるを得ない開発者がたくさんいる様だ。

楽器アプリの少なさに幻滅してiPhone/iPadに流れるユーザーもきっとたくさんいると思う。

Googleの優秀なエンジニア達がこの問題を知らないはずはない。きっと多様なハードウェアに対応する為にソフトウェアがかなり抽象化・多層化されていて、簡単には改善出来ないのだろうと思う。

まあそれにしても、Android 5.0(?)ではぜひとも改善して欲しいものだ。




2012/11/20 追記:
4.2になって少し改善されたみたいだ。

Android 4.2で今度こそ反応の良い楽器アプリは可能になるのか!?






 

2011年10月13日

作ればわかる!Google App Engine for Javaプログラミング

App Engineの本をまた買ってしまった。

まだ第2章までしか読んでいない。だけど多分この本は「当たり!」だと思う。説明がていねいで分かりやすい。


「作ればわかる!Google App Engine for Javaプログラミング」

http://amzn.to/ov0264



第2章のサンプルアプリケーションは「イツドコデダレガナニシタ」。この解説の中で書かれている、画面遷移方法の説明、文字化け対策についての説明、グローバルエラー画面の作成方法の辺りを読んで、気に入った。ここまでていねいに書かれているのであれば、この先の章の他のサンプルアプリケーションの解説についても期待が持てる。

特に僕みたいに普段の仕事ではJavaを使わない人にとっては、とても便利な参考書になりそうだ。

早く残りの9章を読み終えて、自分でも何か作ってみたくなって来た。








2011年10月4日

無料で容量無制限のソースコードホスティング! Bitbucket素晴らしい!

Bitbucket (https://bitbucket.org/)

が Git に対応したとの事で、早速登録して使って見た。


もちろんクレジットカードの登録なども不要。
5ユーザーまでなら無料で公開・非公開のリポジトリをいくつでも作成出来る!

GitHubからのコピーも、URLを入力してボタンをクリックするだけで完了した。

GitHubを初めて使った時も感動した。ただ、無料アカウントでは非公開のリポジトリを作成出来ないのが残念だった。

Bitbucket とてもいい感じ。これから重宝しそうだ!








2011年9月25日

Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


さて、いよいよ今回は石を裏返す時のアニメーションを作ってこのアプリを完成させたい。


背景と石の描画に画像を使う


その前に、ちょっと見栄えが良くなる様に、盤面の背景と石の描画に画像を使って見た。盤面も少しだけ小さくして周りに枠が出来る様に調整した。

こんな感じで、劇的にとは行かないまでも大分見た目が良くなったと思う。




背景の画像は無料素材サイトで見つけたものを利用させてもらった。
黒と白の石の画像はInkscapeを使って何とか自分で描いた。円にグラデーションを付けただけだけど、ベタで描くよりは断然良くなったと思う。


Android 1.5でエラー発生!


さて、自慢してやろうと思ってヨメの携帯にこのアプリを入れて見た。機種は第一世代AndroidのHTC Heroだ。起動すると、何やらエラーが出て起動しない。よく見たらAndroidのバージョンが1.6ではなく1.5だった。

マニフェストのSDK VersionとプロジェクトのターゲットSDKバージョンを3に変更してやって見てもやっぱりダメだ。

Logcatを見ると、「リソースが見つからない」というエラーの様だ。Android 1.5以下では画像ファイルは res/drawable-mdpi ではなく res/drawable フォルダに配置しないと行けないのだった。

知っていれば何でもない事なのだけど、解決するまでに1時間も費やしてしまったので、忘れない様に一応書いておこう。


石を裏返すアニメーションを作る


前回の記事でTextViewの文字列をぐるぐる回すアニメーションを作った。 これはAndroid標準のアニメーション機能を使って簡単に出来た。ただ、石を裏返すアニメーションの方は標準のアニメーション機能ではちょっと難しそうだ。盤面と石の描画をカスタムViewで行っているからだ。

もし標準のアニメーションを使うのであれば、カスタムViewの上にImageViewなどをオーバーレイしてそれをアニメーションさせれば良いかなとも思ったけれど、同時に裏返される石の数は毎回異なるのでかなりコードが複雑になってしまいそうな気がする。

一方、自分でアニメーション処理を実装するのであれば、別スレッドでタイマー処理を行って一定時間ごとに描画が更新される様にすれば良い。処理の概要は次の様な感じになる。

  • タイマーイベントが発生する毎に石の横幅を変化させて描画する。
  • アニメーションにかける時間の半分を使って横幅を100%から0%まで減らし、残りの半分の時間で0%から100%まで戻して行く。
  • その際、最初の半分は元の色、残りの半分は裏返した後の色を使う。
  • 裏返し対象の石は複数あり得るのでArrayListに入れておいて、描画ルーチンの中でループして処理する。

いろいろ試した結果、0.6秒かけてアニメーションさせて、その間に15回のタイマーイベントを発生させるぐらいがちょうどいい感じになった。
Viewを使った場合は最高でも4FPSぐらいしか出ないという事を読んでいたので、もっと荒いアニメーションにしないと行けないかと思っていたけれど、実際にやって見た限りではこれぐらい細かくタイマーイベントを発生させないとアニメーションがスムーズに見えなかった。


裏返しのアニメーションを付ける前がこれ。


裏返しのアニメーションを付けた最終形がこれ。




ビットマップ画像を拡大/縮小して表示する


最初はアニメーション中に石の横幅を変えて表示する際に、サイズを指定して drawBitmap を呼んでいた。でもこれだと毎回拡大/縮小の処理をする分遅くなるので、起動時にアニメーションで必要になる分の Bitmap を createScaledBitmap を使って生成して配列に保持しておく様にした。これでスムーズに表示される様になった。
参考: Android 奔走記: Bitmap を使ってみた





ソースコード


プロジェクト全体のソースコードはこちら。
mikehibm/MiReversi at ver4 - GitHub

これでこのプロジェクトはついに完成!
6月の第一回目からちまちまと作って来て、ほぼ3ヶ月かかってしまった。。。

せっかくここまで作ったので、近いうちにマーケットに一般公開できる状態まで持って行きたいと思う。いくつか面白い機能追加のアイデアはあるので、その部分を作り込んだらぜひアップしたい。

このプロジェクトでは、SurfaceView を使わなくても普通の View でここまで出来るという事が分かった。次は SurfaceView を使って何か作りたい。その次は GLSurfaceView の攻略だ。

まだまだ先は長いな。(笑)

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!











2011年9月4日

今注目すべき7インチAndroidタブレットは?

iPadは下に置いて使う分にはいいのだけれど、仰向けに寝転がって使うには重さがあり過ぎる。

そこで自然と300グラム台のタブレットが欲しくなる。。。となると7インチだ。

試しにAmazon.comで7インチのAndroid タブレットを検索して見たら、あまりにもたくさんヒットしたので驚いた。1年前と比べると格段に多くの選択肢があって嬉しい。

ただこれからAndroidタブレットを買うのなら、CPUは1GHz以上でOSのバージョンは3.x以上のものにしたいところ。

もしクリスマス頃に余裕があったらどれを買うかなあ、などと考えながらいくつか注目すべき機種をピックアップして見た。



速報:サムスン Galaxy Tab 7.7発表、スーパー有機ELプラス & 1.4GHzデュアルコア、335グラム








国内初のAndroid 3.2搭載7インチタブレット「GALAPAGOS A01SH」の発売日が決定。8月30日より販売開始
イーアクセス、Android 3.2搭載の7インチタブレット「GALAPAGOS(A01SH)」 - YouTube
2011-09-05 - 新・たけぞう瀕死の日記 | GARAPAGOS A01SH買った





動画:レノボから199ドルの7型 Androidタブレット IdeaPad Tablet A1







Android 3.2搭載の7インチタブレット「MediaPad」、Huaweiが発表 - ITmedia ニュース







Camangi、Android 3.1搭載7インチタブレット「Mangrove 7・WiFi」を正式発表。8月中旬に発売へ







2011/09/27 追記: これもいいかも。

7インチAndroid 3.2搭載タブレット「Acer ICONIA Tab A100」開封の儀 - ガジェット通信

Iconia Tab A100 Review - SlashGear





う~ん、スペック的にはサムスンのGalaxy Tab 7.7が最先端を走っている感じ。デュアルコアで1.4GHzとは、相当早そうだ。

最先端のスペックを求めないのであれば、レノボのIdeaPad A1というのも良さそうだ。$200ドルを切るのであれば、ヨメにも気軽に「買っていい?」と言えそうな気がする。^^
ただこれはOSがAndroid 2.3との事。3.2以上にアップデートされるなら真剣に考えたい。



最後にこちらにも注目! 意外と良いかも。

AmazonがKindleタブレットを7インチにとどめたのは賢明―モックアップ写真あり!

iPadに強敵現る? Amazon Kindleタブレット、こんな感じです! : ギズモード・ジャパン









2011年8月15日

Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


前回までで思考ルーチンがそこそこ動く様になってゲームとしては結構遊べる状態になったので、今回からは少しずつグラフィックを改善して行きたい。

ひとまずこんな感じで、ゲーム終了時の結果表示の文字列をぐるぐる回してみた。
http://www.youtube.com/watch?v=AYld0bnjxeI

FrameLayoutを使ってオーバーレイ表示


前回まではカスタムのReversiViewの中でdrawTextを使って勝ち負けを表示していた。今回はその代わりにAndroid標準のアニメーション機能を使いたいので、ReversiViewの中心に重なる様にTextViewを配置して、そこに文字列を表示した。

複数のViewを重なる様に配置するには、FrameLayoutを使うと良いらしい。

TextViewには標準でドロップシャドウの機能が付いているのでこれも利用した。


文字列を画面の中心に配置するのは、

 android:layout_gravity="center"

とするだけで出来た。素晴らしい。^^

ちなみにTextViewの上のViewは、ゲーム終了時に画面の背景を暗くするアニメーションを入れる為に使っている。




AnimationをXMLファイルで定義


AnimationをXMLファイルで定義するには、res/の下にanimフォルダを作ってそこに適当な名前でXMLファイルを作成する。

色々試行錯誤して見た結果、こんな形になった。
回転しながらズームイン&フェードインして、一瞬止まった後でスーっと上に動く感じ、が作りたかったので。

また文字列を回すのと同時に、背景を少しずつ暗くするのもalphaアニメーションを使って行なっている。

あと、ちょっとハマったのは、setタグの属性として「xmlns:android=...」を付けないと set全体にfillEnabledとfillAfterの属性を付けられなかった点だった。




AnimationをTextViewに適用


ActivityのonCreateでAnimationUtils.loadAnimation()を呼んで上で定義したアニメーションを準備しておく。

実際にアニメーションを開始するには、TextViewのstartAnimation()を呼ぶだけだ。





ソースコードと次回の予定


現時点のプロジェクト全体のソースコードはこちら。
mikehibm/MiReversi at ver4 - GitHub

次回はいよいよコマをひっくり返す時のアニメーションを作りたい。それが出来たら一応完成かな?


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!








 .

2011年8月2日

Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


前回の記事からあっと言う間に半月以上も経ってしまった。

その間、会社のXenServerがトラブったり、オフィスの引越し準備と大きなプロジェクトのカットオーバーが重なったりで、7月はなかなか大変な月だった。

そんな中で実はオセロの思考ルーチンを少しずつ実装したりもしていた。結局、仕事と子育てで一杯一杯なのはいつもの事なので、早朝5時起きしてでもなんとか自分の時間を作ってやって行くしかないかなあと思う。



思考ルーチンの実装


オセロの思考ルーチンを作り込む事は、このアプリを作り始めた時の「Androidでの2Dグラフィックス処理の習得」という目標からは完全に脱線している。でもどうしても誘惑に負けて、作り込まずにはいられなかった。(笑) 面白いのだから仕方がない。

下の2つのリンク先にはとてもとてもお世話になった。
-鶯教-コンピュータ・リバーシ講座

リバーシ(オセロ)プログラム

今のところ、作成した思考ルーチン(?)は下の4種類。それぞれが ComputerPlayer クラスのサブクラスになっている。

(クラス名クリックでソースコードを表示します)
ComputerPlayer0 何も考えずに打てる場所の中からランダムに選んで打つ。
ComputerPlayer1 次に打つ場所の候補のリストを、事前に定義された評価テーブルにしたがってソートした上で、評価の最も高い場所に打つ。打った結果についての先読みはしない。
ComputerPlayer2 1手先までの局面の評価値を計算し、最も評価の高い手を選ぶ。
ComputerPlayer3 「ネガマックス法」にしたがって最大4手先までの局面の評価値を計算し、最も評価の高い手を選ぶ。


クラスの継承関係はこんな感じになっている。
  Player (人間とコンピュータの両方に共通のコードを記述)
     HumanPlayer   
     ComputerPlayer (スレッドの起動・停止など全ての思考ルーチンに共通する処理を記述)
       ComputerPlayer0 (各思考ルーチンの実装はこのレベルのクラスに記述)
       ComputerPlayer1
       ComputerPlayer2
       ComputerPlayer3

ゲーム実行時の基本的な流れとしては、HumanPlayerの番になるとボード上の有効な場所がタッチされるまで待ち、ComputerPlayerの番になると別スレッドを起動して思考ルーチンを走らせる様になっている。

思考ルーチンは戻り値としてPointを返すので、あとは人間がその座標をタッチした場合と同様の処理を行えばよい。

ただ、思考ルーチンが走っている間に電話がかかって来たり別のアプリが前面に呼び出されたりするとActivityが終了してしまう可能性がある。その場合は思考ルーチンのスレッドもきちんと終了させる必要があるので、その辺りにはちょっと気を使わないと行けない。



気になる強さは?


ComputerPlayer1と2はそれなりに遊べるとは思うけど、かなり弱い。

ComputerPlayer3は、個人的にはそこそこ強いのではないかと思う。ただめちゃくちゃ強いというほどでもない。自分で対戦して見た限りだと、5回やると4回は自分が負ける。1回ぐらいはぎりぎり勝てるという感じだ。

不思議な事に、こいつに自分が負けるとなぜか嬉しい。早起きして眠い目をこすりながらデバッグした甲斐があったというものだ。(笑)



ソースコードと次回の予定


現時点のプロジェクト全体のソースコードはこちら。
mikehibm/MiReversi at ver3 - GitHub

次回はいよいよコマを裏返す時のアニメーションとか、ゲーム終了時の結果表示画面などをグラフィック機能を駆使して作り込んでみたい。早寝早起きして頑張ろう。^^


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!





2012/1/12 追記:
ここにある思考ルーチンは最強かも!
Booby Reversi Download Page

強いオセロプログラムについて







2011年7月15日

XenServerのiSCSI共有ストレージが壊れた時の復旧方法メモ

トラブルの概要


先日、社内で使っているXenServerの共有ストレージにアクセス出来なくなると言うトラブルが起こった。

使っていた共有ストレージは、Buffalo TeraStation ProのiSCSIモデル。セットアップしてから約1年半、全く何のトラブルも無く動いていた機器だったので、油断していた。

ビルディングのメンテナンスで長時間の停電があって、その停電からの復帰後に起動したら「iSCSIの論理ボリュームを作成出来ない」という旨のエラーがストレージ側のログに出て、XenServerからの接続が一切出来なくなってしまった。

TeraStationの管理画面を開いて色々やって見たけれどもどうもらちが開かないので、TeraStationからHDDを取り出してPC(サーバー)に直接接続してみた。Raid1で4本のHDDを2本ずつ別々にミラーリングしているだけだったので、1本だけ取り出してもそのまま読めるだろうと思ったのだ。

まずはPC上でDVDからUbuntuを起動する。でもそのままではHDDはマウント出来なかった。かなり焦りながら調べた所、どうやら mdadm コマンドでSoftware Raidとして認識させる必要があるらしいという事が分かった。
ソフトウェアRAIDのデグレード(縮退)モードからの復旧 - RX-7乗りの適当な日々

mdadmを使ったRAIDの再構成 - 日々雑文



mdadmでミラーリングを認識


まずは、
sudo apt-get install mdadm
でmdadmをインストール。
mdadm --examine --scan
cat /proc/mdstat
これでRaidの認識は出来た。次に、
mdadm --run /dev/md0
mdadm --run /dev/md1
mdadm --run /dev/md2
とやると、ミラーリングされたデバイスに片肺状態でアクセス可能になる。
cat /proc/mdstat
でACTIVEと表示されるのが確認出来る。



lvm2で論理ボリュームを認識


ここまで来たら、lvm2で論理ボリュームを確認する所までは一気に行ける。
sudo apt-get install lvm2
pvscan
vgscan
lvscan

これで論理ボリュームの情報が表示されればOK。保存されていた仮想マシンのディスクイメージの分だけ論理ボリュームが存在している。

この論理ボリュームを何とかUbuntuでマウント出来れば、データが取り出せるはずだ。よし、復旧までもう少し。。。

と思ったが、ここからがまた結構長かった。データがそこにあるのが分かっているのに取り出せない、という辛い状態で数時間を費やす事になってしまった。

しょんぼり技術メモ

LVMの論理ボリュームをddでコピー - litediary




ddで吸いだす


とりあえずlvmの論理ボリュームのままでは扱いにくいので、 dd コマンドでボリュームを丸ごと別のディスクにファイルとしてコピーしておく。実はddコマンドなんて普段使う事がほとんど無いので、勉強になった。(笑)
dd if=/dev/VS_XenStorage..(論理ボリュームの名前) of=/media/EXTERNAL/data.vhd bs=4M
これで論理ボリュームからdata.vhdというファイルにコピー出来た。

さて、XenServerの仮想マシンのイメージはVHD形式で保存されているらしいという事は分かったのだが、それがどうやってもマウント出来ない。

最初は losetup と kpartx と言うコマンドでループバックデバイスを構成してから mountコマンドでマウントという方法を試した。下の例の様に、その方法で解決出来たという情報がいくつか見つかったからだ。
Citrix Forums : Unable to mount a VHD in Dom0 ...

CTX117791 - How To Mount a Linux LVM Partition in a XenServer Host - Citrix Knowledge Center

CTX116183 - How To Mount a XenVM Filesystem on XenServer Host - Citrix Knowledge Center

Xenのイメージファイルをマウントする « そこはかと

lvmのディスクイメージをmount - 間違いだらけの備忘録

LVMで使っていたドライブからデータを救出する - kinneko@転職先募集中の日記

上の方法ではどうしても上手く行かなかったので、さらに検索。。。

次に、vdfuse というコマンドでVHDがマウント出来るらしいという事でやって見たが、これもなぜか上手く行かなかった。
Mounting a VHD in Linux




VirtualBoxでVHDをアタッチ


一晩空けて、ふと思い付いた。VirtualBoxの仮想マシンにVHDファイルをアタッチして見たらどうだろうか。。。結果、これが見事に成功だった。VirtualBox上のUbuntu仮想マシンで、アタッチされたディスクをマウントして無事データを復旧する事が出来た。

ただこの方法では、VirtualBoxでアタッチする時にエラーになって出来なかったケースもあった。確か「Parent file with UUID XXXX-XXX-XXXX-....XXX cannot be found...」みたいなエラーメーッセージだった。

これは、どうもXenServerでスナップショット機能を使っていた場合に起こる様だ。スナップショットによって複数の論理ボリュームにディスクイメージのデータが分散してしまっている場合は、VirtualBoxでは上手くアタッチ出来ないのかも知れない。

あと、qemu-img のコンバート機能を使ってもVHDからRAWフォーマットに変換出来るそうだ。RAWフォーマットに変換出来れば、あとは上記のlosetup と kpartxを使う方法でマウント可能になる。
qemu-img convert -f vpc -O raw data.vhd data.raw



XenConvertで仮想マシンを復旧


データをVHDのイメージから取り出す事は上の方法で出来たが、仮想マシン自体をもう一度XenServerで起動するにはどうしたら良いだろうか。データだけ取り出すよりも、元の仮想マシンがそのまま動かせればそれに越した事はない。

まず考えたのは、上でコピーしたdata.vhdファイルの中味を、ddコマンドでXenServerのローカルストレージに作った領域に書き戻してやるという方法だった。理論的にはこれでバッチリ復旧だと思った。でも実際にローカルストレージに元のイメージと同じサイズの仮想ディスクを作成して、そこに dd コマンドで仮想マシンのイメージをそのまま書きこんで起動しようとしても、エラーになって出来なかった。

そこで最後の手段として、VHDのイメージをXenConverterで読み込んで、変換先に「XenServer」を指定して変換して見た。これはなんとか上手く行った。XenConverterでの処理は1台の仮想マシンにつき2時間~5時間ぐらいかかるので大変だったけれども、最終的にはほとんどの仮想マシンをこの方法で元通り稼働させる事が出来た。

復旧後のXenServerのホストはもちろん「TeraStation無し」で動いている。今はローカルストレージをメインで使って、プラス別のLinuxサーバーのNFSシェアを共有ストレージとして使っている。

「NFSは遅いだろう」という先入観があったけれども、TeraStationのiSCSIの時と比べると変わらない。というよりもむしろパフォーマンスは良くなっているかも知れない。









 

2011年7月13日

Androidでオセロゲームを作ってみる (2) ゲームロジックの実装

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


前回はひとまずカスタムViewを使ってボードを描いて、画面をタッチした時にコマを配置する所まで出来た。

今回はゲームのロジックを実装して実際に遊べる所まで一通り作ってみた。

必要な処理は次の4つになる。
  1. 画面タッチ時に、押された場所がコマを配置しても良い場所かどうか判断する。
  2. 挟んだ相手のコマを自分のコマで置き換える。
  3. 配置可能な場所が無い場合はスキップする。
  4. 両者とも配置可能な場所が無い場合はゲーム終了とする。
これに加えて、「次に配置可能な場所」を示してくれるヒント機能も付けて見た。

小さな丸で表示されているのが「次に配置可能な場所」。
濃い色にすると見辛くなるのでかなり薄くしてある。

ゲーム終了時の結果表示。とりあえずToastでさらっと。

メニューは「設定」、「カウント」、「最初から始める」、「閉じる」の4つ。
設定でヒント表示のON/OFFを変更出来る。

画面を縦表示に固定


マーケットで配布されているオセロゲームをいくつか試した限りでは、全て縦表示で固定されていたので、同じように縦表示で固定する事にした。これはマニフェストファイルでの設定だけなので簡単だ。
Activityタグの属性として、
android:screenOrientation="portrait"
を入れておけばOK。

画面が切り替わる時に盤面の状態を保存


何もしないと、一旦他の画面に切り替わった後で再表示される場合に画面が初期状態に戻ってしまう。

なので盤面の状態を保存しないと行けない。

そのために、Boardクラスに getStateString というメソッドを作った。ここから各セルのステータスを1文字で表したものを連結した64文字からなる文字列を返す様にする。さらに現在どちらの番なのかを表す文字を先頭に付加するので、実際には1+64=65文字になる。

本当の所は、セルの状態は「黒/白/空白」の3つしか無いので情報としては2ビットあれば足りる。そう考えると2bit x 64セル / 8 + 1 = 17という事で、17バイトのバイト配列に格納する事も出来る。ただビット演算などが面倒なので今はこのまま65文字の文字列に格納する事にする。

最初は、この文字列を ViewのonSaveInstanceStateで状態を保存して、onRestoreInstanceStateで復元していた。

ただこれだとひとつ問題がある。端末の「戻る」ボタンで画面を閉じた時にはどちらのメソッドも呼ばれず、状態が失われてしまうのだ。

なのでこれらのメソッドを使わずに代わりにActivityのonPauseとonResumeを使う様に変更した。この場合はデータの保存先はプログラムで適当な所を選ぶ必要がある。今回は保存したいデータが65文字の文字列なので、SharedPreferenceを使う事にした。

これで「戻る」ボタンが押されてもちゃんと状態が保存される様になった。




Viewの invalidate メソッドで必要な部分のみを再描画


画面がタッチされてコマを配置する時に BoardクラスのchangeCellメソッドを呼んでいる。このメソッドはこの配置によって変更された全てのセルをArrayListに入れて返してくれる。View側ではこれを受け取って、順に変更されたセルの領域を指定しながらViewのinvalidateメソッドを呼んでいる。

invalidateを引数なしで呼んだ場合はView全体が再描画されるが、引数rectに領域を指定して呼び出すと、その領域だけが再描画される。もちろん出来るだけ再描画する量を減らした方が処理が速くなる。



drawTextで文字を表示


画面の下の部分に現在どちらの番なのかが分かるように drawRoundRectメソッドで四角の枠を表示した。それから、黒と白それぞれの現在のコマ数をdrawTextメソッドで表示する様にした。

さて、これらの座標やフォントサイズの指定方法に少し悩んだ。

基本的には、何も考えずに数値を指定すれば Android側がDIPというデバイス非依存な単位として扱ってくれるらしい。だから画面の物理的な大きさや解像度が変わってもプログラム側は何もしなくてもいいはずだ。

でもこのページを読むと、なにやらXperiaでちょっとした問題があるとの事。
Y.A.M の 雑記帳: Android multi screen 対応
ただし、Xperia では anyDensity = false にすると、drawable-hdpi ではなく drawable-mdpi に格納した画像が使われるという問題があります。

ということで、私は anyDensity = true にして、XML側で sp や dip でサイズ指定し、コード側で指定しなければならない場合は、XMLで dimension を設定して、それを getDimension してsetWidth などに渡しています。
という事なので、yanzmさんの真似をしてXMLファイルに座標関係の値を全て書き出す事にした。

XMLファイルの書き方とプログラムでの使い方の説明はこちら。
Y.A.M の 雑記帳: Android Dimension 単位

いちいちXMLを編集するのは最初はちょっと面倒だけれども、慣れてしまえば逆に微妙な調整などはこの方がやり易いかも知れない。

デバイス間の解像度の違いについては、実機(800x480)とエミュレータ(480x320)で試して見たところ上手く表示されているので、多分大丈夫。


ゲームロジックの実装


Cellクラスで表されている各セルは「今このセルにコマが配置されたらどのセルとどのセルが裏返されるか」という「裏返し対象セル」のリスト(mReversibleCells)を持っている。手番が変わるごとに毎回全てのセルについてこれを計算し直している。

このリストのサイズが1以上ある場合は、そのセルは次にコマを配置可能な場所であるという事になる。サイズが0なら、タッチされても何も起こらない。

リストのサイズが1以上ある(配置可能な)セルがタッチされた場合は、この前もって計算された「裏返し対象リスト」に格納されているセルのステータスを順に現在の手番を表すステータス(黒または白)に置き換えて行く。

もし配置可能なセルが一つも存在しない状態になった場合は、その手番はスキップ(パス)される。




今回はここまで


さて、これで一応ゲーム開始から終了まで動作する状態になった。

でもここまで来ると、もう少し完成度を上げて見たくなる。やっぱり、コマはきれいな画像で表示したいし、裏返す時のアニメーション効果とコンピュータとの対戦機能ぐらいは欲しい。

どこかにそこそこ強いオセロの思考ルーチンのコードが公開されていないものだろうか。探して見よう。(笑)

ここまでのプロジェクト全体のダウンロードはこちらから。
mikehibm/MiReversi at ver2 - GitHub


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!









 

2011年6月28日

Androidでオセロゲームを作ってみる (1)

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


Androidアプリで2Dグラフィックスを扱う練習をしようと思う。

とりあえず簡単なものという事で、オセロ(リバーシ)ゲームを作りたい。

まずはViewを使って描画するか、SurfaceViewを使って描画するかを決めないと行けない。
GLSurfaceViewというのもあるけれどもこちらは今やろうとしている事にはオーバースペックなので置いておく。

SurfaceViewならAndroidで高速描画ゲームが作れる (1/3) - @IT

【libro】 Google androidプログラミング入門/SurfaceViewによる高速描画/SurfaceViewとは?

Graphics の基本

グラフィックス(1)-Viewクラスへの描画 - 愚鈍人

KENSINのホームページ-Androidで星が流れるプログラムを作成してみる

Viewを使った場合は大体4fpsぐらいしか出ないが、SurfaceViewだと30fpsぐらいは出るみたいだ。この差は大きい。

でも「チェスなどのボードゲームならViewで充分」という記述もよく見かけるので、今回はとりあえずViewで行こうと思う。どうしても必要なら後からSurfaceViewに変更するという事で。

まずはこんな感じで初期状態を描画してみた。それなりに見える。(笑)

横向きの場合はこんな感じになる。




カスタムビューによるグラフィック表示


Activityクラスではカスタムビューをセットするだけ。


ReversiViewというカスタムビューを作ってこの中でオセロの表示処理を全て行う。


onDrawから呼ばれるdrawBoardの中で
Paint paint = new Paint();
としているのはパフォーマンス上は良くないらしい。後でちゃんと直そうと思う。

色の指定なども本当はXMLファイルに出してしまいたいところ。

あと、円を描く前にアンチエイリアスを指定しないとギザギザになってしまうので注意。


モデルとビューの分離


オセロの盤面の状態を保持するのと、ゲームのロジックを記述するために2つのクラスを作成しておこう。盤面全体を表すBoardクラスと、個々のマスを表すCellクラスがあれば良いと思う。

Boardクラスにはwidth, height, top, leftプロパティと、Cellクラスのオブジェクトを格納する2次元配列を用意しておく。

Cellクラスには、width, height, top, leftプロパティと、そのマスがどういう状態にあるかを示す変数を作成しておこう。各マスは、「何もない」「黒」「白」の3つの状態を持つので、それらを表すEnum型も定義する。

Boardクラスのソースコードはこちら。

Cellクラスのソースコードはこちら。


とりあえずここまで


ここまでで、画面をタッチすればそこに黒または白のコマを置く事が出来る様になった。

一応、既にコマが置かれているマスをタッチしても反応しない様になっている。でもまだゲームのロジックと言えるものは何も書いていないので、オセロのルールに関係無く自由にコマを置けてしまうし、挟んでも挟まれても何も起こらない。

しかし、これだけでも「一から自分で作っている」という感じがたまらない。(笑)

プロジェクト全体のダウンロードはこちらから。
mikehibm/MiReversi at master - GitHub


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!







2011年6月15日

AndroidでURLを開く度に自作のアプリを起動する (5) 時間のかかる処理を別スレッドで実行する



HTTP通信を別スレッド化する


Androidアプリケーションでスレッドを使う方法はいろいろあるみたいだが、今回は以下の手順で実装した。
  1. ActivityクラスでRunnableインターフェースをインプリメントする。
  2. android.os.HandlerクラスのインスタンスをActivityクラスで保持する。
  3. Runnableのrunメソッドをオーバーライドする。
  4. 別スレッドでの処理を開始する。


1. ActivityクラスでRunnableインターフェースをインプリメントする。

Activityでインプリメントせずに、単にその都度Runnableのインスタンスを生成しても多分大丈夫。ただこちらの方が多少はメモリの節約になるらしい。(という記述をどこかで読んだ気がする。)
public class IntentReceiveActivity extends Activity implements Runnable {
  ...
}

2. android.os.HandlerクラスのインスタンスをActivityクラスで保持する。

別スレッドでの処理が完了した後にUIスレッド側で実行する処理を受け取る為に必要になる。
private Handler mHandler = new Handler();

3. RunnableのrunメソッドをOverrideする。

別スレッドでの処理が完了した後、mHandlerのpost()メソッドを呼んでUIスレッド側で実行したい処理を渡す。
変数urlとtitleはfinalで宣言しないとハンドラ内から参照する事が出来ないので注意。でも逆に言うとfinalで宣言したローカル変数がそのまま参照出来るので、Bundleに詰め込んでパラメータとして渡すなどという方法と比べるとこの書き方はかなり便利だと思う。
@Override
 public void run() {
  //HTTP通信を実行してページのタイトルを取得
  final String url = getIntent().getDataString();
  final String title = HttpUtil.getTitle(url, getString(R.string.msg_no_title));

  //処理完了後、ハンドラにUIスレッド側で実行する処理を渡す。
  mHandler.post(new Runnable(){
   @Override
   public void run(){
    try {
     //ListViewを更新(該当のurlが既にListViewから削除されていた場合はfalseを返す)
     if (updateList(url, title)){
      //データベースを更新。
      HistoryDb.save(url, title);
     }
    } catch (Exception e) {
        showErrorDialog(e);
    }
   }
  });
 }

4. 別スレッドでの処理を開始する。
new Thread(this).start();
実行時のイメージはこんな感じになった。処理中はくるくる回るアイコンを表示しておくと結構いい感じ。

HTTP通信処理中の表示(一番上の行)


複数のスレッドが同時に走っているのが分かる

処理中とそうでない時の表示の切り替えは ListItemAdapter クラスの中でtitleがセットされているかどうかで判断して行っている。その部分のソースは こちら。



ネットワーク接続が可能かどうか事前に調べる


ネットワークに接続されていない場合はタイトルの取得を行わずにURLだけを一覧に追加する様にした。接続されているかどうかの判断は下の関数で可能だ。

private boolean isConnected(){
  ConnectivityManager cm = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE);
  NetworkInfo info = cm.getActiveNetworkInfo();
  if (info == null){
   return false;
  } else {
   if (info.isConnected()){
    return true;
   } else {
    return false;
   }
  }
 }
AndroidSDK開発のレシピ―104個のレシピで学ぶAndroidアプリ開発の極意 塚田 翔也 (著)より



ブラウザを自動的に開くかどうかを設定で選べる様にする


設定画面にチェックボタンを追加して、インテントで受け取ったURLを自動的にブラウザで開くかどうか設定出来る様にした。ONの場合とOFFの場合で試してみた所、自動的にブラウザを開かずに単に一覧リストを表示するだけの方が使いやすい様な気がする。




リダイレクト時に複数のエントリが追加されてしまう問題


自動的にブラウザを開く場合にWebサーバーからリダイレクトのレスポンスが返って来ると、標準ブラウザはリダイレクト先のURLを開く為にまたインテントを発行する様になっている。そのインテントを再度このアプリが受け取ると、結局一覧リスト上はURLが微妙に違うだけで内容は同じページ、という行が複数追加されてしまう事になる。

例えば、
http://d.hatena.ne.jp/hogehoge/
というサイトを開いて、
http://d.hatena.ne.jp/hogehoge/touch
にリダイレクトされた場合にこの現象が起きる。

これを防ごうとすると、

「インテントを受け取った時にそれがリダイレクトされて来たものなのかどうか判定してそうであれば無視する。」

という動作が出来ればいいのだが、その判定方法が見つからないのでこの方法は今の所実現出来ていない。

次善の策としては、

「時間制限を設けて、前のインテント処理から一定の時間内(20秒とか)は同じタイトルの行を追加しない様にする。」

というやり方が考えられる。

ただ、ややこしくなりそうなのでそこまで実装するのは今回は見送り。(笑)



全ソースと次の目標


今回の全ソースはこちらからダウンロード可能になっている。
mikehibm/android-browser-intent03 at intent04 - GitHub

他にも「設定画面で標準以外のブラウザを選べる様にする」とか、「ListViewにチェックボックスを付けて処理対象を選択出来るようにする」とか、検討した機能はあった。

「メールを送る代わりにどこかのサーバーにデータを送信する」というのも面白そうだ。Google DocumentsのSpreadsheetに書き込めば意外と使い途があるかも知れない。

ただこのアプリを変更するのは今回で一旦ストップしておこうと思う。

実を言うと、「気になったリンクの一覧を簡単にPCに送る」というもともとの目的は Google Readerの「スターを付ける」機能を使えばほぼ達成出来る事に気付いてしまったからだ。
Googleリーダーに「後で読む」っぽい機能をつける方法 : ライフハッカー[日本版]
情報の収集、閲覧、集約は全てGoogleリーダーに任せることにした。 - iPhoneとiMacと自分と…

「スターを付ける」を使い出してからは、この自作アプリを使うのはTwitterなどGoogle Readerの管理外のURLをPCに送りたい時だけになってしまった。まあそれでも完全に用が無くなった訳ではないのでよしとしよう。


それに、他にもっと面白そうな事がたくさんある。あり過ぎて困る。

次からはSurfaceViewを使ったグラフィック処理や、SoundPoolを使って音を出す事などをやってみようかなと思っている。

あと、OpenGLにも興味を惹かれる。何か子供向けの教育アプリでも作れないものかなとこんな本を読んでみたりもしている。
OpenGLで作る Android SDKゲームプログラミング 中島 安彦 (著), 横江 宗太 (著), 株式会社パンカク (著)
簡単なものから始めて少しずつ機能を追加して行く構成になっていて、とても分りやすい。


AndroidでURLを開く度に自作のアプリを起動する







2011年6月7日

米国版HTC EvoがGingerbreadにアップデートされて変わった点とは

6月3日からGingerbread(Android 2.3.3)への公式アップデートが可能になったので、早速やって見た。

Androidのバージョンがほぼ最新に近い2.3.3になるという事で、事前の情報だとこんな感じで色々と変わるみたいだった。
Nexus Sを2日使った感想(ソフトウェア編) | juggly.cn
Android 2.3「Gingerbread」スクリーンショットツアー~待望のあんな機能やこんな機能が! : ライフハッカー[日本版]

さて、ワクワクしながら実際にアップデートしてみた所、見た目はHTCのSense UI(以前と同じバージョン)のままなのでほとんど違いに気付かない。これには正直がっかり。


Sense UIが悪い訳ではないけれど、やっぱり新しいもの好きなので少しは見た目も変わって欲しかった。 まあ別のHomeアプリを入れればいくらでも変えられるので問題は無いけど。

Androidのバージョンはこの通り、確かに2.3.3に変わっている。




バッテリー使用状況のグラフ表示

個人的には一番目立った変更点はこれだった。


バッテリーの使用状況が分かり易くなったのでこれは確かに有り難い。



全体的な動作の改善

確かに個々の動作が前よりもキビキビとしている様に感じる。もしかすると気のせいかも知れないというレベルだけれども、なんとなく以前よりも動作が軽くなった様に思える。


と、実は今日まで普通に使っている分にはこれぐらいしか違いに気付かなかった。 以下、上のリンクの記事などを見ながら細かく変更点をチェックした。



SIP通話に標準対応

Menu→Settings→Callを開くと一番下に「Use Internet calling」というオプションが追加されている。ここでWifi接続時にSIP通話機能を使うかどうかを設定出来る(中央の画像)。 またその上の「Accounts」を開くと、SIPアカウントを複数追加する事が出来る様になっている(右の画像)。

試しに以前に構築したテスト用のAsteriskサーバーに接続して発信した所、問題無く使えた。これは社内にSIPのVoIP環境がある所ではものすごく有用な機能ではないだろうか。Android携帯さえあれば内線の受信・発信も出来る様になるからだ。もちろん今までもSIP電話アプリを入れれば出来た事だが、OSの標準機能として統合されているという事で、さらに安心して使える様になりそうだ。


但し自宅のWifi接続を使って会社のLANにVPN接続してからSIPで発信しようとすると出来なかった。 VPN経由はダメみたいだ。




テキストのコピー&ペースト

テキストを選択する際の「つまみ」の画像などはSense UIの関係か標準とは違う見た目になっている。 以前より多少使い易くはなったかも知れない。

でもペーストする時に勝手に両端に空白を入れられてしまうのはどうも変な感じ。

また、日本語の文字列を選択状態にすると「コピー」の選択肢が出ずに「ペースト」しか選べなくなる場合があった。これはどう見てもバグっぽい。もしかすると使っているIMEとの相性もあるのかも知れないが、よく分からない。



電源ボタン長押しで再起動が可能に

ほとんど使う事はないけども、これ(Restart)も確か以前は無かったような気がする。


そんなこんなで、Gingerbreadにアップデートはされたものの、日常的な使用感という意味ではこれまでとほとんど違いが無い。バッテリーの持ちが特に良くなったという感じも無い。

とりあえず大きな不具合が無さそうだと言う事だけでも有難い、…とポジティブに受け取っておこうと思う。(笑)






.

2011年6月2日

米国でHTC EvoのGingerbreadへのアップデート開始へ

Sprintから公式アナウンスがあった。


Sprint Community: Announcements: HTC EVO 4G from Sprint Gets Android Gingerbread Update

Gingerbreadが公開されたのが2010年の12月6日との事なので、ちょうどその半年後にEvoに反映される事になる。

よし、いよいよ来週からGingerbreadだー。


この際だからFroyo(Android 2.2)とGingerbread(Android 2.3)の違いを予習しておこう。

Android 2.3 Gingerbreadの新機能のまとめ | Macin' Blog

【レビュー】Google携帯「Nexus S」の実力は? - 【後編】Androidの"買い時"到来 (1) Gingerbreadで何が変わったのか | 携帯 | マイコミジャーナル

#android しづ子ファッション通信 Σ(^A^;) : で結局 gingerbread って何が変わったの? - Froyo から変化しているUIを集めてみた

Android 2.3(Gingerbread)における新機能、変更点のメモ | juggly.cn

Difference Between Android 2.2 (Froyo) and Android 2.3 (Gingerbread)

Android 2.2 (Froyo) Vs 2.3 (Gingerbread)

う~ん、NFC非搭載の端末では正直言ってあんまり劇的に感動させられる様な違いは無いのかも知れない。

文章に表せない点で使い勝手が良くなっている事に期待したい。あとはVoIPに標準対応という辺りが面白そうかな。






 

2011年5月31日

AndroidでURLを開く度に自作のアプリを起動する (4) HTTP通信を行ってタイトルを取得する



前回まででSQLiteデータベースに履歴一覧を保存出来る様になり、少しは本格的(?)にアプリらしくなって来た。

今回はURLからWebページのタイトルを取得して表示する様に変更したい。

スレッドを使って非同期にHTTP通信を行ない、処理結果をListViewに反映すると言う、このブログの流れからすると今までで最高レベルの難易度(笑)になる内容だ。実際、作っていて「あ、そうだったのか」と気付かされる事が多くとても勉強になった。プログラミングは実際に手を動かして試行錯誤しながら習得するに限ると改めて感じた。


前回までの画面

今回の画面
URLだけでなくページのタイトルも表示
すると一気に実用的(な感じ)になった。
ついでにListViewの各行の背景にグラ
デーションを使ってみた。




URLからHTMLを取得する


まずはHTTP通信でURLが示すWebページにアクセスしてHTML文字列を取得する部分だ。HttpUtilというクラスをプロジェクトに追加して、そこに色々書いて行く事にした。

そのHttpUtilクラスに作ったgetHtmlメソッドはこんな感じになった。

public static String getHtml(String url){
        String result = null;
        
        HttpGet httpGet = new HttpGet(url);
        DefaultHttpClient client = new DefaultHttpClient();
        HttpParams httpParams = client.getParams();
        
        HttpConnectionParams.setConnectionTimeout(httpParams, 1000 * 10);   //接続のタイムアウト(ms)
        HttpConnectionParams.setSoTimeout(httpParams, 1000 * 60);           //データ取得のタイムアウト(ms)
        client.setParams(httpParams);

        try {
            // レスポンスを取得
            HttpResponse httpResponse = client.execute(httpGet);
            int status = httpResponse.getStatusLine().getStatusCode();

            if (HttpStatus.SC_OK == status){
                //Content-Typeを取得
                Header[] headers = httpResponse.getHeaders("Content-Type");
                if (headers.length > 0) { 
                    String contentType = "";
                    contentType = headers[0].getValue();
                    if (contentType.contains("text/html")){

                        //HTMLを取得
                        HttpEntity entity = httpResponse.getEntity();
                        if (entity != null){
                            byte[] arr = EntityUtils.toByteArray(entity);
                            
                            //文字エンコーディングを判定
                            String encoding = detectEncoding(arr);
                            if (encoding == null) encoding = findEncoding(entity, arr);
                            
                            //判定されたエンコーディングでバイト配列から文字列に変換
                            result = new String(arr, encoding);
                            
                            //entityのリソースを解放
                            entity.consumeContent();            
                        }
                    }
                }
            }
        
        } catch (ClientProtocolException e) {
            Log.d(TAG, e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, e.getMessage());
        } finally {
            //HTTPクライアントを終了させる
            client.getConnectionManager().shutdown();
        }
        
        return result;
    }


予想もしなかった部分でつまずいた。文字エンコーディングの判別だった。

SDK標準のDefaultHttpClientクラスが上手く処理してくれるのかと思っていたらそうではなく、取得したバイト配列を文字列に変換する部分は自分でやらないと行けない様だ。

最初は自前でResponseヘッダのContent-Typeを見たりmetaタグのcharsetの値を見たりして、なんとか上手く動くようにはなったのだが、それでもたまに表示したタイトルが文字化けしている事があった。なぜだろうと該当のWebページのソースを表示して見ると、metaタグでの指定はEUCなのに実際にはUTF-8でエンコーディングされている、などという様にResponseでの指定内容と実際のエンコーディングが異なっているケースがある事が分かった。

結局、Responseに含まれているエンコーディング指定は完全には信用出来ないのだ。ガーン!

それで色々調べた挙句、「juniversalchardet.jar」というライブラリを利用させてもらう事にした。
juniversalchardet - Java port of universalchardet - Google Project Hosting

juniversalchardet - Javaについて

このライブラリを使っている部分はこんな感じ。
private static String detectEncoding(byte[] arr){
        byte[] buf = new byte[4096];
        ByteArrayInputStream stream = new ByteArrayInputStream(arr);
        UniversalDetector detector = new UniversalDetector(null);

        try {
            int nread;
            while ((nread = stream.read(buf)) > 0 && !detector.isDone()) {
                detector.handleData(buf, 0, nread);
            }
        } catch (IOException e) {
            Log.d(TAG, e.getMessage());
        }
        detector.dataEnd();
        return detector.getDetectedCharset();
    }


2011/06/02 追記:
下のページではjuniversalchardetについて「デコード時の文字コードがEUC-JPだった場合に、判定に失敗する可能性が非常に高い。」と書かれているので、要注意。
エンコード時の文字コードが不明なURLをJavaでデコード | grush-blog




HTMLからタイトルを抜き出す


HTMLが取得出来たら、次は<title>タグの始まりと終わりを探してその間の文字列を抜き出せばOKだ。

正規表現を使えば楽勝だ、と思っていたら、これも意外と手こずってしまった。

単純に
<title>タイトルの文字列</title>
という形になっていれば問題無いのだが、
<title id='aaa'>タイトルの文字列</title>
などの様に開始タグに属性が付いている事もある。

それに、
<title>(改行)
タイトルの(改行)
文字列(改行)
</title>
みたいにtitleタグの中に改行が入っているケースもある。正規表現は行単位に処理される(と思う)ので、開始タグと終了タグの間に改行が入ってしまうと途端に話がややこしくなる。(多分。)

改行については最初に除去してから処理すればいいかと思ってやってみたけれども、そうすると文字列が長い場合に大量にCPUを使って戻って来なくなったりするので簡単には行かなそうだった。

そんなこんなで結局正規表現を使うのは止めて indexOf を駆使(?)して自前で抜き出す事にした。

本当はDOMを操作するライブラリなどを使えばこの辺を簡単にしてくれるメソッドが用意されているのかも知れない。

実際のタイトル抽出部分のコードがどうなったかは、こちらから参照可能だ。javaの達人であればもっとキレイに書けると思うので、ちょっと恥ずかしい。

それから、HTML文字列に数値文字参照(例:&#65374;→ 「〜」)が含まれている場合や、実体参照(例:&gt;→「>」)が含まれている場合もあったので、タイトルを抜き出した後それぞれ変換処理をかませるようにしておいた。

単にHTMLからタイトルを抜き出すだけの処理なのに、結構色々あるものだ。

とここまで書いて今また一つ気付いてしまった。 コメントアウトされたtitleタグへの対応だ。
<!-- <title>コメントアウトされたタイトル</title>-->
<title>抽出して欲しいタイトル</title>
というパターンになっていると今回のコードだとコメントアウトされた方のタイトルを抜き出してしまう。こう言うケースにも対応しようと思うと、やはり何らかのDOMライブラリを使う方がいいのかも知れない。




全ソースと次の目標


今回のソースでHTTP通信の処理は別スレッドで行う様になっている。Androidプログラミングでは必須とも言えるマルチスレッド化については、長くなりそうなので別のエントリで書こうと思う。

ダウンロードはこちらから。
mikehibm/android-browser-intent03 at intent04 - GitHub


AndroidでURLを開く度に自作のアプリを起動する







.

EclipseでAndroidのプログラムを実行しようとして変なxml.outファイルが出来てしまう場合

EclipseでAndroidの開発をしている時にすごく気になる事があった。

CTRL+F11で「実行」のはずが、何も起こらずに、代わりによく見ると変なファイルが出来ているのだ。

エディタでXMLファイルを開いている場合に発生する。

javaのソースを開いている場合は問題ないので、CTRL+F6を押してjavaのソースファイルを開いた状態にしてから実行する様に気を付けていたのだが、急いでいる時はついつい忘れてしまって、不便な事この上ない。

その解決方法が今日やっと見つかった。
C/J Prog's Blog: EclipseでF11を押すとAndroidManifest.xml.outが生成されるときの対処
- EclipseにADT PluginとWTP Pluginをインストールしている
- Androidプロジェクトで作業している
- xmlファイルを開いている
- (Ctrl+)F11を押す

これはEclipse WTPの機能で、xmlファイルに対して Run As > XSL Transformation を実行したことになるため。

以下の設定を変更すればOK。

Window > Preferences > Run/Debug > Launching を開き、
"Always launch the previously launched application' in the 'Launch Operation' section."
のラジオボタンを選択する。

との事。助かりました。

これですっきりした。ww







.

2011年5月19日

AndroidでURLを開く度に自作のアプリを起動する (3) SQLiteデータベースを使う



先日のBloggerの障害の影響でこの前書いたエントリが消えてしまった。「ほぼ全てのデータが復旧された」との事だったが、僕が先週の金曜日に朝6時に起きて1時間半もかけて書いた文章はとうとう戻って来なかった。(涙)
Googleの「Blogger」が20時間半に及ぶサービス障害、週末にほぼ復旧 - ニュース:ITpro
やっぱりブログ記事もどこかにバックアップしておいた方が良いのだろうか。と言ってもローカルに保存すればハードディスクのクラッシュや記録メディアの紛失が心配だし、別のクラウドサービスに保存するにしても結局データが消えてしまうリスクは無くならない。考え出すと切りがない。。。

気を取り直して、部分的に残っていた下書きからもう一度書き直す事にした。


さて、


前回までで「ブラウザで表示したURLの履歴をメールで送信する」という目的は一応達成出来た。

ただしこの状態だと、何らかの理由でこのアプリのプロセスが終了させられた場合に履歴の一覧が消えてしまうという問題がある。単純に他のアプリに隠れて画面の裏に回っただけではすぐにプロセスが終了する事はないのだけれども、他のアプリがメモリを大量に使用して空きメモリが不足した場合や、単に長い時間プロセスがアクティブにならなかった場合などにシステムの判断で終了させられる事になる。

今回はプロセスが終了しても履歴の一覧を保持出来る様に、SQLiteのデータベースに保存する様に変更したい。

データベースを開く


AndroidでのSQLiteデータベースの扱い方は大体下のリンク先を見れば把握出来る。
Androidアプリのデータ保存方法の一つ「SQLite」の使い方 SQLiteOpenHelper編 | mucchinのAndroid戦記

Androidアプリでのデータベース基礎 ~速習! Androidアプリケーション開発(4)~(1/3):CodeZine

データを簡単に保存する方法(SQLite編) « Tech Booster
データベースを新規に作成するのではなく、予めアプリのリソースとして組み込んでおいたものをコピーする方法はこちら。
Y.A.M の 雑記帳: Android あらかじめ作成した SQLite database をアプリに取り込む

今回は1テーブルしか使わないので、HistoryDbというクラスを作ってその中でデータベースに関する操作をすべて行う事にしようと思う。もし複数のテーブルがある場合は、データベース全体を表すクラスと各テーブルに対応したクラスに分けた方が多分すっきりするだろうと思う。

とりあえずまずは init() メソッドを作って、最初に必ずこれを呼び出してデータベースが無ければ作成する様にした。
public static void init(String pkgName) throws Exception{
    DbPath = "/data/data/" + pkgName + "/" + DBNAME;
    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DbPath, null);
    try {
        //テーブルが無ければ作成する。
        createTable(db);
    } catch (Exception e) {
        throw e;
    } finally{
        db.close();
    }
}
 
private static void createTable(SQLiteDatabase db) {
    String sql = "CREATE TABLE IF NOT EXISTS " + TBL_HISTORY
  + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT, title TEXT);";
    db.execSQL(sql);

    //urlにインデックスを追加。
    sql = "CREATE INDEX IF NOT EXISTS 'main'.'ix_history_url' ON 'history' ('url' ASC)";
    db.execSQL(sql);
}



データベースから一覧を取得する


HistoryDbクラスに selectAll() というメソッドを作ってその中で全件を取得する。取得した結果をループして、1レコード分の情報をHistoryDbクラスのインスタンスのプロパティ(正確には手抜きしたのでパブリックなメンバー変数)にセットし、ArrayListに追加して行く。
public static ArrayList selectAll() throws Exception{
    ArrayList array = new ArrayList();
  
    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DbPath, null);
    try {
        //全件を取得する。
        String[] columns = {"_id", "url", "title"};
        String where = null;
        String having = null;
        String group_by = null;
        String order_by = "_id DESC";
  
        Cursor cursor = db.query(TBL_HISTORY, columns, where, null, group_by, having, order_by);
        while (cursor.moveToNext()){
            HistoryDb hist = new HistoryDb();
            hist.id = cursor.getInt(cursor.getColumnIndex("_id"));
            hist.url = cursor.getString(cursor.getColumnIndex("url"));
            hist.title = cursor.getString(cursor.getColumnIndex("title"));
            array.add(hist);
        }
    } catch (Exception e) {
        throw e;
    } finally{
        db.close();
    }
    return array;
}


結果のArrayListを受け取ったActivity側では、ArrayListの情報をまたループしてListViewにバインドされた adapter に入れ直している。ちょっと冗長なやり方になってしまった。
//DBから全件取得。
ArrayList array = HistoryDb.selectAll();

//ListViewに表示。
adapter.clear();
for (HistoryDb hist : array) {
    adapter.add(hist.url);
}




データベースに書き込む


HistoryDbクラスに save() メソッドを作って保存処理を書く。既にDBに存在しているかどうかは本来は主キーである「_id」の値で判断するべきだが、今回はURLでも一意になるのでURLを検索キーとして使った。まずUPDATEを実行してその結果が0件であればINSERTを行うというロジックになっている。
public static void save(String url, String title) throws Exception {
    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DbPath, null);
    try {
        //同じURLが既にあれば更新する。無ければ挿入する。
        insertOrUpdateHistory(db, url, title);
    } catch (Exception e) {
        throw e;
    } finally{
        db.close();
    }
}
 
private static void insertOrUpdateHistory(SQLiteDatabase db, String url, String title) {
    ContentValues values = new ContentValues();
    values.put("url", url);
    values.put("title", title);

    String[] args = { url };

    //同じURLが既に保存されていれば更新する。
    int n = db.update(TBL_HISTORY, values, "url = ?", args );
    if (n == 0){
        //無ければ挿入する。
        db.insert(TBL_HISTORY, null, values);
    }
}




メニューにアイコンを付ける


メニューにアイコンを付けて見た。すると一気に見栄えが良くなった。アイコン一つで、不思議なものだ。アイコンの指定方法は、メニューを定義しているXMLファイル内のitemタグの属性として、
android:icon="@android:drawable/ic_menu_XXX"
を付けるだけだ。


Androidに標準で含まれているアイコンの一覧は、こちらで調べられる。
Taosoftware: Android メニューアイコン



今回の全ソース


今回の全ソースはこちらからダウンロード出来る様になっている。
mikehibm/android-browser-intent03 - GitHub

さて、ここまででデータベースに履歴一覧を保存出来る様になり、少しはアプリケーションらしくなって来た。

せっかくここまで来たら、一覧にURLだけでなくページのタイトルも表示したくなって来た。タイトルはインテントでは飛んで来ないので、アプリ内でHTTP通信を行って取得する必要がありそうだ。それから、ページがリダイレクトされた場合にリダイレクト元とリダイレクト先の両方のURLが履歴として残ってしまうという問題も、出来ればなんとかしたい。

さて、上手く行くかどうか、それは次回のお楽しみという事で。


AndroidでURLを開く度に自作のアプリを起動する







.

2011年5月17日

富士通の「Windows 7」携帯 LOOX F-07C に欲しかった3つの機能

こんな変わった携帯がドコモから発表されたらしい。
「超ちっこい7搭載PC(ケータイ機能もあるよ)」というところがいいですよ:LOOX Uを超えたケータイサイズの新LOOX──“PC”として写真と動画で見る「Windows 7ケータイ F-07C」 (1/2) - ITmedia +D PC USER
これは面白い。ほとんどネットブックに匹敵するスペックで、Windows Phone 7ではなく通常のWindows 7 Home Premiumが動くとの事。

正直言って僕の理想の「超小型PC携帯」に近付いている。もし機会があったら実際に手に取って触ってみたい。

この発表に触発されて、僕が思う「理想の超小型PC携帯」とは何だろうかと考えてみた。とりあえず思いつくのはこんな感じだろうか。

  • 外部モニター、マウス、キーボードが接続可能
    (これは F-07C で可能らしい。)

  • 重さは170グラム程度かそれ以下
    (F-07Cは約218グラム)

多少の重量オーバーには目をつぶるとしても、欲しかったのは次の3つだ。

  • 外部モニター接続時の解像度は1,680 x 1,050以上
    (F-07Cは1280×720ドット)
  • 10Mbps以上の高速で安定したネット接続
  • Wifiテザリングが可能



もしこんな端末があれば、会社と自宅の両方にクレードル+外部モニター+キーボード+マウスのセットを用意しておいて、会社に着いたら携帯をクレードルにセットして仕事、家ではベッドに寝転がってネット閲覧、大きな画面で見たくなったらクレードルにカチャッ、と言う使い方が出来るのに。


クレードル部分は妻や子供の携帯と共用で使える用になっていればなお嬉しい。その上でネットワークの速度が10Mbps以上あれば自宅のケーブルインターネットは要らなくなる。Wifiテザリングが出来ればiPodやiPadからのネット接続も問題無しだ。

でも考えてみると、僕の使い方だと職場のPCにリモート接続する事がメインの用途になりそうだ。そうだとすると、端末自体のOSは何であっても大した違いは無い様な気がして来た。結局の所、必要なのは携帯機能が付いたシンクライアントという事か。

さて、この流れで行くと数年後にはどんなスペックの端末が出て来るのだろうか、楽しみだ。PCはみんなポケットに入れて持ち歩くのが当たり前になるのだろうか。究極的には腕時計サイズになれば嬉しいのだけれど。(笑)







.

2011年5月9日

AndroidでURLを開く度に自作のアプリを起動する (2) メール送信機能と設定画面の追加



前回はURLを開くインテントを受け取ってListViewに追加した後、標準ブラウザで開くという大まかな流れを作成した。

今回は、ListViewに保持しているURL履歴の一覧をメールで送信するという部分と、その為に必要な設定画面を実装したい。

全ソースがダウンロード出来るリンクを最後に付けておいたので、興味のある方はぜひダウンロードしてあれこれ試してもらえればと思う。




メールを送る


「メールを送る」とは言っても、アプリから直接メールサーバーに接続してデータを送るのは大変だ。やろうと思えばその為のライブラリもある事はあるらしい。
Android: マルチスレッドでJava Mail
Downloads - javamail-android - JavaMail port for the android plateform - Google Project Hosting

が、今回は単純にデフォルトのメーラーを起動する為のインテントを発行するだけにしようと思う。

メーラーを起動する為のインテントは、次の方法で発行出来る。
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:" + to_addr));
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, message );
startActivity(intent);

メニューボタンから「メール送信」を選ぶとListViewの内容をメールで一括送信する様にした。


本当は startActivity の代わりに startActivityForResult を使って実際に送信ボタンが押されたのか、キャンセルされたのかも取得したかったのだが、どうも上手く結果が返って来なかったのでこれは断念した。



設定画面を作る


とりあえず3つの項目を設定出来る様にしておいた。


設定画面の作り方はこちらが参考になった。(特に設定された内容をサマリーに表示する部分)
Y.A.M の 雑記帳: Android 設定画面を作成する
Y.A.M の 雑記帳: Android Preference の summary を動的に変更



ListViewで項目がタップされた時の処理


ListViewで項目がタップ(クリック)された時の処理は、AdapterView.OnItemClickListenerクラスのインスタンスをsetOnItemClickListenerメソッドでListViewにセットすれば記述出来る。

例えばこんな感じになる。
//リストの項目がタップされた時の処理
list.setOnItemClickListener(
    new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id) {
            ListView listview = (ListView)parent;
            selected_url = (String)listview.getItemAtPosition(position);
            dialog.show();
        }
    }
);

今回は項目がタップされたら処理を選択するダイアログを表示する様にしてみた。


この処理は1回だけ実行すれば良いのでActivityのonCreateで行う様にする。
//リストの項目がタップされた時に開くダイアログを準備。
String[] str_items = { getString(R.string.mnu_browser) , 
                       getString(R.string.mnu_send), 
                       getString(R.string.mnu_delete)};
final AlertDialog.Builder dialog = new AlertDialog.Builder(this)
        .setIcon(R.drawable.icon)
        .setTitle(getString(R.string.mnu_select))
        .setItems(str_items, 
            new DialogInterface.OnClickListener(){
                //ダイアログの項目が選択された時の処理。
                public void onClick(DialogInterface dialog, int which) {
                    switch (which){
                        case 0:
                            openBrowser(selected_url);
                            break;
                        case 1:
                            sendEmail(selected_url);
                            break;
                        case 2:
                            deleteUrl(selected_url);
                            break;
                        default:
                            break;
                     }
                  }
              }
        );

参考にしたサイト: 色々なダイアログの例があるので便利。
八角研究所 : Android で再開する Java プログラミング(14) - ダイアログを制するものがAndroidを制する!



今の所の問題点


ここまでで一応URLの履歴をメールで送信出来る様にはなった。

ただ、ちょっと気になる点がある。それはブラウザでリダイレクトが発生する度に新たに「ブラウザで開く」インテントが発生するという事だ。結果として履歴の一覧にはリダイレクト前と後のURLがそれぞれ残る事になる。

例えば、bit.lyなどの短縮URLサービスを使った場合や、スマートフォンからのアクセスを自動的に専用のURLに誘導する様になっているサイトなどでこの現象が起きる。

実質的に同じページを指しているのに履歴一覧に複数行表示されるのは、ちょっと都合が悪い。リダイレクト後のインテントを受け取った時にそれが「リダイレクトされたものである」という事が認識出来ればリストに追加しない様に出来るのだが、今の所その方法を見つけられていない。



今回の全ソース


mikehibm/android-browser-intent02 - GitHub

次回はいよいよSQLiteを使ってローカルデータベースにURLの履歴を保存する様に変更してみたい。

ちなみに、現在大活躍中の参考書はこちら。まだAndroidの世界で右も左も分からない自分には必携の書になっている。
AndroidSDK開発のレシピ―104個のレシピで学ぶAndroidアプリ開発の極意


AndroidでURLを開く度に自作のアプリを起動する






.

2011年5月2日

AndroidでURLを開く度に自作のアプリを起動する (1) インテントを受け取る



最近、ようやく少しずつAndroidのプログラムを作り始めている。

とりあえず必要に迫られて試して見たのが、

「ブラウザで開く」というインテントを受け取ってURLの履歴を保存し、再度そのURLをブラウザで開く

というアプリ。
単純なアプリだが、実際に作って見るとインテントの面白さを実感出来る。



動作の概要


例えば「はてなブックマーク」のアプリからリストの項目をタップすると「ブラウザで開く」インテントが発行される。(もちろんアプリはURLからブラウザを開く事が出来るものであれば何でも良い。)

インテントに対応出来るアプリが複数ある場合は選択ダイアログが表示される。

自作アプリ「intent01」を選択すると、標準ブラウザーが開く。

「戻る」ボタンでブラウザを閉じると自作アプリに戻る。開いたURLの履歴が表示されている。



インテントを受け取る


Androidのインテントには、起動するコンポーネントを指定して発行される明示的インテントと、特に指定しない暗黙的インテントがある。面白いのは暗黙的インテントの場合だ。

暗黙的インテントが発行された場合は、システムがそれを処理出来るアプリを自動的に見つけて起動してくれる。候補となるアプリが複数見つかった場合は、選択する為のダイアログが表示され、ユーザーがどれを起動するか選択出来る仕組みになっている。

つまり自作のアプリを「"ブラウザでURLを開く"インテントの処理が可能です」と宣言しておけば、システムがそれを認識して該当の暗黙的インテントが発行された時に選択ダイアログに表示してくれる訳だ。

この宣言をするには、AndroidManifest.xmlファイルのactivityタグの下に次の記述を追加するだけで良い。

<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="http" />
    <data android:scheme="https" />
</intent-filter>

アプリ内では、getIntent()でインテントを取得出来る。単にホーム画面のランチャーから起動されたのか、別のアプリで「URLをブラウザで開く」操作をされて暗黙的インテント経由で起動されたのかは、取得したIntentのgetAction()で判断可能だ。


if (Intent.ACTION_VIEW.equals(intent.getAction()) ){
    //暗黙的インテント経由で起動された時の処理
} else {
    //ランチャーから起動された時の処理
}



ListViewに表示する


インテントに詰め込まれたURLは、
String url = intent.getDataString();

で取得出来る。

ここまで来れば、後はListViewにURLの文字列を追加する部分さえ作れば履歴の表示が出来る。

注意が必要だったのは、ListViewに表示するArrayAdapterをstaticで宣言する事。でないと起動される度に毎回newされてしまい、リストには最後の1件しか表示されなくなってしまう。



標準ブラウザでURLを開く


ListViewに追加した後、標準ブラウザでこのURLを開く為には、明示的にブラウザを指定して同じインテントを発行し直せば良い。
intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
startActivity(intent);

setClassNameの1つ目の引数はパッケージ名、2つ目はクラス名なので、これらを変えればもちろんFireFoxなど他のブラウザを呼び出す事も可能だ。(オプションで設定可能にしたら便利かも知れない。)

ブラウザからハードウェアのバックボタンで自作アプリの画面に戻るとちゃんとListViewにURLが追加されている。素晴らしい。(笑)

もちろんこのままだとリストの内容はstatic変数で保持しているだけなので、アプリのプロセスが終了した時点できれいさっぱり失われてしまう。本来ならローカルデータベースに保存するなどの処理を追加する必要がある。(それは次回以降のネタとして取って置きたい。)



結局何がしたかったのか


で、実は何がしたかったかと言うと、この履歴一覧の内容をPCに送りたいのだった。

最近寝る前に携帯で気になるニュースやブログ記事をピックアップしておいて、翌日の空き時間にPCでじっくり読むというスタイルが定着して来たので、出来るだけ簡単にURLの一覧をPCに送信する方法はないかと考えていて思いついた方法がこれだ。

もちろん「Read It Later」やその類のアプリもちょっと試しては見たけれども、いちいちユーザーアカウントを作るのも面倒だし、そもそも送信元のアプリから「共有」もしくは「送る」という操作をしないといけないのが面倒に感じていたのだ。例えば、「Google Reader」からだと一覧の記事を長押ししてもそこには「送る」メニューは無く、記事の詳細を開いた上でメニューボタンを押して、「その他」から「Send」を選んで、それからようやく送り先のアプリを選べる事になる。

記事のタイトルだけチェックしてさくさくとPCに送りたいと思うと、いちいち「送る」メニューを選ぶのはどうも使い辛い。

今回の自作アプリだと、一度デフォルトブラウザとして選択しておけば、後はリンクをタップして行くだけで裏で履歴を取ってくれるのでかなり時間が短縮出来そうな気がする。

という事で、次回からメール送信やローカルデータベースへの保存の部分を作ってみたい。

ちなみにここまでのプロジェクト全体のソースはこちらからダウンロード可能になっている。(初めてGitHubに上げて見た。^^)
https://github.com/mikehibm/android-browser-intent01

設定ファイルを除いた本体のソース(Intent01Activity.java)は68行しかないので興味がある方はぜひどうぞ。






追記: 標準ブラウザが格納している履歴を取得する方法

こちらに標準ブラウザが保存している履歴をコンテントプロバイダー経由で取得する例を見つけた。機会があればこの方法も試して見たい。
furafura times: 標準ブラウザのコンテントプロバイダから履歴を取得
WebView逆引き - でこちく備忘録



AndroidでURLを開く度に自作のアプリを起動する









.