1. 程式人生 > >如何寫一個正經的Android音樂播放器 二

如何寫一個正經的Android音樂播放器 二

這一篇講如何

與音樂播放Service互動

稍有經驗的同學都知道,將長時間的操作放在Service中進行,如何做到介面和音樂播放Service的有效溝通呢,在這一章中給出我的答案,同樣希望大神們給出指點。
希望你閱讀(自行翻牆):
ServiceAPI Guidehttp://developer.android.com/guide/components/services.html
ServiceAPI Guide中的有關bound service中的部分:
http://developer.android.com/guide/components/bound-services.html
ServiceAPIhttp://developer.android.com/reference/android/app/Service.html


首先實現一個Service類,我們命名之為MainService,那麼我們應該如何啟動這個Service呢?啟動Service一個Service有兩種方法:一種是startService (Intent);另外一種是:bindService (Intent);
第一種啟動方式,適用於Service獨立完成任務,例如說一個下載,如果不需要暫停或者取消的話,可以這樣來做。但是,我們這是音樂播放應用,把MediaPlayer放在Service中執行播放,這樣的方式很難有暫停等互動。(其實也可以這樣做,例如每次互動操作都用startService來做,通過Intent把暫停等命令出入進去,進度條也可以通過廣播來發送出去,但是這樣做,感覺很醜陋,肯定不是常規的高效做法);

第二種啟動方式,可以在onServiceConnected中,獲得一個Service物件(可能不是一個Service物件,至少是一個可以接觸Service內部操作的控制代碼)。這樣,便可以輕鬆操作播放和接收進度通知。但是這種方式有弊端,一旦與之繫結的context退出,則繫結接觸,Service也會被回收(但是不會執行onDestory方法);
那麼是不是就只是簡單用第二種啟動方式?
當然不是這樣,在谷歌官方文件中,有交代,這兩種方式並不衝突,而且十分適用於音樂播放類的應用。
原文如下:

A bound service
The service is created when another component (a client) calls bindService(). The client then communicates with the service through an IBinder interface. The client can close the connection by calling unbindService(). Multiple clients can bind to the same service and when all of them unbind, the system destroys the service. (The service does not need to stop itself.)

These two paths are not entirely separate. That is, you can bind to a service that was already started withstartService(). For example, a background music service could be started by calling startService() with anIntent that identifies the music to play. Later, possibly when the user wants to exercise some control over the player or get information about the current song, an activity can bind to the service by calling bindService(). In cases like this, stopService() or stopSelf() does not actually stop the service until all clients unbind.
簡單理解是:可以有多個client去繫結同一個Service,並且只有所有繫結的client都解除繫結以後,Service就會被destory掉,不需要執行stopSelf方法,同樣不會回撥到onDestory方法;一個Client可以繫結一個已經start的Service,這樣繫結的話,要停止這個service,則必須先解除一切繫結的client,然後再呼叫stopService或者stopSelf方法。
得到官方的說法後,就可以放心大膽的寫程式碼了。
為了方便,我們定義一個自己的Application類——BeApplication,來執行相關操作。
public class BeApplication extends Application implements ServiceConnection {
@Override
public void onCreate() {
    super.onCreate();

    startMainService();
    bindMainService();
}

public void startMainService () {
        Intent it = new Intent (this, MainService.class);
        startService(it);
    }

    public void stopMainService () {
        Intent it = new Intent(this, MainService.class);
        stopService(it);
    }

private void bindMainService () {
        Intent it = new Intent (this, MainService.class);
        this.bindService(it, this, Service.BIND_AUTO_CREATE);
    }

    private void unbindMainService () {
        this.unbindService(this);
    }

@Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (service instanceof MainService.ServiceBinder) {
            MainService.ServiceBinder binder = (MainService.ServiceBinder)service;
            mMainService = binder.getService();
            mMainService.registerServiceCallback(mPlayManager);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Toast.makeText(this, "onServiceDisconnected name=" + name, Toast.LENGTH_LONG).show();
    }
}


在這個Application類的onCreate方法中,我們先啟動一個Service,然後繫結這個Service。注意,讓自己的BeApplication能夠執行,要在manifest檔案中的<application/>標籤下,定義android:name=包名加我們application類的類名
現在寫我們用音樂播放的Service類——MainService,同樣記得在manfest檔案中宣告這個Service。
<pre name="code" class="java">public class MainService extends Service {
public static class ServiceBinder extends Binder {

        private MainService mService = null;

        public ServiceBinder(MainService service) {
            mService = service;
        }

        public MainService getService () {
            return mService;
        }
    }
@Override
    public IBinder onBind(Intent intent) {
        return new ServiceBinder(this);
    }
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_STICKY;
        //return super.onStartCommand(intent, flags, startId);
    }
}



注意之前在BeApplication中的onServiceConnected方法,我們在那個方法中,取得了由MainService中的onBind方法返回的IBinder物件,並通過這個物件,我們在BeApplication中拿到這個用於播放音樂的Service,這樣以來,與Service互動容易的多。
也許你會問,這樣已經拿到service物件了,那有何必要去start這個service呢?
注意MainService中的onStartCommend方法,此方法返回了一個int常量,對於這個常量,官方有這樣的解釋:
START_STICKY
If the system kills the service after onStartCommand() returns, recreate the service and call onStartCommand(), but do not redeliver the last intent. Instead, the system calls onStartCommand() with a null intent, unless there were pending intents to start the service, in which case, those intents are delivered. This is suitable for media players (or similar services) that are not executing commands, but running indefinitely and waiting for a job.
另外 還有兩個常用的常量:START_NOT_STICKY 和START_REDELIVER_INTENT ,可以在此連結進行擴充套件閱讀:http://developer.android.com/guide/components/services.html#ExtendingService
此變數的功能在於,一旦系統殺死了這個service,在重新啟動這個service的時候,onStartCommand中的intent不起作用。這適用於,在onStartCommand中,並不執行工作的Service。
附service生命週期流程圖: