1. 程式人生 > 其它 >學習Android之Service

學習Android之Service

 Service是什麼

 是Android中實現程式後臺執行的解決方案。

 注意:Service並不是執行在一個獨立的程序當中的,而是依賴於建立Service時所在的應用程式程序。當某個應用程式程序被殺掉時,所有依賴於該程序的Service也會停止執行。

 實際上Service並不會自動開啟執行緒,所有的程式碼都是預設執行在主執行緒中的。所以,我們需要在Service內部手動建立子執行緒,並在 這裡執行具體的任務,否則就有可能出現主執行緒被阻塞的情況。

 定義一個Service

 可以在包名下右鍵New一個Service。

class MyService : Service() {

    override fun onBind(intent: Intent): IBinder {
        TODO(
"Return the communication channel to the service.") } }

 

 onBind方法是Service中唯一的抽象方法。

 處理事情的邏輯還需要重寫一些方法:

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        
super.onDestroy() }

 

 這3個方法最常用。

onCreate()方法會在Service建立的時候呼叫;

onStartCommand()方法會在每次Service啟動的呼叫;

onDestory()方法會在Service銷燬的時候呼叫。

 一般,如果需要Service一啟動就立刻執行任務,就將邏輯寫在onStartCommand()方法中。當Service銷燬時,在onDestory()方法中回收不再使用的資源。

 另外,每個Service都需要在AndroidManifest.xml檔案中進行註冊才能生效。

 

 啟動和停止Service

 藉助Intent來實現。

        startServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            startService(intent)
        }
        stopServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            stopService(intent)
        }

 

 另外,可以在Service內部呼叫stopSelf()方法自我停止。

 

 但是從Android 8.0系統開始,應用的後臺功能被削弱,現在只有當應用保持在前臺可見狀態的情況下,Service才能穩定執行,一旦進入應用後臺,Service隨時都有可能被系統回收。

 如果需要長期在後臺執行一些任務,可以使用前臺Service和WorkManager。

 

 Acitivty和Service通訊

 雖然Service是在Activity裡啟動的,但是在啟動了Service之後,Activity與Service基本就沒有什麼關係了。

 如果我們需要用Activity來控制住Service就需要藉助onBind()方法了;

 比如: 我們希望在MyService裡提供一個下載功能,然後在Activity中可以決定何時開始下載,以及隨時檢視下載進度。實現這個功能的思路是建立一個專門的Binder物件來對下載功能進行管理。

 修改MyService中的程式碼,如下所示:

    private val mBinder = DownloadBinder()

    class DownloadBinder : Binder() {
        fun startDownload() {
            Log.d("MySeriver", "startDownload: ")
        }
        fun getProgress(): Int {
            Log.d("MySeriver", "getProgress: ")
            return 0
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }

 

 這裡新建了一個DownloadBinder類繼承Binder,在內部提供方法。接著在MyService中建立了DownloadBinder的例項,在onBind()方法裡返回了這個例項。

 那麼在Activity中如何呼叫呢?

 

class MainActivity : AppCompatActivity() {
    lateinit var downloadBinder: MyService.DownloadBinder
    
    private val connection = object : ServiceConnection{
        override fun onServiceConnected(name: ComponentName?, service: IBinder) {
            downloadBinder = service as MyService.DownloadBinder
            downloadBinder.startDownload()
            downloadBinder.getProgress()
        }
        override fun onServiceDisconnected(name: ComponentName) {
            TODO("Not yet implemented")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bindServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE)   //繫結Service
        }
        unbindServiceBtn.setOnClickListener {
            unbindService(connection)   //解綁Service
        }
    }
}

 

 首先建立ServiceConnection的匿名類實現,重寫裡面的兩個方法。第一個方法是在Activity和Service成功繫結的時候呼叫,第二個只有在Service的建立程序崩潰或者被殺掉的時候呼叫,這個方法不太常用。

 我們又通過向下轉型得到了DownloadBinder的例項,有了這個例項,Activity和Service之間的關係就變得非常緊密了。

 現在我們可以在Activity中呼叫DownloadBinder中的任何public方法。這裡在onServiceConnected()方法中呼叫了DownloadBinder的startDownload()和getProgress()方法。

 但是Activity和Service的繫結是在按鈕的點選事件裡面完成的。呼叫bindService()方法進行繫結,接收三個引數:

  第一個引數:剛剛構建出的Intent物件,

  第二個引數:是前面創建出的ServiceConnection的例項,

  第三個引數:是一個標誌位,這裡傳入BIND_AUTO_CREATE表示在Activity和Service進行繫結後自動建立Service。

 解除繫結呼叫unbindService()方法即可。

 

 Service的生命週期

 Service有onCreate()、onStartCommand()、onBind()和onDestroy()等方法。

 一旦呼叫了startService()方法,相應的Service就會啟動,並回調onStartCommand()方法,如果這個Service之前還沒有建立過,就會先執行onCreate()方法,再執行onStartCommand()方法。

 注意:雖然每呼叫一次startService()方法,onStartCommand()方法就會執行一次,但實際上每個Service只會存在一個例項。所以不管呼叫了多少次onStartCommand()方法,只需要呼叫一次stopService()或stopSelf()方法,Service就會停止。

 還可以呼叫Context的bindService()來獲取一個Service的持久連線,這時就會回撥Service中的onBind()方法。呼叫方可以獲取到onBind()方法裡返回的IBinder物件的例項,這樣就能自由地和Service進行通訊了。

 注意:如果對一個Service既呼叫了startService()方法,又呼叫了bindService()方法的,這種情況下根據Android系統的機制,一個Service只要被啟動或者被綁定了之後,就會處於執行狀態,必須要讓以上兩種條件同時不滿足,Service才能被銷燬。所以,這種情況下要同時呼叫stopService()和unbindService()方法,onDestroy()方法才會執行。

 

 前臺Service

 從Android 8.0系統開始,只有當應用保持在前臺可見狀態的情況下,Service才能保證穩定執行,一旦應用進入後臺之後,Service隨時都有可能被系統回收。

 前臺Service和普通Service最大的區別就在於,它一直會有一個正在執行的圖示在系統的狀態列顯示,下拉狀態列後可以看到更加詳細的資訊。

 如何建立前臺Service,修改MyService中的程式碼,如下所示:

    override fun onCreate() {
        super.onCreate()
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel("my_service", "前臺Service通知", NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        val intent = Intent(this, MainActivity::class.java)
        val pi = PendingIntent.getActivity(this, 0, intent, 0)
        val notification = NotificationCompat.Builder(this, "my_service")
            .setContentTitle("this is content title")
            .setContentText("this is content text")
            .setSmallIcon(androidx.core.R.drawable.notification_icon_background)
            .setLargeIcon(BitmapFactory.decodeResource(resources, androidx.constraintlayout.widget.R.drawable.abc_btn_default_mtrl_shape))
            .setContentIntent(pi)
            .build()
        startForeground(1, notification)
    }

 用了建立通知的方法,只不過構建Notification物件後並沒有使用NotificationManager將通知顯示出來,而是呼叫了startForeground()方法,它接收兩個引數:

  第一個引數:通知的id;

  第二個引數:構建的Notification物件。

 呼叫startForeground()方法後就會讓MyService變成一個前臺Service,並在系統狀態列顯示出來。

 另外,Android 9.0系統開始,前臺Service需要去AndroidManifest.xml檔案中進行許可權宣告:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

 

 啟動前臺Service後,即使退出應用程式,MyService也會一直執行,如果殺掉應用,MyService就會停止運行了。

 

 IntentService

 Service中的程式碼都是預設執行在主執行緒中的,如果直接在Service中處理一些耗時邏輯,就很容易ANR(Application Not Responding)。

 這個時候就需要用到Android多執行緒程式設計技術了。可以在Service的每個具體方法裡開啟一個子執行緒,在裡面處理耗時邏輯。

 寫法如下:

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        thread { 
            // 處理具體的邏輯
        }
        return super.onStartCommand(intent, flags, startId)
    }

 

 但是,這種Service一旦啟動,就會一直處於執行狀態,必須呼叫stopService()或stopSelf()方法,或者被系統回收,Service才會停止。

 所以,如果想要實現讓一個Service在執行完畢後自動停止的功能,就要在thread中處理完邏輯的後面加上stopSelf()方法。

        thread {
            // 處理具體的邏輯
            stopSelf()
        }

 

 但是偶爾會忘記開啟執行緒或者呼叫stopSelf()。所以Android就專門提供了一個IntentServiec類,可以建立一個非同步的、自動停止的Service、

 新建一個MyIntentService類繼承自IntentService,程式碼如下所示:

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(p0: Intent?) {
        // 列印當前執行緒id
        Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "onDestroy executed")
    }
}

 

 這裡需要先呼叫父類的建構函式,並傳入一個字串,只是在呼叫的時候使用的。

 然後在子類中實現onHandleIntent()這個抽象方法,這個方法中可以處理一些耗時邏輯,因為它已經執行在子執行緒中了。

 它是會在執行完成後自動停止的,onDestroy()方法是為了驗證它有沒有自動停止。

 現在來啟動這個IntentService,修改MainActivity中的程式碼,如下所示:

        startIntentServiceBtn.setOnClickListener {
            // 列印主執行緒ID
            Log.d("MainActivity", "Thread id is ${Thread.currentThread().name}")
            val intent = Intent(this, MyIntentService::class.java)
            startService(intent)
        }

 

 其實IntentService的啟動方式和普通的Service沒什麼兩樣。

 對了,Service都是需要去AndroidManifest.xml裡註冊的:

        <service android:name=".MyIntentService"
            android:enabled="true"
            android:exported="true"/>