Titanium mobile で Android の Service を使う


Titanium mobile で MP3 プレイヤー的なものを作る時、 Android だと特に何もせずに Titanium.Media.AudioPlayer とか Titanium.Media.Sound を使うとバックグラウンドで音がなってたんですが、Activity が殺されたかなにかで新たに Activity が立ち上がった時に、前になっていた音が止められなくなる事がありました。

通常の Android の作法はどんな感じだろう、と思って調べてみるとバックグラウンドのプロセスは Service が受け持って、 Activity はいつ落とされても良いように、あくまでも UI に徹するという事のようです。

KitchenSink を見てみたら Titanium mobile でも Service が使えるみたい。

KitchenSink の service の例
android_services.js
testservice.js

tiapp.xml に service を設定する
Update your tiapp.xml

で、下記の通り試してみた所、意図した通りに動くようになりました。もっと良いやり方があれば @tanusai までお知らせください。

まずは Titanium.Android.createServiceIntent() で取得した Intent に putExtra( “interval”, 1000 ) などとしてインターバルを設定。その他にも渡したいものがあれば入れる。

var soundIntent = Ti.Android.createServiceIntent( { url:  'playSoundService.js' } );
soundIntent.putExtra('interval', 1000);

その Intent を Titanium.Android.createService() に渡して返って来たインスタンスを start() で起動すると Service が起動します。

sound_service = Ti.Android.createService( soundIntent );
sound_service.start();

ただし定期的に起動する度に毎回スクリプトが頭から処理されるので、 Titanium.Android.currentService で取得した Service のインスタンスに値を保持しておいて、上手いこと初回起動時のみの処理やら定期的な処理やらを分岐させる。

↓ サービスの中身( playSoundService.js )

var service = Ti.Android.currentService;
var intent = service.intent;

if( !service.initialized ){
	service.addEventListener( 'playSound' , function( e ){
		if( service.sound == undefined || service.sound.url != service.fileToPlay ){
			if( service.sound ){
				service.sound.stop();
			}
			service.sound = Ti.Media.createSound({
				url:service.fileToPlay
			});
		}
		service.sound.play();
	});
	service.addEventListener( 'pauseSound' , function( e ){
		service.sound.pause();
	});
	service.addEventListener( 'resumeSound' , function( e ){
		service.sound.play();
	});
	service.addEventListener( 'stopSound' , function( e ){
		service.sound.stop();
	});

	service.initialized = true;
}

service に sound を入れておくと、Activity 側から直接参照できるようになります。

で、Activity の方では起動した時に Service が起動しているかどうかを Titanium.Android.isServiceRunning で確認して、起動してなければ新たに起動。起動していれば Titanium.Android.currentService でインスタンスを取得してそれを使用する。

 
↓ View で使う JS の中身

var sound_service;
var soundIntent = Ti.Android.createServiceIntent( { url:  'playSoundService.js' } );

if( Ti.Android.isServiceRunning( soundIntent ) == false ){
	Ti.API.info( 'service is not Running' );
	soundIntent.putExtra('interval', 1000);
	sound_service = Ti.Android.createService( soundIntent );
	sound_service.addEventListener('resume', function(e) {
		if( e.source.sound ){
			Ti.API.info( 'sound.getTime() ' + e.source.sound.getTime() );
		}
	});
	sound_service.addEventListener('pause', function(e) {
	});
	sound_service.addEventListener('stop', function(e) {
		Ti.API.info('Service is stopped');
	});

	sound_service.start();
}else{
	Ti.API.info( 'service is Running' );
	sound_service = Ti.Android.currentService;
}

これで Service と Activity の連携ができるようになりました。
値の受け渡しは Service のインスタンスに入れた後に fireEvent して受け渡します。
例えば上記の内容で音声を再生させたい時には Activity 側で


sound_service.fileToPlay = 'music.mp3';
sound_service.fireEvent( 'startSound' );

とすると Service 側で音が鳴り始めます。

★まだよくわかっていない事
・Ti.Android.currentService が返すものが必ず意図した Service なのかなんなのか
・Intent の仕組み
・Service を stop する適切なタイミング
・音楽の再生は定期的な処理が不要なので、interval じゃない方法で常駐させてイベントで連携するのが正しいと思うんだけど、その方法が見つからない

★思った事
・iOS と Android でアプリの考え方がまったく違う事がわかって面白い
・Titanium mobile の Sound や MediaPlayer は再生が終了するとオブジェクトごと消えてなくなる事がある気がするけど気のせいか
・Android のエミュレータ頑張れ