如何正确的使用 Service?
簡介
Service(服務)是 Android 四大組件之一,它的主要作用是執行后臺操作,Activity 提供了 UI 界面來跟用戶交互,而 Service 則沒有 UI 界面,所有的操作都是在后臺完成。
Service 跟 Activity 一樣也可以由其它應用程序啟動,即使用戶切換到了其它應用,Service 仍然保持在后臺運行。
此外,一個組件可以與 Service 進行綁定(bind)來跟 Service 進行交互,甚至是進行進程間通信(IPC)。
通常情況下可以使用 Service 進行網絡請求、播放音樂、文件 I/O 等操作。
創建服務
要創建一個 Service 首先需要繼承 Service 來實現一個子類。
public class TestService extends Service {@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags,int startId) {return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic boolean onUnbind(Intent intent) {return super.onUnbind(intent);}@Overridepublic void onDestroy() {super.onDestroy();} }類似于 Activity,所有的 Service 都要在 Manifest 里面進行聲明,如下:
<manifest ... >...<application ... ><service android:name="xxx.xxxs.TestService" />...</application> </manifest>通過在 <service> 標簽里將 android:exported 設置為 false。可以防止其他的程序來啟動你的 Service。
啟動服務
通常情況下有兩種方式來啟動 Service,startService() 和 bindService()。
startService()
Intent intent = new Intent(this, TestService.class); startService(intent); // 開啟服務 stopService(intent); // 停止服務當組件通過調用 startService() 啟動 Service 后,Service 就可以在后臺無限期的運行,即使啟動 Service 的組件被銷毀也不受影響。
一般情況下 startService() 是執行單一操作,并且不會將執行結果返回給調用者。例如,它可能是下載文件或者上傳文件,通常操作完成后會自動停止。
該方式允許多個組件同時對相同的 Service 進行 startService() 操作,但是如果只要有其中有一個組件調用了 stopSelf() 或 stopService(), 該 Service 就會被銷毀。
bindService()
Intent intent = new Intent(this, TestService.class); ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {}@Overridepublic void onServiceDisconnected(ComponentName name) {} }; // 綁定服務 bindService(intent, connection, Context.BIND_AUTO_CREATE); // 解綁服務 unbindService(aidlConnection);當組件通過調用 bindService() 啟動 Service 后,Service 就處于綁定狀態了。這種方式提供了 client - service 的接口,可以讓調用者與 Service 進行發送請求和返回結果的操作,甚至可以進行進程間的通信(IPC)。
只要有一個組件對該 Service 進行了綁定,那該 Service 就不會銷毀。如果多個組件可以同時對一個 Service 進行綁定,只有所有綁定的該 Service 的組件都解綁后,該 Service 才會銷毀。
盡管兩種方式是分開討論的,但是并不是互斥的關系,使用 startService() 啟動了 Service 后,也是可以進行綁定的。
注意:雖然 Service 是在后臺運行的,但其實還是在主線程中進行所有的操作。Service 啟動時除非單獨進行了定義,否則沒有單獨開啟線程或者進程都是運行在主線程中。
所以任何能阻塞主線程的操作(例如:播放音樂或者網絡請求),都應該在 Service 中單獨開啟新的線程來進行操作,否則很容易出現 ANR。
系統方法
在創建一個 Service 時,必須要去繼承 Service,并且需要重寫父類的一些方法來實現功能。以下是主要方法的介紹。
onStartCommand()
當另一個組件(如:Activity)通過調用 startService() 來啟動 Service 時,系統會調用該方法。一旦執行該方法,Service 就會啟動并在后臺無限期執行。
如果實現該方法,在 Service 執行完后,需要調用 stopSelf() 或 stopService() 來停結束Service。
如果只是會通過綁定的方式(bind)的方式來啟動 Service 則不需要重寫該方法。
onBind()
系統會調用這個函數當某個組件(例如:activity,fragment)通過調用 bindService() 綁定的方式來啟動 Service 的時候。在實現這個函數的時候,必須要返回一個 IBinder 的繼承類,來與 Service 進行通信。
這個函數是默認必須要重寫的,但是如果不想通過綁定的方式來啟動 Service,則可以直接返回 null。
onCreate()
系統會調用此方法在第一次啟動 Service 的時候,用于初始化一些一次性的變量。如果 Service 已經啟動了,則此方法就不會再別調用。
onDestroy()
系統在 Service 已經不需要準備被銷毀的時候會調用此方法。Service 中如有用到 thread、listeners、receivers 等的時候,應該將這些的清理方法寫在此方法內。
生命周期
與 Activity 類似,Service 也有生命周期回調方法,可以實現這些方法來監控 Service 狀態的變化來執行相關操作。
startService()
onCreate() -> onStartCommand() -> onDestroy()
bindService()
onCreate() -> onBind() -> onUnbind() -> onDestroy()
系統資源回收
當系統內存不足的時候,系統會強制回收一些 Activity 和 Service 來獲取更多的資源給那些用戶正在交互的程序或頁面。當資源充足的時候可以通過 onStartCommand() 的返回值,來實現 Service 自動重啟。
public int onStartCommand(Intent intent, int flags, int startId) {return START_NOT_STICKY | START_STICKY | START_REDELIVER_INTENT; }START_NOT_STICKY
當系統因回收資源而銷毀了 Service,當資源再次充足時不再自動啟動 Service,除非有未處理的 Intent 準備發送。
START_STICKY
當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service。而且再次調用 onStartCommand() 方法,但是不會傳遞最后一次的 Intent,相反系統在回調 onStartCommand() 的時候會傳一個空 Intent,除非有未處理的 Intent 準備發送。
START_REDELIVER_INTENT
當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service,并且再次調用 onStartCommand() 方法,并會把最后一次 Intent 再次傳遞給 onStartCommand(),相應的在隊列里的 Intent 也會按次序一次傳遞。此模式適用于下載等服務。
IntentService
Service 本身默認是運行在主線程里的,所以如果在 Service 要進行一些會堵塞線程的操作,一定要將這些操作放在一個新的線程里。
為了滿足后臺運行異步線程的需求,Android 的框架提供了 IntentService。
IntentService 是 Service 的子類,并且所有的請求操作都是在異步線程里。如果不需要 Service 來同時處理多個請求的話,IntentService 將會是最佳的選擇。
使用該服務只需要繼承并重寫 IntentService 中的 onHandleIntent() 方法,就可以對接受到的 Intent 做后臺的異步線程操作了。
public class TestIntentService extends IntentService {public TestIntentService() {super("TestIntentService");}public TestIntentService(String name) {super(name);}@Overridepublic void onCreate() {super.onCreate();}@Overrideprotected void onHandleIntent(@Nullable Intent intent) {//TODO: 耗時操作,運行在子線程中}@Overridepublic void onDestroy() {super.onDestroy();} }前臺服務
什么是前臺服務?
前臺服務是那些被認為用戶知道(用戶所認可的),且在系統內存不足的時候不允許系統殺死的服務。
前臺服務必須給狀態欄提供一個通知,它被放到正在運行(Ongoing)標題之下 —— 這就意味著通知只有在這個服務被終止或從前臺主動移除通知后才能被解除。
為什么要使用前臺服務?
在一般情況下,Service 幾乎都是在后臺運行,一直默默地做著辛苦的工作。但這種情況下,后臺運行的Service系統優先級相對較低,當系統內存不足時,在后臺運行的 Service 就有可能被回收。
那么,如果我們希望 Service 可以一直保持運行狀態,且不會在內存不足的情況下被回收時,可以選擇將需要保持運行的 Service 設置為前臺服務。
例如:App中的音樂播放服務應被設置在前臺運行(前臺服務)——在 App 后臺運行時,便于用戶明確知道它的當前操作、在狀態欄中指明當前歌曲信息、提供對應操作。
如何創建一個前臺服務?
新建一個服務。
public class ForegroundService extends Service {private static final int RESULT_CODE = 0;private static final int ID = 1;public ForegroundService() { }@Overridepublic void onCreate() {super.onCreate();Intent intent = new Intent(this, MainActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(this, RESULT_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);NotificationCompat.Builder builder;// 兼容 Android 8.0if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {String channelId = "foreground_service";NotificationChannel channel = new NotificationChannel(channelId, "channel_1", NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);channel.setLightColor(Color.GREEN);channel.setShowBadge(true);NotificationManager notificationManager = getSystemService(NotificationManager.class);notificationManager.createNotificationChannel(channel);builder = new NotificationCompat.Builder(this, channelId);} else {builder = new NotificationCompat.Builder(this);}builder.setContentIntent(pendingIntent).setContentTitle("這是前臺通知標題").setContentText("這是內容").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher_round).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setPriority(NotificationManager.IMPORTANCE_HIGH).setDefaults(Notification.DEFAULT_SOUND);startForeground(ID, builder.build());}@Overridepublic int onStartCommand(Intent intent, int flags,int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {return super.onBind(intent);} }啟動與停止前臺服務
Intent foregroundIntent = new Intent(this, ForegroundService.class); startService(foregroundIntent); // 啟動前臺服務 stopService(foregroundIntent); // 停止前臺服務前臺服務與普通服務的區別
- 前臺 Service 的系統優先級更高、不易被回收;
- 前臺 Service 會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄后可以看到更加詳細的信息,非常類似于通知的效果。
服務保活
通過前面的介紹我們了解到 Service 是后臺服務來執行一些特定的任務,但是當后臺服務在系統資源不足的時候可能會回收銷毀掉 Service。
那么如何讓后臺服務盡量不被殺死呢?基本解決思路如下:
提升 Service 的優先級
為防止 Service 被系統回收,可以嘗試通過提高服務的優先級解決。優先級數值最高為 1000,數字越小,優先級越低。
<service android:name=".ui.service.TestService" ><intent-filter android:priority="1000"/> </service>persistent 屬性
在 Manifest.xml 文件中設置 persistent 屬性為 true,則可使該服務免受 out-of-memory killer 的影響。但是這種做法一定要謹慎,系統服務太多將嚴重影響系統的整體運行效率。
<application android:persistent="true"> </application>該屬性的特點如下:
- 在系統啟動的時候會被系統啟動起來。
- 在該 App 被強制殺掉后系統會重新啟動該 App,這種情況只針對系統內置App,第三方安裝的 App 不會被重啟。
將服務改成前臺服務
重寫 onStartCommand 方法,使用 startForeground(int, Notification) 方法來啟動 Service。利用 Android 的系統廣播
利用 Android 的系統廣播檢查 Service 的運行狀態,如果被殺掉就重啟。系統廣播是 Intent.ACTION_TIME_TICK,這個廣播每分鐘發送一次。我們可以每分鐘檢查一次 Service 的運行狀態,如果已經被銷毀了,就重新啟動 Service。
參考資料
- 官方文檔 - 服務
- Service 前臺服務的使用
我的 GitHub
github.com/jeanboydev
我的公眾號
歡迎關注我的公眾號,分享各種技術干貨,各種學習資料,職業發展和行業動態。
技術交流群
歡迎加入技術交流群,來一起交流學習。
總結
以上是生活随笔為你收集整理的如何正确的使用 Service?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [python]打日语
- 下一篇: java 图片不失真缩放,ico格式图片