2011年4月9日土曜日

Androidマーケットへの登録


スムースに動作するコンパスアプリが欲しかったので、自分で作ってマーケットに登録してみました。
GLSurfaceView(openGL)を用いてスムースな動作を目指してます。トレードオフとして、起動が少し遅いですが…
もし興味のある方はこちらからどうぞ。

※あくまで個人製作ですので、要望やバグ報告はマーケットに登録してあるメールアドレスからお願いします

登録の際、少しはまったところを挙げておくと…

■マーケットに表示されるアプリアイコンがデフォルトのまま

これは、リソースのdrawable-hdpiにのみアプリアイコンを入れていて、他のdrawable-mdpiとdrawable-ldpiにはデフォルトアイコンが入っていた事が原因でした。

■IS03で画像(テクスチャ)が表示されない

drawable関連でもう一つ。
IS03は特殊な解像度なので、drawable-320dpiというリソースフォルダを作成し、その中に対応する画像を入れる必要があります。SDK2.3からはdrawable-xdpiというリソースフォルダが使用できるようです。

■全年齢対象に出来ない

はまったところではありませんが、現在地を取得するアプリは全年齢対象アプリに出来ないようです。
今回、コンパスの真北を取得するために現在地の取得を行っているので、それが原因でした。


さて、Googleにデベロッパーとして登録するとデベロッパーコンソールというページが使えるようになるのですが、これが凄く便利です。

■統計情報の表示
さすがGoogleといった感じで、インストールされたプラットフォーム、端末、有効なインストール数、国等の情報をグラフ表示してくれます。


■詳細なバグレポート
一番驚いたのはここ。
アプリごとのエラーレポートを見る事が出来、その中ではエラーの発生時間、種類はもちろんのこと、なんとどのクラスの何行目でエラーが起こっているかも見る事が出来ます。開発時にLogCatでログ見ているとエラーが起きた時に内容が表示されますが、その内容がそのままデベロッパーコンソールで見る事が出来るイメージです。
開発者には嬉しい仕様ですね。

これだけ充実していると、また何か作りたくなってきてしまいますね。

EclipseにAndroid SDKのサンプルを読み込んで、警告を消す

Android SDKにはGoogle製のサンプルがいくつもついてきます。
Androidアプリを開発する時にはEclipseを使う事が多いと思いますが、Eclipseにそのサンプルを読み込む方法と、そのままでは警告が出てしまいますので、それらを消す方法をご紹介します。


まず、新規プロジェクトを作成し、Androidプロジェクトを選択します。


ここで、普通であればパッケージやアプリ名等を入力して新規プロジェクトを作成しますが、「既存サンプルからプロジェクトを作成」から、インポートしたいサンプルを選択します。ただし、この項目はビルドターゲットを先に指定しておかないとアクティブにならないので、先に指定しておきます。
あとは、完了を押すとEclipseにサンプルプロジェクトがインポートされます。


さて、そのままでも問題なくビルドして端末にインストールできるのですが、上のキャプチャのように警告が大量に出てきます。
Eclipseはデフォルトではプロジェクトを横断して警告を表示してくるので、他のプロジェクトを進めているときでもこの警告が表示され、自分のプロジェクトでの警告やエラーがそれらに埋もれてしまいます。

ここでは、下記2つの対策を紹介します。


■選択プロジェクト上のエラー/警告のみ表示する


項目名通り、選択しているプロジェクトのエラー/警告のみが表示されます。
警告が表示される「問題」タブの右上、▼マークをクリックし、上の画像のように「表示」->「プロジェクト上のエラー/警告」を選択します。


■プロジェクト単位でエラー/警告を無視する


こちらの方法では、サンプルプロジェクトのエラー/警告のみを無効にします。

読み込んだプロジェクトのプロパティを開き、「Javaコンパイラ」->「エラー/警告」を選択します。
「プロジェクト固有の設定を可能にする」にチェックを入れ、必要な項目を「無視」に変えていき、OKを押すとプロジェクトのビルド後に変更が適用されます。
Google製のコードですし、コピペはあっても基本的にはソースを直接変更する事は無いと思いますので、全部無視で良いと思います。


