アンドロイドアプリができるまで:005 アラーム音と加速度センサーによるアラームストップ機能の解説

2010年03月30日 11:13 by タオソフトウェアため吉
アンドロイドアプリができるまで:005

アンドロイドアプリができるまで:005

アンドロイドファンの皆様、こんにちは。タオソフトウェアのため吉です。この連載では、Androidのアプリケーションが一体どうやってつくられているのか、その工程をお伝えしています。アンドロナビにて3月8日(月)より無料ダウンロード開始となったタオソフトウェアの「ShwakeUp」を具体事例として取り上げ、実際のアプリ開発の現場の様子をご覧いただければと思います。


前回は「ShwakeUp」の内部について、特に画面の作製の仕方や画面とプログラムの橋渡しの仕方について解説しました。今回は、めざまし時計の肝である音の鳴らし方と、ShwakeUpの特徴である、加速度センサーによるアラームストップ部分について解説をしたいと思います。



アラーム音を鳴らす

ShwakeUpでは、当初アンドロイド端末に標準で用意されているアラーム音から選択する仕様だったのですが、それではつまらないと言う事で、Androidの音楽再生ソフトで再生可能にした曲データ(CD等からSDカードに音楽ファイルをコピーします)をアラーム音として設定可能な仕様に変更になりました。


一見良さそうな仕様なのですが、標準で用意されているアラーム音とは異なり、総ての人が音楽を聴けるようにしているわけではありません。音楽データがない時にどのように処理をするのか考える必要があります。音楽データが入っていない状況としては、

  1. アンドロイド端末を何台も持っていたりする人
  2. 日常的に使用していないアンドロイド端末(開発端末?)
  3. 音楽は別の機械(iなんたらやウオークマン)で聞いている

などが考えられます。


しかし今回は、目覚まし時計を入れているようなアンドロイド端末であれば音楽データは一度ぐらいは入れているはずだ、と仮定しました。従って、ShwakeUpとしては、音楽ファイルが存在しない場面はレアケースとして扱い、このような場合はアラーム音の変更はできないという簡単な仕様にしました。


また、音楽ファイルを選択後、選択したファイルをSDから削除されてしまう事も考えられます。このため、音楽再生時にファイルが見つからなかった時は、ShwakeUpで用意してあるアラーム音が鳴るようにしました。


アラーム音は端末に入れている音楽データから選択できる仕様に

アラーム音は端末に入れている音楽データから選択できる仕様に

このように、一見簡単な仕様変更と思われるものでも、色々と追加処理や考える事が多く発生します。どのような仕様に落ち着かせるかは、ソフトを使用する人を想定して決めていくことが重要です。


よく、「総ての人に使いやすいソフトウェア」という言葉を聞きますが、これは「総ての人に使いにくいソフトウェア」と同義語だと考えています。特にAndroidでは、狭い画面の中でどうやって使いやすいソフトウェアにするか試行錯誤を繰り返していく必要があります。このためには使用するユーザや、使用場面を想定しながら作成する事が重要になります。想定が明確であればあるほどアプリとしての個性が際立ってきますし、その方がユーザの方々からのフィードバックも得られやすいと考えています。


前置きはこれぐらいにして、実際のソースコードの解説に入ります。実際にプログラムを作製していくと、正常の動作について記載した個所よりも、エラー処理や特殊な状況の処理に関する記載の方が多くなります。エラー処理等に関する記載を入れて行くと膨大な解説になってしまうので、今回解説するソースコードは正常処理の部分のみを抜き出したものとなっています。


まずは、アンドロイドの中に入っている、上記のような音楽データを取得する方法です。ShwakeUpで表示される上記画面のような音楽データ一覧を取得するにはどうしたらいいでしょうか?答えは簡単で

	Cursor cursor = getContentResolver().query(
			MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
			null, null, null, null);

