學習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"/>