2011年10月26日水曜日

android-sdk r14への移行

android SDKをr14に移行してみた。r13までのように、SDKを入れ替えてツールをダウンロードすれば終わりかと思ったらそうではなかったのでここに書いておく。

android-sdkを所定の位置に置いた後で、androidコマンドを起動してツール類を全部インストールする。ここまでは問題なし。
次にちゃんと動くかどうかコンパイルしてみる。

% ant debugとすると
BUILD FAILED
/xxxxx//build.xml:65:

Error. You are using an obsolete build.xml
You need to delete it and regenerate it using
        android update project
あらら、build.xmlファイルを新しくしなくてはいけないようだ。
android update projectとしろと書いてあるのでそうする。

% rm build.xml
% ant update project -p ./
No project name specified, using Activity name 'XXXActivity'.
If you wish to change it, edit the first line of build.xml.
Added file ./build.xml
プロジェクト名を入れなかったのでactivityの名前でプロジェクト名が作られた。
build.xmlのを書き換えてこれでコンパイルできるかと思うと...

% and debug
BUILD FAILED
/usr/local/java/android-sdk/tools/ant/build.xml:466: The following error occurred while executing this line:
/xxxxxx/OpenFeintAPI/build.xml:65:
Error. You are using an obsolete build.xml
You need to delete it and regenerate it using
        android update project
また同じエラーが出てしまった。
OpenFeintを使っているので、そちらも更新しろと。

OpenFeintもandroid update project -p ./ としてコンパイルできるようになったが。

build.xml:600: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
という警告が出る。これはr14以前から出ていたもので、目障りだから消しておく。
今回はandroid-sdk/tools/ant/build.xmlを直接書き換えてしまった。
antタスクののプロパティに includeantruntime="false"を追加しておく。
これで終了。

r14に移行のドキュメントはちゃんと書かれていて、探すと見つかったので読んでおく。
antでのプロジェクトの作り方やプロジェクトの更新方法のドキュメントもこの機会に読んでおく。

まとめ、ライブラリプロジェクト(今回はOpenFeint)のほうは、API level7対応なので
% android update lib-project --path ./ --target 5

自分のコードの方は
android update project --path ./ --name ProjectName

もともとあった、default.propertiesはproject.propertiesに置き換えられ、build.propertiesはant.propertiesに変えられた。
% ant release としてリリースビルドもできたので大丈夫なようだ。

2011年10月25日火曜日

プログラムの起動

ここでは自分のandroidプログラミングのスタイルを書き連ねていこうかと思っています。入門程度の難易度になるかと思うけど参考になる人もいるかもしれないと思う。現在の仕事はandroid向けのゲームを作るのが主になっているので、ここでもゲームプログラミングの内容が多くなる予定です。

1. プログラムの起動まで

ここでは、よくあるようにHello Worldを作りますが
1. ViewはSurfaceViewを使う
2. プログラムの起動時にスレッドを作っておく
SurfaceViewを使うのはandroid.widget以下のViewを使うより速いからです。もうひとつ、android.widget以下のViewだと描画はメインスレッドで行われなくてはならないのですが、ゲームの処理はゲーム用のスレッド(以下ゲームスレッドと呼びます)でも行ないますし、描画もゲームスレッドで行いたいからです。ただし、メインスレッドとゲームスレッドで描画が競合しないように作らなくてはいけません。

スレッドについてですが、これから作っていくプログラムではデータのローディングから処理のほとんどを自分で生成したゲームスレッドで行います。長い時間の掛かる描画やデータのロードをメインスレッドで行うようにすると、ANR (Application Not Responding)で落ちる場合があります。時間の掛かる処理はメインスレッドでは何をするかの指示をするだけで実際の処理はゲームスレッドで行うようにします。

サンプルコードを書きます。
package jp.co.mekira.android.examples.helloworld1;

import android.os.Bundle;
import android.app.Activity;
import android.view.Window;
import android.util.Log;

public class HelloWorld1Activity extends Activity {
    private GameView gameView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        gameView = new GameView(this); // <-- ここは遅くちゃダメ
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(gameView);
    }

    @Override
    public void onDestroy() {
        if (gameView != null) {
            gameView.finish();
        }
        super.onDestroy();
    }
}