上記のように、Androidで用意されているContentProvider(コンテントプロパイダー)という仕組みを利用して「MediaStore」に一覧をくださいと要求(query)を出すだけで一覧が取れてしまいます。一般的な携帯電話とは異なり、このようにプログラムから簡単に曲データを取得する事が可能です。



曲の再生

曲の再生も簡単です。Androidには音楽やビデオを簡単に再生できるクラスが用意されており、どの音楽を鳴らすかの指定をしてから、startコマンドやstopコマンドを実行するだけです。

 	public void play(Context context, Uri uri){
		MediaPlayer mediaPlayer = new MediaPlayer();
		mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
		mediaPlayer.setDataSource(context, uri);

		mediaPlayer.setLooping(true);
    	mediaPlayer.prepare();
    	mediaPlayer.start();
    }

上記のplayメソッドでは、Uri(前述したCursorから取得します)にどの曲を再生するかの情報が入っています。


  1. Androidで用意されている、MediaPlayerを使用する準備をします。
  2. どのような音楽データを再生するかを指定します。
  3. どの音楽データを再生するかを指定します。
  4. 曲を最後まで再生した後、終了するか繰り返し再生するかの指定です。目覚ましなのでtrueを指定し繰り返し再生させます。
  5. 曲を再生する前の準備をします。
  6. 曲の再生を開始します。


なんとなく感じをつかんでもらえたのではないでしょうか?音楽を再生するには、音楽フォーマットが何かとかまったく気にすることなくMediaPlayerクラスを使うだけで簡単に再生が可能です。



加速度センサー

音楽の再生方法は、非常に簡単と感じられたのではないでしょうか?次にAndroidやiPhone等、最近のスマートフォンには必ず付いている加速度センサーの使い方を説明します。現在発売されているAndroidに付属している加速度センサーは、物体の、X,Y,Z軸の加速度の変化を取得する事ができます。


ShwakeUpでは、指定した強さで何回振ったかをこの加速度センサーを使って判断しています。


アラーム中に表示される画面

アラーム中に表示される画面

センサープログラムの定型処理

ShwakeUpでは、朝になると目覚まし音と共に、左の画面が表示されます。この画面はプログラム上で、WakeUpActivityという名前が付けてあり、以下のソースコードはWakeUpActivity内で加速度センサーの処理をしている部分を抜き出した物です。


public class WakeUpActivity extends Activity implements SensorEventListener {

	//センサーマネージャー
    private SensorManager mSensorManager;

    public void onCreate(Bundle savedInstanceState) {        

        //センサーサービス取得
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);

        if (sensors.size() > 0) {
            Sensor sensor = sensors.get(0);
            mRegisteredSensor = mSensorManager.registerListener(this,
                sensor,
                SensorManager.SENSOR_DELAY_FASTEST);
        }
    }

	public void onAccuracyChanged(Sensor sensor, int accuracy) {

	}
    /**
     * センサーイベント
     */
	@Override
	public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        	// シェイクと判定された場合は、カウントダウンを行う
        	if( mShakeDiter.ShakeDiterm(event) ){
    			count++;
    			setShakeCountDown(count);
        	}

            if(count >= mShakeTimes)
            	finish();
        }
	}

