AndroidでBGMを再生する

さて、ほとんどAndroidを触ることもなかった最近です。


リハビリを兼ねて極小さなプログラムを書いてみました。
Google Developer Day 2008にてルービンさんが実演されていたMP3プレーヤーの極手抜き版です。
要はServiceを用いてActivityが実行を終了しても再生を続けることを目指しました。
うまくいったので晒しておきます。

Service

AndroidではServiceはActivityと違い画面を持ちません。
デーモンであるかのように専用のスレッドにて常駐します。
ActivityはServiceに対し、start、stopを行うことや、既に実行されているServiceに対してbindを行うことができます。
ServiceにbindしたActivityはServiceに対し専用のメソッドを呼び出すことや、逆にServiceがActivityのCallbackを呼び出すことができます。
Serviceにて提供するサービスをActivityが利用するにはServiceの実装に用いたaidlにより生成されたInterfaceを利用する必要があります。
詳しくはAPI Demoのapp.Service、また公式ドキュメントのaidlを参照してください。

http://code.google.com/android/reference/aidl.html

今回はmp3を決め打ちで再生していますのでaidlは使っていません。
ActivityにてServiceを起動するとmp3の再生を開始します。
loopingを設定していますので無限に実行します。

package minghai.practice2;

import java.io.IOException;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class PlayerService extends Service {
    private NotificationManager mNM;
    MediaPlayer mp;

	@Override
    protected void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

        // Display a notification about us starting.
        showNotification();

		mp = new MediaPlayer();
    }
	
	@Override
    protected void onStart(int startId, Bundle arguments) {
		if (mp != null && mp.isPlaying()) return;
		
		try {
			mp.setDataSource("/sdcard/your.mp3");
			mp.setLooping(1);
			mp.prepare();
			mp.start();
		} catch (IOException e) {
			Log.d("TEST", e.getMessage());
			throw new RuntimeException(e.getMessage(), e);
		}
	}

    @Override
    protected void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(R.string.start_service);
        mp.stop();
        mp.release();
        
        // Tell the user we stopped.
        Toast.makeText(this, "MP3 Player Stopped", Toast.LENGTH_SHORT).show();
    }
    
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	
    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // This is who should be launched if the user selects our notification.
        Intent contentIntent = new Intent(this, MP3Player.class);

        // This is who should be launched if the user selects the app icon in the notification,
        // (in this case, we launch the same activity for both)
        Intent appIntent = new Intent(this, MP3Player.class);

        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = "Playing MP3";

        mNM.notify(R.string.start_service,          // we use a string id because it is a unique
                                                    // number.  we use it later to cancel the
                                                    // notification
                   new Notification(
                       this,                        // our context
                       R.drawable.stat_happy,       // the icon for the status bar
                       text,                        // the text to display in the ticker
                       System.currentTimeMillis(),  // the timestamp for the notification
                       "MP3 Player Service",        // the title for the notification
                       text,                        // the details to display in the notification
                       contentIntent,               // the contentIntent (see above)
                       R.drawable.icon,             // the app icon
                       "MP3 Player",                // the name of the app
                       appIntent));                 // the appIntent (see above)
    }
}

Notification

Serviceを用いる場合にはNotificationを定義しておきます。
これはサービスが実行されている間ホームのUIからServiceにアクセスする手段を与えます。
Androidではサービスを実行している間、Notificationを設定しておくと画面の一番上のステータスバーにアイコンが表示されます。
この時、ステータスバーをクリックし、下に向かってドラッグすると実行中のサービスの一覧画面になります。


一覧からServiceのアイコンをクリックすると設定されたActivityが実行されます。
今回の場合元のActivityが実行され、音楽の再生を停止することが可能になります。

Activity

今回のActivityが行っていることは実にシンプルです。
画面にはボタンを2つ用意しています。
1つはServiceの開始であり、音楽の再生開始ボタンです。
もう一つはServiceの停止ボタンです。


Serviceは1度起動すれば、複数起動することはありません。

package minghai.practice2;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MP3Player extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        
        Button button = (Button)findViewById(R.id.start);
        button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                startService(new Intent(MP3Player.this, PlayerService.class), null);
            }
        });
        
        button = (Button)findViewById(R.id.stop);
        button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                // Cancel a previous call to startService().  Note that the
                // service will not actually stop at this point if there are
                // still bound clients.
                stopService(new Intent(MP3Player.this, PlayerService.class));
            }
        });
    }
}

まとめ

ServiceはonCreateで実際のサービスを開始してはいけないようです。MediaPlayerの場合、再生がすぐに停止する症状に悩みました。

このプログラムを作成した後に気付いたのですが、英語のサイトでもっと優れたmp3プレーヤーを作成された方がいらしました。
m3用ですが十分に参考になると思います。

http://www.helloandroid.com/node/140

ところでmp3の再生にemulatorがCPUの20%から30%も使用してしまいます。
やっぱりAndroidのemulatorは重いですね。