Android Service

醉過才知酒濃發表於2019-01-25

[TOC] 在Android中 Service 是一個可以在後臺執行長時間操作而不提供使用者介面的應用元件。Service可以有其他應用元件啟動,而且當使用者切換到其他應用,Service仍將在後臺執行。此外,元件可以繫結到服務,以與之進行互動,甚至是執行程式間通訊(IPC)。例如,Service可以處理網路事務、播放音樂,執行檔案I/O或與內容提供程式互動,而所有這一切均可在後臺執行。

注意:

  1. Service是在其託管程式的主程式中執行,它既不建立自己的執行緒,也不在單獨的程式中執行(除非另行指定)。這意味著,==在Service中執行任何耗時操作都應該在Service內建立新執行緒來完成這項操作。== 通過使用單獨的執行緒,可以降低發生“應用無響應”(ANR)錯誤的風險。
  2. 任何元件(如Activity)都可以控制同一個Service,而系統也只會建立一個對應的Service例項。

Service服務基本上可以分為兩種形式:

1. 啟動: 當應用元件(如Activity)通過呼叫 startService() 啟動Service時,Service即處於“啟動”狀態。一旦啟動,服務即可在後臺無限期執行,即使啟動服務的元件已經被銷燬也不受影響。已啟動的服務通常是執行單一操作,而且不會將結果返回給呼叫方。例如,它可能通過網路下載或上傳檔案。操作完成後,服務自動停止執行。

2. 繫結: 當應用元件(如Activity)通過呼叫 bindService() 繫結到Service時,Service即處於“繫結”狀態。繫結Service提供一個客戶端-伺服器介面,允許元件和Service進行互動、傳送請求、獲取結果,甚至是利用程式間通訊(IPC)跨程式執行這些操作。僅當有應用元件繫結時,繫結Service才會執行。多個元件可以同時繫結到同一個Service,但全部取消繫結後,該Service就會被銷燬。

這是兩種形式的Service的生命週期:

image
**注意:**Service是可以同時以這兩種方式執行,也就是說,它就可以是啟動Service(啟動後無限執行),也允許繫結(隨繫結元件一起銷燬)。問題在於Service是否實現了一組方法: onStartCommand()(允許元件啟動Service) 和 onBind() (允許繫結Service)。

1.Service的基本應用

建立Service,必須是Service的子類。 如下:

open class BaseService :Service(){
    val TAG = "hugo"
    override fun onCreate() {
        super.onCreate()
        LogUtil.i(TAG,"onCreate-----------")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        LogUtil.i(TAG,"onStartCommand-----------")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        super.onDestroy()
        LogUtil.i(TAG,"onDestroy-----------")
    }
    override fun onBind(intent: Intent?): IBinder? {
        LogUtil.i(TAG,"onBind-----------")
        return null
    }


    override fun onUnbind(intent: Intent?): Boolean {
        LogUtil.i(TAG,"onUnbind-----------")
        return super.onUnbind(intent)
    }
}

複製程式碼

然後根據需要重寫一些回撥方法:

  • onStartCommand() :當另一個元件(如Activity)通過呼叫 startService() 請求啟動服務時,系統就會呼叫此方法。一旦執行此方法,Service就會啟動並可在後臺無限期執行。所以如果實現這個方法,在完成服務任務後,需要手動通過呼叫 stopSelf() stopService() 方法來停止服務。(如果Service只想提供繫結服務,則可以不用實現此方法。)
    注意: 該方法的返回值是用於描述系統應該如何在服務終止的情況下繼續執行服務,該方法返回的值必須是以下常量之一:

1. START_NOT_STICKY:如果系統在 onStartCommand() 返回後終止服務,則除非有Intent要傳遞,否則系統==不會==重建服務。這是最安全的選項,可以避免在不必要時應用能夠輕鬆重啟所有未完成作業的服務。

2. START_STICKY:如果系統在 onStratCommand()返回後終止服務,則會==重建服務==並呼叫onStartCommand(),但==不會==重新傳遞最後一個Intent。相反,除非有Intent要啟動服務(在這種情況下,將傳遞這些Intent),否則系統會通過一個空Intent呼叫onStartCommand()。這個適用於不執行命令、但無限期執行並等待作業的媒體播放器(或類似服務)。 3. START_REDELIVER_INTENT:如果系統在 onStratCommand()返回後終止服務,則會==重建服務==,並通過傳遞給服務的最後一個Intent呼叫onStartCommand()。任何掛起的Intent均依次傳遞。這適用於主動執行應該立即恢復的作用(例如下載檔案)的服務。

  • onBind() :當另一個元件想通過呼叫 bindService() 與Service繫結時,系統將呼叫此方法。這個方法實現中必須返回 IBinder 提供一個介面,供客戶端用來和Service進行通訊。(如果不希望允許繫結,則應返回null。)
    注意: 該方法只有在第一個元件通過呼叫 bindService() 與Service繫結時,系統才會呼叫,而元件和以及有其他元件繫結過的Service繫結時,系統是不會在呼叫該方法。
  • onUnBind() :當通過 bindService() 繫結服務後,一旦該服務與所有元件之間的繫結全部取消後,系統會呼叫該方法。
  • onRebind() : 該方法在呼叫font color=0099ff> onUnBind() 方法是返回 true時在重新繫結時,系統會呼叫該方法。
  • onCreate() :首次建立Service時,系統將呼叫此方法來進行一些一次性設定程式(在呼叫 onStartCommand() onBind() 方法之前)。如果Service已經在執行,則不會呼叫此方法。
  • onDestroy():當Service不在使用被銷燬時,系統將呼叫此方法。應該實現這個方法用來清理Service用到的所有資源(如執行緒、註冊的偵聽器、接收器等)。這是Service接收的最後一個呼叫。
  • onConfigurationChanged() :該方法是在配置發生改變時呼叫,如螢幕旋轉時,系統就會回撥該方法。

如果元件通過呼叫 startService() 啟動服務(這會導致對 onStartCommand() 的呼叫),則服務將會一直執行,知道服務使用 stopSelf() 自行停止執行,或由其他元件通過呼叫 stopService() 停止它執行為止。

如果元件通過呼叫 bindService() 來建立繫結服務(且未呼叫 onStartServic() ),則服務只會在該元件與其繫結時執行。一旦該服務與所有的元件之間的繫結全部取消,系統便會銷燬它。

在使用Service時我們必須在清單檔案(AndroidManifest.xml)中宣告我們建立的服務如下:

<application ...>
    <service android:name=".BaseService"/>
</application>
複製程式碼

這樣我們就可在元件中使用 startService() bindService() 方法啟動或繫結服務了。

2.建立啟動Service

1.啟動服務

啟動服務由另一個元件(如Activity)通過呼叫 startService() 啟動,這會呼叫服務的 onStartCommand() 方法。

程式碼示例如下:

val intent = Intent(this,BaseService::class.java)
startService(intent)
複製程式碼

使用啟動建立Service的生命週期如下:

onCreate() -> onStartCommand() -> onDestroy()
注意: Service只有在還沒有建立時,啟動才會呼叫 onCreate() 方法,如果已經建立在呼叫 startService()方法時,Service 只會呼叫 onStartCommand()方法。

2.停止服務

服務啟動後,除非系統必須回收記憶體資源,否則系統不會停止或銷燬服務,而且服務在 onStartCommand() 返回後會繼續執行。其生命週期是獨立於啟動它的元件的,並且可以無限期的執行,即便啟動它的元件被銷燬了也是沒有影響的。所以我們必須在服務中結束工作後通過呼叫 stopSelf() 方法來自行停止執行,或者由另一個元件通過呼叫 stopService() 方法來停止執行。
程式碼示例如下:

class MyService :Service(){

     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        
        this.stopSelf()

        return super.onStartCommand(intent, flags, startId)
    }
}

class MainActivity : AppCompatActivity(){
    
    fun stopService(){
        val intent = Intent(this,MyService::class.java)
        this.stopService(intent)
    }
}

複製程式碼

一旦呼叫了 stopService() stopSelf() 方法停止服務,系統就會盡快的銷燬服務。

但是,如果服務同時處理多個 onStartCommand() 請求,則應該在所有的啟動請求處理完成之後在停止服務,而不是在第一個請求結束後而新的請求還未完成時就停止服務,這樣會使第二個啟動請求終止執行。為了避免這個問題,可以使用 stopSelf(int) stopSelfResult(int) 方法來確保服務停止請求始終是在最新的請求完成後。
stopSelfResult(int) :該方法會返回一個 Boolean 型別的值,如果為true,那麼這個請求就是最新的請求,系統就會把該服務停止,如果為false,那麼這個請求不是最新的請求,系統就不是停止該服務。
stopSelf(int) :該方法是一個沒有返回值的stopSelfResult(int)方法,除了沒有返回值,其他的都一樣。

示例程式碼如下:

class MyService :Service(){

     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        
        this.stopSelf(startId)
        
        //或var boolean = this.stopSelfResult(startId)

        return super.onStartCommand(intent, flags, startId)
    }
}
複製程式碼

注意: 為了避免浪費系統資源和消耗電池電量,我們應該在工作完成後停止服務。即使服務啟用了繫結,一旦收到了啟動請求的呼叫,那麼我們也仍需要親自停止服務。

3.建立繫結服務

繫結服務由應用元件通過呼叫 bindService() 與服務繫結,以便建立長期連線。

image