上記のソースコードについて順に解説していきたいと思います。


  1. 最初に、getSystemService(SENSOR_SERVICE)メソッドで、システムレベルで用意されているセンサーサービス(マネージャ)を取得します。getSystemServiceメソッドは、その他にも振動系サービス(VIBRATOR_SERVICE)取得や、Wi-fiサービス(WIFI_SERVICE)の取得等様々なシステムレベルのサービスを取得できます。
  2. センサーに関する情報を扱うセンサーマネージャーを取得しましたから、次に、センサーのタイプをもう少し細かく指定して、使用できるセンサーリストを取得します。今回は加速度ですから、Sensor.TYPE_ACCELEROMETERを指定していますが、傾き(TYPE_ORIENTATION)や温度(TYPE_TEMPERATURE)など、様々なセンサーを取得できます。もちろんアンドロイド端末によっては、これらのセンサーを物理的に持っていない物もあります。センサーが付いていて使用可能かどうかはこの段階でわかります。
  3. センサーがあった場合は、mSensorManager.registerListenerというメソッドを使用して、センサーマネージャーにリスナを設定します。リスナとは、何かがあった時に自動的に呼び出されるメソッドで、加速度センサーの場合は加速度に変化があった場合に、自動的に呼び出して欲しいメソッドを指定します。
    さて、加速度に変化があった時とは、どのような時でしょうか?実は手に持っている状態ですと手の細かい振れを検知しますので、常に加速度が変化し、設定したリスナを呼び出します。加速度センサーに検知されない状態は、テーブル等に置いた時のみと考えると良いでしょう。
    実際のソースコードでは、このリスナを指定する第一引数にthisを指定しています。これは、現在のクラスWakeUpActivityを示し、onAccuracyChangedと、onSensorChangedがリスナとて自動的に呼ばれます。
  4. onAccuracyChangedリスナ
    センサの精度が変更された時に呼び出されるようですが、今回は使いませんので何も記載していません。
  5. onSensorChangedリスナ
    加速度が変化したときにこの、onSensorChangedリスナが呼ばれます。シャカシャカと振った時に強さを判断する場所で、一番大事な場所でもあり実際にセンサーの値からデータを使って計算をする処理になるので、非常に複雑になります。
    ソースコードの保守性を良くするため、センサーの値の判断は、mShakeDiter.ShakeDiterm(event)メソッドに処理を任せています。ある強さ以上で「シャカ」と振ったときは、このメソッドがtrueを返します。
    trueを返した時は、「シャカ」を何回したのかの回数を一つ足しておき、setShakeCountDown(count)メソッドで画面上に大きく緑で出している数字文字を更新しています。
    最後に、countが、mShakeTimes(何回振ったら目覚ましを停止するか保持している変数)以上になったら、finish(終了)させています。


ざっくりとWakeUpActivity内での処理について記載しましたが、どれぐらいの強さで振られたのかを判断する部分(mShakeDiter.ShakeDiterm(event))は含まれていません。次に、強さの判断ロジックについて解説をしますが、上記Activity内のロジックはほぼ定型の処理となりますので、温度センサーを感知するプログラムを作製するときにでも同じような感じでプログラムを作り使い回しが可能です。



強さの判断

ソースコードを見やすく、保守性を高めるために、強さの判断用クラス、ShakeDiterminationを作製しました。このクラスは加速度に付いて取り扱うためのクラスとして作製しました。以下は先に出てきました、ShakeDitermのソースコードです。加速度の実際の値は、eventという変数に入ってきます。eventから値を取得して計算している部分は以下のようになります。

// センサー加速度の標準化
 private float orientSenVal(SensorEvent event){

float x = event.values[0];

 float y = event.values[1];

 float z = event.values[2];

 samplingX[position] = Math.abs(x);

 samplingY[position] = Math.abs(y);

 samplingZ[position] = Math.abs(z);

float targetVal =    getMedian(samplingX) + 

 getMedian(samplingY) + 

 getMedian(samplingZ);

return targetVal;

 }

センサーから取得できる値eventの中には以下のような情報が入っています。単位は、m/s2です。


  • event.values[0] X軸(左右)
  • event.values[1] Y軸(上下)
  • event.values[2] Z軸(前後)


寝起きで振られることになるので、どの方向に向けて振られるのかまったくわかりません。そこで全方向に付いて絶対値を取ってから、各方向の値を足す事で加速度として値を使用しています。

/**

 * センサーが変化したときの処理

 * シェイクしたと判定したらtrueを返す

 */

 public boolean ShakeDiterm(SensorEvent event) {

 float shakeLev;        // シェイクの強さレベルの数値化

 boolean ret = false;

shakeLev = 30 + ( 8 * (float)mShakeLevel );

// 加速度を標準化した値を利用する

 float targetValue = orientSenVal(event);

if(targetValue > shakeLev){

 val++;

 if(val > 10){

 val = 0;

 ret = true;

 }

 }

return ret;

 }

さて、先のorientSenValは上記の真ん中あたりで呼ばれて、シェイクレベルの画面で設定した値が入っているshakeLev変数よりも強い時「シャカ」と振ったと判断しています。


比較している、shakeLevを見てみると、shakeLev = 30 + ( 8 * (float)mShakeLevel )と謎の計算式が出てきます。
mShakeLevelには、1から5までのシェイクレベル設定画面で設定した値が入っており、1は簡単に反応する、5はなかなか反応しない設定となります。この設定された値に8を掛けています。つまり1レベル異なると8倍異なります。そしてレベル1の時、38になるように30を足しています。


シェイクレベル調整は試行錯誤を繰り返した

シェイクレベル調整は試行錯誤を繰り返した

さて、この8やら30やらどこから出てきたのでしょうか?速度というのは体感しやすいですが、加速度は非常に体感しずらいもので、30m/s2といっても普通はわかりません。


仕方がないので、振って振って振りまくりました。そして手に持っただけで反応するのは敏感すぎるので、ある程度簡単に反応する値をレベル1の38としました。


実際振ってみると分かるのですが、振る人間による違いはあるのはもちろんですが、体を使って振った時、片手で持って振った時、両手で持って振った時、スナップを利かせて振った時、等、同じ人間が同じ力加減で振ったと思っても実際に端末に伝わる加速度は異なります。そして、さらにはG1やHT-03A等の端末により取得できる値がかなり違う事がわかりました。


仕方がないので、数式等を用いるのはあきらめ、HT-03Aをひたすら振り値を調べていきました。そして最終的に、立っている状態であまりスナップを聞かさない動作を基本条件として、導きだしたのがshakeLev = 30 + ( 8 * (float)mShakeLevel )という計算式です。


実際に、このシェイクレベル調整は、色々なロジックを試しながら、最後の最後までチューニングを行っていきました。今回の開発では、こんな大変だとは思わなかったと嘆きながら深夜ひたすらHT-03Aを振り続けている人がいました。まさに試行錯誤そのものです。そんな苦労もあり、とてもいい感じの強さレベルに仕上がっていると思います。


えープログラムってそんないい加減なの?と思われるかもしれませんが、加速度センサーは、端末によっても感度が違いますし、最終的にはどのように補正をしていくかがそのほとんどを占めていき、非常に泥臭い処理になっていきます。プログラミングはある一線を越えると、愛と気力と根性だと思っていますが、今回もその良い一例となっていると思います。



まとめ

Androidは機能を提供する部品があらかじめ多く用意されているので、少ないコード量で実装することができることが理解いただけたのではないでしょうか。今回は触れていませんが、


  • アンドロイド端末が再起動した時
  • 端末の時刻を変更した時
  • レジュームが効かないようにしておく必要が有る


など考慮すべき場面が色々とあり、ShwakeUpも、目覚まし機能そのものの実装よりもこれらの例外処理や、使用感を良くするためのコードの方が量が多かったりします。アンドロイドアプリの開発では機能個々の実装にそれほど手間がかからない分、気配り(品質)に注力できるという事が言えるかと思います。


これまで開発環境の構築からプログラムの実装まで、5回にわたってアンドロイドアプリの開発について連載してきました。いかがでしたでしょうか?アプリケーションを開発する側がどのような事を考えて設計、実装しているのか、その一端を少しでもお伝えできていればうれしく思います。本連載は今回で最終回となります。最後までお付き合いいただきありがとうございました。また機会があればどこかでお会いしましょう!!



「Shwakeup」無料ダウンロードページ


前回までの記事