サンプルの中では、ApiDemoが便利ですね。簡単にですが、何ができるか知る事が出来ます。
サンプルプロジェクトも結構多いので全部を網羅するのは大変そうですが、ちょくちょくのぞいてみると面白いかもしれませんね。

Androidでセンサーから方位を取得する

最近ネイティブAndroidプログラムに手を出しています。
FlashのAS3を触っていると、AndroidのJavaはあまり敷居が高くなく感じますね。whileの挙動とか、イベントリスナの登録あたりはちょっと違いますけど。
イベントリスナの登録は、AS3でいうNetStraemのonMetaDataハンドラの扱いに似ていますね。ただ、Javaの方はインターフェイスで強制実装させるあたり、厳格になっています。

さて、本題ですが、Androidでコンパスを作成する時の話です。
コンパスを作成するにあたってセンサの値を取ってくる必要があるのですが、他のセンサーと同じようにSensorManagerから読み出す事で使用する事が可能です。

public class Compass extends Activity implements SensorEventListener{

    private SensorManager sm;

    @Override
    public void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        sm=(SensorManager)getSystemService(SENSOR_SERVICE);
        
    }

    @Override
    protected void onResume() {
        super.onResume();

        //全センサを取得
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);
        //加速度センサ
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);
        //磁気センサ
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
        //方位センサ
        List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ORIENTATION);

        for(Sensor sensor : sensors){
            //イベントを登録
            sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
        }
    }

    …

}

Sensor.TYPE_ORIENTATIONが方位センサのタイプ定数になります。
ただ、このタイプは現在非推奨となっており、Android2.2からはSensorManager.getOrientation()が推奨されます。使い方としては、上記の方法で加速度と地磁気センサの値を取得し、それらの値から然るべき方位を計算する、という形になります。

//SensorEventListenerにより実装が要求されるメソッド
public void onSensorChanged(SensorEvent event) {
    
    float[] amv=new float[3];
    float[] mfv=new float[3];
    float[] orv=new float[3];

    switch(event.sensor.getType()){
        case Sensor.TYPE_ACCELEROMETER:
            amv=event.values.clone();
        break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            mfv=event.values.clone();
        break;
    }

    float[] in_r=new float[16];
    float[] out_r=new float[16];
 
    if(amv!=null && mfv!=null){

        SensorManager.getRotationMatrix(in_r, null, amv, mfv);
        SensorManager.remapCoordinateSystem(in_r, SensorManager.AXIS_X, SensorManager.AXIS_Z, out_r);
        SensorManager.getOrientation(out_r, orv);

        Log.d("message", String.valueOf(orv[0]));
        Log.d("message", String.valueOf(orv[1]));
        Log.d("message", String.valueOf(orv[2]));

    }
 
}

ただ、このまま実装すると、リファレンスにもある通り特定の状態でしか正しい方位を取得できません。

Using the camera (Y axis along the camera's axis) for an augmented reality application where the rotation angles are needed:
remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);

画面が自分から見て手前側に傾いている時は正しく方位が見えますが、向こう側に傾いている時は方位が反転してしまいます。これを調整するにはremapCoordinateSystemの第二、第三引数を調整すると良いのですが、例えば横向きにする時には

Using the device as a mechanical compass when rotation is Surface.ROTATION_90:
remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);

とすれば良いようです。

これを、端末の向きによらず正しい方位を取得するようにしたかったので、色々試してみた結果

remapCoordinateSystem(inR, AXIS_X, AXIS_Y, outR);

を使用すると端末の向きによらず正しい方位を取得する事が出来ました。

ただし、iPhoneでもそうですが、寝転がってコンパスを見ると正しい方位が表示されません。まぁ、そんなことをする人はあまりいないと思いますが…