要建立繫結服務,必須實現 onBind() 回撥方法以返回 IBinder 用於定義組服務通訊介面。然後其他應用元件可以呼叫 bindService() 返回來進行繫結並獲得 onBind() 回撥方法返回的 IBinder 物件,元件可以通過 IBinder 物件和服務進行通訊。繫結服務只是用於其繫結的元件,因此當沒有元件繫結到該服務時,系統會自行銷燬該服務(所以繫結服務不用像 通過 onStartCommand() 啟動的服務需要手動來停止服務)。

繫結服務程式碼如下:

class MyService :BaseService(){




    override fun onBind(intent: Intent?): IBinder? {
        super.onBind(intent)
        Toast.makeText(this,"服務繫結了",Toast.LENGTH_SHORT).show()
//        this.stopSelf()

        return MyBinder()
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Toast.makeText(this,"服務解綁",Toast.LENGTH_SHORT).show()
        return super.onUnbind(intent)
//        return true
    }

    public interface MyIBinder{
        fun invokeMethodInMyService()
    }

    open class MyBinder :Binder() ,MyIBinder{

        fun stopService(){

        }

        override fun invokeMethodInMyService() {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }

    }
}


//MainAcitity
    if(myServiceConnection == null){
        myServiceConnection =  MyServiceConnection()
    }
    bindService(Intent(this,MyService::class.java),myServiceConnection, Context.BIND_AUTO_CREATE)

class MyServiceConnection:ServiceConnection{
        val TAG = "hugo"
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        //這個是繫結服務時回撥接收 IBinder 例項用的
            var binder = service as MyIBinder
            binder.invokeMethodInMyService()
            LogUtil.i(TAG,"onServiceConnected------  ComponentName:$name")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
        //這個是當出現意外和繫結服務斷開時的回撥
            LogUtil.i(TAG,"onServiceDisconnected------  ComponentName:$name")
        }

    }
複製程式碼

在繫結服務時需要傳入三個引數第一個是==Intent==,第二個是==ServiceConnection==介面的實現類,第三個是繫結的操作選項這個值可以是以下:

0
Context.BIND_AUTO_CREATE,
Context.BIND_DEBUG_UNBIND,
Context.BIND_NOT_FOREGROUND,
Context.BIND_ABOVE_CLIENT,
Context.BIND_ALLOW_OOM_MANAGEMENT,
Context.BIND_WAIVE_PRIORITY

這下面的是混合模式

0
Context. BIND_AUTO_CREATE,
Context.BIND_DEBUG_UNBIND,
Context.BIND_NOT_FOREGROUND,
Context. BIND_ABOVE_CLIENT,
Context.BIND_ALLOW_OOM_MANAGEMENT,
Context.BIND_WAIVE_PRIORITY,
Context.BIND_IMPORTANT
Context.BIND_ADJUST_WITH_ACTIVITY.

這些值的具體作用請看官方說明 注意需要科學上網

需要注意:
onCreate() 只有在服務第一次啟動時才會呼叫;
onBind()方法只有在第一次繫結時才會呼叫,從第二次繫結開始都不會在呼叫該方法。

使用繫結服務建立Service生命流程如下:

onCreate() -> onBind()->ServiceConnection.onServiceConnected() ->onUnbind() ->onDestroy()
注意:當在 onUnbind()方法返回true時, 下一次繫結時會走 onRebind()方法。(這個只在使用了啟動服務後才會生效)

4.解綁服務

繫結服務可以不用去解綁,因為繫結服務會在繫結的元件銷燬的時候自動解綁,但是不建議這麼做,因為這樣會產生ServiceConnection洩漏 如下:

 android.app.ServiceConnectionLeaked: Activity com.hugo.myservice.OtherActivity has leaked ServiceConnection com.hugo.myservice.OtherActivity$MyServiceConnection@c818357 that was originally bound here
        at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:1610)
        at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:1502)
        at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1659)
        at android.app.ContextImpl.bindService(ContextImpl.java:1612)
        at android.content.ContextWrapper.bindService(ContextWrapper.java:698)
        at com.hugo.myservice.OtherActivity$onCreate$3.onClick(OtherActivity.kt:33)
        at android.view.View.performClick(View.java:6597)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access$3100(View.java:778)
        at android.view.View$PerformClick.run(View.java:25885)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
複製程式碼

所以推薦在繫結元件銷燬前呼叫unbindService()方法進行解綁操作 如下:


    override fun onDestroy() {
        super.onDestroy()
        //myServiceConnection 是 ServiceConnection 的實現類物件
        if(myServiceConnection != null){
           unbindService(myServiceConnection)
        }
    }
複製程式碼

呼叫unbindService()時傳入繫結時傳入的ServiceConnection 物件。

在解綁後服務不會馬上銷燬,只有所有與該服務繫結的元件都解綁且該服務沒有用startService()啟動過,這是系統才會銷燬該服務。

相關文章