2011年6月29日

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月16日

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月8日

米国版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月3日

米国で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年6月1日

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







.