最初にactivityの生成。 onCreate()をオーバーライドしてactivityを作る数行の処理ですが、ここでの肝はonCreate()は速く終了するという事。その為に14行目のGameVew()のインスタンスを作る処理は速く終わる必要があります。
andoridに限らずdocomoのiアプリでも、Windowシステム向けのアプリケーション(X Window SystemやMacintoshやWindows)でもこの部分は速く終わらせてシステムのメインループに戻す必要があります。
やってはいけない代表的な事がネットワークを介してデータを読み込む(少しでもダメです)とか,大量のデータを読み込む(ディスクなどの速い媒体からでも)事です。

package jp.co.mekira.android.examples.helloworld1;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.util.Log;

public class GameView extends SurfaceView implements
                                          SurfaceHolder.Callback,
                                          Runnable {
    private SurfaceHolder holder;
    private boolean       surfaceCreated;
    private Thread        thread;
    private int           status;

    private Paint         bgPaint;
    private Paint         textPaint;
    private int           textSize;

    public static final int StatusNOP  = 0;
    public static final int StatusDraw = 0;

    public GameView(Activity activity) {
        super(activity);
        init();

        thread = new Thread(this);
        thread.start();
    }

    private void init() {
        holder = getHolder();
        holder.addCallback(this);

        surfaceCreated = false;
        thread         = null;
        status         = StatusNOP;

        bgPaint = new Paint();
        bgPaint.setStyle(Paint.Style.FILL);
        bgPaint.setARGB(0xff,0xff,0xff,0xff); //背景は白

        textSize = 24;
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setARGB(0xff,0,0,0); //文字の色は黒
        textPaint.setTextSize(textSize);
    }

    public void surfaceChanged(SurfaceHolder holder,
                               int format, int width, int height) {
    }

    public void surfaceCreated(SurfaceHolder holder) {
        surfaceCreated = true;
        repaint();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        surfaceCreated  = false;
    }

    public void run() {
        while (thread != null) {
            if (status == StatusDraw) {
                repaint();
            } else {
                synchronized (this) {
                    try {
                        wait(); //止めてしまう
                    } catch (Exception e) {
                    }
                }
            }
        }
    }

    public void finish() {
        thread = null;
        wakeup();
    }

    private void wakeup() {
        synchronized(this) {
            notifyAll();
        }
    }

    public void repaint() {
        Canvas canvas = null;

        if (!surfaceCreated) {
            //surfaceCreated()より前に呼び出された場合は何もしない
            return;
        }

        try {
            canvas = holder.lockCanvas();
            synchronized (holder) {
                paint(canvas);
            }
        } catch (Exception e) {
        } finally {
            if (canvas != null) {
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }

    protected void paint(Canvas c) {
        //背景を塗りつぶす
        c.drawRect(0,0,getWidth(),getHeight(),bgPaint);

        String str = "ハローワールド";
        Rect   bounds = new Rect();
        int    xx,yy;

        //文字列の描画範囲を取得する
        textPaint.getTextBounds(str,0,str.length(),bounds);
        xx = (getWidth() - bounds.width()) / 2;
        yy = (getHeight() - textSize) / 2;
        c.drawText(str,xx,yy,textPaint);
    }
}


SurfaceViewを継承したGameViewは、初期化時にゲームスレッドを生成します。今回はプログラムの骨組みを作るだけなのでゲームスレッドは何もしないでいきなりwait()を呼び出して止めてしまいます。用がないのにCPUを使いたくありませんから、電池も消耗しますし。yield()やsleep()ではなくてwait()で完全に止めます。プログラム終了時にはnotifyAll()メソッドを呼び出しスレッドを起こして終了させます。
描画にはrepaint()を呼び出しますが、この名前はjava.awtやjavax.microedition.lcdui.Canvasクラスと同じ名前を使っているだけです。

ソースコード一式はここ
githubにもおいてあります。 git clone git@github.com:michiro/HelloWorld.git でソースコード一式取得できます。

2011年10月17日月曜日

最初の一歩

今更ながらblogを書いてみる。主にandroidでのプログラムの作り方などを書いてみようかと思う。
できれば実際に動くコードとその説明も書いてみたい。