天天看點

安卓播放器實作背景播放服務

安卓開發,除了我們經常寫的Activity、Fragment等顯示給使用者的控件外,我們還可能需要程式在退出到背景的時候,繼續給使用者提供服務的功能,這裡就需要用到Android的服務Service。

安卓服務是對使用者不可見的,它沒有界面,隻是開啟了一個在背景持續運作的線程,就算使用者退出到背景,隻要不停止服務,服務就可以繼續為使用者提供服務。不像Activity會有固定的生命周期,當使用者把程式退出到背景,程式失去焦點之後,Activity就必須調用onStop方法,失去與使用者的互動關系。

下面我們就來實作一個在背景播放的服務:

其實,我想大家學習服務其實都會有一個了解誤區,就是服務Service和Activity到底的差別在哪裡?是沒有界面,還是會一直執行onStartCommand方法,讓線程在背景持續執行?

也就是說,隻要我們手動的stratService或者bindService來開啟服務之後,這個我們繼承自系統的Service的服務就會一直持續不斷的調用onStartCommand方法,直到我們手動的調用stopService或者調用服務的stopSelf結束服務才會停止運作。

我想大家一定也會和我一樣存在上面的誤區,但是我們可以來做個實驗:

class DifferentSpeedPlayerService : Service() {

    private var playerType: String? = null
    private val mMediaResoureceOne = "https://qiniu.fjreading.com/summary/audio/73cd583f9822fde89936ed5dc59317dc"
    private val mMediaResoureceTwo = "https://qiniu.fjreading.com/summary/audio/b76d0070f7a77ad96f1e3d53c62306d0"
    private val mMediaResoureceThere = "https://qiniu.fjreading.com/summary/audio/04df5b8d56d050e981beeb2b7aefaed7"

    override fun onBind(intent: Intent?): IBinder? {
      LogUtils.e(">>>>>>>>>>>>>>>","綁定服務,傳回IBinder對象")
        //擷取傳遞過來的參數來傳回不同的播放器對象
         if(ObjectUtils.isNotEmpty(intent)){
             val bundle = intent?.extras
             playerType = bundle?.getString("player_type")
             LogUtils.e(">>>>>>>>>>>>>>>>", "palyer_type:$playerType")
         }
        return LocalBinder()
    }

    /**
     * 用戶端 Binder 對應的類
     */
    inner class LocalBinder : Binder() {
        internal// 傳回 LocalService 的執行個體,用戶端可以調用其中的公共方法
        val service: DifferentSpeedPlayerService
            get() = [email protected]
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        LogUtils.e(">>>>>>>>>>>>>>>","onStartCommand Int:"+Int+"startId:"+startId)
        return super.onStartCommand(intent, flags, startId)
    }
    fun getDifferentPlayerByPLayerType(): IPlayerWapper?{
        var player= DifferentSpeedPlayer.getSpeedPlayerInstance()
        player?.initPlayer(baseContext)
        player?.prepareDataSource(mMediaResoureceTwo,250)
        return player as IPlayerWapper
    }

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        LogUtils.e(">>>>>>>>>>>>>>>", "newConfig:$newConfig")
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        LogUtils.e(">>>>>>>>>>>>>>>", "onRebind")
    }

    override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
        super.dump(fd, writer, args)
        LogUtils.e(">>>>>>>>>>>>>>>", "dump")
    }

    override fun onCreate() {
        super.onCreate()
        LogUtils.e(">>>>>>>>>>>>>>>", "onCreate")
    }

    override fun onLowMemory() {
        super.onLowMemory()
        LogUtils.e(">>>>>>>>>>>>>>>", "onLowMemory")
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        LogUtils.e(">>>>>>>>>>>>>>>", "onTaskRemoved")
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        LogUtils.e(">>>>>>>>>>>>>>>", "level:$level")
    }

    override fun onUnbind(intent: Intent?): Boolean {
        LogUtils.e(">>>>>>>>>>>>>>>", "intent:$intent")
        return super.onUnbind(intent)
    }
    override fun onDestroy() {
        super.onDestroy()
        LogUtils.e(">>>>>>>>>>>>>>>", "onDestroy")
    }
           

上面的服務重寫了Service類的所有的方法,下面就來測試一下,startService和bindService方法開啟服務的不同點在于調用的Service的方法不同。

startService方法開啟服務會調用服務的onCreate方法來建立一個服務,然後再調用服務的onStartCommand方法來運作服務,值得注意的是:onCreatef方法隻會調用一次,onStartCommand方法在你重複調用startService方法會重複執行。

但是這裡你已經得到了開啟服務的效果,如果是播放音樂,無論手機是熄屏還是App退出到背景,你播放音樂的服務線程不會受到影響,這裡你隻要不手動調用stopService方法,服務就會持續運作在背景。

調用bindService方法來開啟服務,首先也會調用服務的onCreate方法,然後調用服務的onBind方法,來傳回一個繼承自Binder對象的子類。我們可以在綁定服務的時候用繼承ServiceConnection的子類連結對象裡面擷取到Service裡面定義的LocalBinder對象,然後通過這個LocalBinder對象擷取Service對象。這樣我們就可以拿到定義在服務裡面的成員變量,這裡這個成員變量可以是一個播放器和可以是一個在背景運作的線程。

class DifferentSpeedServiceConnection : ServiceConnection{

        var mDifferentSpeedPlayer: DifferentSpeedPlayer? = null
       override fun onServiceDisconnected(name: ComponentName?) {

           LogUtils.e(">>>>>>>>>>>>>>>", "onServiceDisconnected:$name")
       }

       override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
           try {
               val localBinder = service as DifferentSpeedPlayerService.LocalBinder
               val articlePlayerService = localBinder.service
               mDifferentSpeedPlayer =  articlePlayerService.getDifferentPlayerByPLayerType() as DifferentSpeedPlayer?
               mDifferentSpeedPlayer?.startPlay()
               LogUtils.e(">>>>>>>>>>>>>>>", "元件名稱:$name" + "綁定的服務:" + service)
               LogUtils.e(">>>>>>>>>>>>>>>", "mDifferentSpeedPlayer" +mDifferentSpeedPlayer)
           }catch (e: Exception){
               Log.e(">>>>>>>>>>>>>>", "綁定服務異常:$e")
           }
       }
   }
           

這裡擷取我們可以思考一下,為什麼我們不能直接通過ServiceConnection對象來擷取Service對象,而是需要在Service裡面定義一個Binder對象的子類,然後通過onBindf方法來傳回到ServiceConnection的onServiceConnected(name: ComponentName?, service: IBinder?) 方法呢?這裡我們會發現了共同對象IBinder,然後發現IBinder對象是一個接口,這就說明服務群組件或者界面綁定是通過public interface IBinder {}接口來實作綁定的。

我們解綁時需要傳遞一個ServiceConnection對象來取消綁定。

但是,有時候我們可能會用到一些第三方服務,比如百度語音合成、百度定位等,這些第三方服務其實都是有自己的服務,是以就不需要再自己去開啟一個服務來處理邏輯,隻需要按照第三方的開發文檔進行內建就行?根據按照開發經驗,如果播放器沒有開啟背景服務,程式退出到背景,調用第三方的服務也會經常報錯,是以說明安卓用戶端如果需要實作一個播放器,就需要在背景開啟一個背景服務來實作背景播放功能。

繼續閱讀