はじめに
この記事はだいぶ前(2012年)にココログの「キリーの日本語プログラミング」に掲載したコラムですが、再編集してこちらにも投稿します。今となっては古い話ではありますが、OSによってプログラム言語(AndroidではJava)が決め打ちされている環境で規定と異なるもの(Mind)を走らせたことの記録としてお読みください。
インテントサービスで効果音を再生
サービスの一つである「インテントサービス」を実装した。
インテントサービスは「サービス」という名前を冠しているため、サービスと同様に当初はWindowsのサービスのようなものだろうと想像していたのだが、実はそのようなものとはかなり異なると後になって分かった。
要するにAndroidの「インテントサービス」は多少時間がかかる処理をバックグラウンドで実行することが目的らしい。そのため処理が終わると共にサービスは終了する(当初は「なぜサービスなのに勝手に終了するのか?」と疑問に思ったものだった)。つまり常駐することが目的なのではなく、時間のかかる処理をバックグラウンドで行うことが目的なのである。
実は当面の開発テーマとして「薬の飲み忘れアプリ」を考えている(歳がバレるが)。アラームマネージャから定期的にプログラムを起動し、アラームを鳴らすべきタイミングの判定や音を鳴らす処理をインテントサービスにしようと思っている。
そのため、インテントサービスの処理内容としては音を鳴らしてみることにした。
元となるアクティビティを作成
まず元となるアクティビティを普通に作成する。アクティビティ側での起動処理は前回投稿の「画面遷移」での子のアクティビティの起動と良く似て、Mindで以下のように記述する。(サービスへのデータ渡しはまだ実装しておらず単純起動とした)
<パッケージ名>と <クラス名>で インテントサービスを起動
ちなみに子アクティビティの起動の時は以下であった。
<パッケージ名>と <クラス名>で インテントを実行
後者では「・・を実行」だったものが前者では「・・を起動」となっており、呼び出し元は子の終わりを待たないことが想像できるようにした。
処理単語「インテントサービスを起動」が実行されると Mind→C→Java へと制御が移行し最終的に以下のJavaコードが動作する。(これまた子アクティビティの起動と似たような記述となっている)
intent = new Intent();
intent.setClassName( pkgname, classname );
this.startService(intent);
1つのパッケージに主(アクティビティ)・副(インテントサービス)の2つを含めることになるのも画面遷移の時と類似であり、C記述の共有ライライブラリ(DLL)のAndroid環境下での構成は以下のようになる。
(パッケージ化する前の元ファイル状態のうち主要なものだけを抜粋)
assets
|-- bin
| |-- euc-uni-encode.bin ←Mindが使うUnicode変換テーブル
| `-- uni-euc-decode.bin ←Mindが使うUnicode変換テーブル
`-- mco
|-- MyIntentService.mco ←インテントサービスのMindオブジェクトコード
`-- TestSoundService2.mco ←メインアクティビティのMindオブジェクトコード
bin
|-- classes
| `-- jp
`-- co
`-- scriptslab
`-- TestSoundService2 ←パッケージ名
|-- MyIntentService.class ←インテントサービスのJavaクラス
`-- TestSoundService2.class ←メインアクティビティのJavaクラス
libs
|-- armeabi
|-- libdllker_MyIntentService.so ←C記述DLL (Mindランタイムとディスパッチャ)
`-- libdllker_TestSoundService2.so ←C記述DLL (Mindランタイムとディスパッチャ)
res
|-- raw
| `-- se_maoudamashii_onepoint23.ogg ←効果音を収めたファイル [注1]
JavaからC→Mindへと処理を移行する仕組み自体は前投稿の「画面遷移」と同じなのだが、作成したインテントサービスを実行してみたところ問題が発生した。2度目のサービス起動で落ちてしまうのである。
原因は Java→C に渡される環境情報 env, thiz の2つの扱いだった。たとえば一番最初にCが呼び出される箇所は以下のようになっていた。
Mindカーネルの "dllstartup.c" (まずかったときの記述)
static JNIEnv* env_save; //←単一の大域変数への退避はまずかった
static jobject thiz_save;
jstring
dllStartup_common( JNIEnv* env, jobject thiz, jstring pkgnamej,
jstring classnamej, jbyteArray mcobufferj )
{
//~略~
env_save = env;
thiz_save = thiz;
//~略~
JavaからCが呼び出されたとき、上記のように第1/第2引数で環境情報 env, thiz の2つが渡される。このあと延々とMindの処理に入るため、これらを一旦大域変数に格納し、Mind→Java の逆呼び出し時には、この大域変数を参照していたのだが、ボタン押下やサービス要求などのイベント発生時にこれを共用してはいけなかったのだ。
JNIの本「JNI Java native Interface プログラミング(ロブ・ゴードン著、林秀幸訳、ピアゾン・エデュケーション)」によれば
「JNIEnvポインタをstatic変数に入れてグローバルとして使用したくなるかもしれないが、そんな誘惑に負けてはいけない」
と書かれているのだが、まさにこれをやってしまっていた。
対策として、この情報は JavaからC側に制御が移るごとにスタックに入れることで解決した (Cランタイムで隠蔽するのでMindからは見えない)。
インテントサービス内でアラームを鳴らす方法
次にインテントサービス内でアラームを鳴らす方法だが、クラス SoundPool または MediaPlayer を使う方法がよく取り上げられている。効果音のような短いものは前者が使われるようだが、試しに MediaPlayer を使ってみた。
MediaPlayerはそのコンストラクタで音源を指定する方法がよく書かれているが、Mindでの記述統一(たとえばレイアウトやビューと同じような割り当てを使いたかった)のため、コンストラクタでは音源は指定せず、MediaPlayer#setDataSource のメソッドで明に指定するようにしたが、この方法では音を出すのに都合3段階が必要となる。
Mindでの記述は以下のようになる。
インテントサービス処理とは 本定義 (・ → ・)
_音源パスは 文字列定数 「raw/se_maoudamashii_onepoint23」
_プレイヤー1は プレイヤー
メディアプレイヤーを生成し 偽?
ならば <~略~>
終り
つぎに
_プレイヤー1に 入れ
_音源パスと _プレイヤー1で メディアプレイヤーにロードし 偽?
ならば <~略~>
終り
つぎに
100と 0を _プレイヤー1で メディアプレイヤーを演奏し 偽?
ならば <~略~>
終り
つぎに。
MediaPlayerにローカルファイル(たとえば raw/ 配下のファイル)で音源を指定する時のURIは
android.resource://raw/se_maoudamashii_onepoint23
のようになるのだが、記述が煩雑であるためMindのライブラリ側で補完を行うことにより、アプリケーション側では短形式として
raw/se_maoudamashii_onepoint23
で済むようにした。
Mindユーザーの@mylifewithviolinさんがMindコンパイラの内部に興味を持たれているようなので参考情報として投稿してみました。本記事はしばらくつづきます。次は(ステップ7/9)。
[注1]: 試した音源は 著作:音楽素材/魔王魂 http://maoudamashii.jokersounds.com/