Android入門教程 | 四大元件之Service(前臺服務,後臺服務)
Service是一種可在後臺執行長時間執行操作而不提供介面的應用元件。服務可由其他應用元件啟動,而且即使使用者切換到其他應用,服務仍將在後臺繼續執行。
此外,元件可透過繫結到服務與之進行互動,甚至是執行程式間通訊 (IPC)。 例如,服務可在後臺處理網路事務、播放音樂,執行檔案 I/O 或與內容提供程式進行互動。
前臺服務
臺服務執行一些使用者能注意到的操作。例如,音訊應用會使用前臺服務來播放音訊曲目。前臺服務必須顯示通知。 即使使用者停止與應用的互動,前臺服務仍會繼續執行。
啟動前臺服務
前臺服務可以給使用者提供介面上的操作。 每個前臺服務都必須要在通知欄顯示一個通知(notification)。使用者可以感知到app的前臺服務正在執行。 這個通知(notification)預設是不能移除的。服務停止後,通知會被系統移除。 當使用者不需要直接操作app,app需要給使用者一個狀態顯示的時候,可以用前臺服務。
在 activity 中啟動服務,呼叫
startForegroundService(Intent)
方法。
startForegroundService(Intent(applicationContext, ForegroundService1::class.java))
然後在 service 中,需要對應地使用
startForeground
方法。
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()}") val pendingIntent: PendingIntent = Intent(this, ForegroundDemoAct::class.java).let { notificationIntent -> PendingIntent.getActivity(this, 0, notificationIntent, 0) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val chanId = "f-channel" val chan = NotificationChannel(chanId, "前臺服務channel", NotificationManager.IMPORTANCE_NONE) chan.lightColor = Color.BLUE chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager service.createNotificationChannel(chan) Log.d(TAG, "服務呼叫startForeground") val notification: Notification = Notification.Builder(applicationContext, chanId) .setContentTitle("RustFisher前臺服務") .setContentText(") .setSmallIcon(R.drawable.f_zan_1) .setContentIntent(pendingIntent) .build() startForeground(1, notification) } else { Log.d(TAG, "${Build.VERSION.SDK_INT} < O(API 26) ") } return super.onStartCommand(intent, flags, startId) }`
我們來看 service 裡的這段程式碼。建立了一個簡單的
Notification
。
- PendingIntent會被分配給Notification,作為點選通知後的跳轉動作
- 使用NotificationManager先建立了一個NotificationChannel
- 用Notification.Builder配置並建立一個Notification,例如配置標題,內容文字,圖示等
- 啟動前臺服務,呼叫
startForeground(1, notification)
方法
在裝置上會顯示出一個通知,點選這個通知,會跳轉到 ForegroundDemoAct 。這是之前用 PendingIntent 設定的。
停止服務
可以用
stopService
來停止服務
stopService(Intent(applicationContext, ForegroundService1::class.java))
這樣 Service 退出,走
onDestroy
方法。
停止前臺服務
在Service中呼叫
stopForeground(boolean)
方法,能停止前臺,但是不退出整個服務。 這個boolean表示是否取消掉前臺服務的通知。false表示保留通知。
例如在Service中呼叫
stopForeground(false)
服務變成了後臺服務,並沒有退出。此時對應的通知可以滑動取消掉。
報錯資訊
ANR
在Activity中呼叫
startForegroundService(Intent)
啟動服務,但是不呼叫
Service.startForeground()
。 一加5手機Android10執行log如下
2021-08-26 23:03:25.352 25551-25551/com.rustfisher.tutorial2020 D/rustAppUseStartService: 呼叫 startForegroundService 主執行緒資訊Thread[main,5,main]2021-08-26 23:03:25.368 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onCreate Thread[main,5,main] rustfisher.com2021-08-26 23:03:25.370 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onStartCommand flags:0, startId:1 [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]2021-08-26 23:03:35.375 1596-1720/? W/ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}2021-08-26 23:03:35.382 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onDestroy [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]2021-08-26 23:03:52.956 1596-1720/? E/ActivityManager: ANR in com.rustfisher.tutorial2020 PID: 25551 Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}`
Bad notification
我們在
ForegroundService1的方法
onStartCommand
里加入
startForeground
。 如果
startForeground(0, noti)
的id傳入0,則會報錯
RemoteServiceException
。
29871-29871/com.rustfisher.tutorial2020 E/AndroidRuntime: FATAL EXCEPTION: main Process: com.rustfisher.tutorial2020, PID: 29871 android.app.RemoteServiceException: Bad notification for startForeground
後臺服務
後臺服務執行使用者不會直接注意到的操作。例如,如果應用使用某個服務來壓縮其儲存空間,則此服務通常是後臺服務。
- 文中的服務/Service 指的是後臺服務。
- 示例使用Kotlin實現。
新建服務
我們新建一個 ServiceStartDemo 類繼承 Service
class ServiceStartDemo : Service() { companion object { const val TAG = "rustAppStartDemoService" } override fun onCreate() { super.onCreate() Log.d(TAG, "onCreate ${Thread.currentThread()}") } override fun onBind(intent: Intent): IBinder? { Log.d(TAG, "onBind ${Thread.currentThread()}") return null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()}") return super.onStartCommand(intent, flags, startId) } override fun onDestroy() { super.onDestroy() Log.d(TAG, "onDestroy [$this] ${Thread.currentThread()}") } }
- 在各個生命週期方法中我們打上log,便於後續觀察
- log 中打出服務物件的詳細資訊,執行緒資訊等等
- onBind 方法中我們返回null,表明這個服務不能用bindService的方式啟動
在 AndroidManifest.xml 中註冊這個服務
<manifest xmlns:android=" <!-- ... --> <application> <service android:name=".service.start.ServiceStartDemo" android:enabled="true" android:exported="false" /> <!-- ... --> </application></manifest>
注意
- name 是我們的服務的類名。它是唯一必需的屬性
- enabled 為 true,表明系統可以示例化這個服務。預設值為 true
- application 也有自己的 enabled 屬性,預設值為 true。該屬性適用於所有應用元件,包括服務
- exported 這裡設定為 false,表明不給其他程式(app)使用,僅適用於我們這個 app
這樣我們的服務就準備完畢了。
startService 啟動服務
在 activity 中呼叫 startService 方法,啟動服務。
startService(Intent(applicationContext, ServiceStartDemo::class.java))
呼叫方法後,ServiceStartDemo服務會啟動起來。 首次啟動的話,服務會走onCreate和onStartCommand方法。 初始化性質的程式碼,放在onCreate裡。
服務已經存在的情況下,用startService方法啟動服務,服務會走onStartCommand方法。 此時onStartCommand裡的startId會自增。用這個數值可以看出這個Service物件被啟動了多少次。
同時我們可以在Service的log裡觀察到,Service的生命週期函式是在主執行緒中執行的。 因此Service也可能會遇到ANR問題。不能把過於耗時的任務放在生命週期函式里。
Activity 與 Service 溝通
Activity 與 Service 是相互獨立的元件。用
startService
方法啟動服務並不會讓 activity 持有service 的例項。 它們之間可以用廣播來進行溝通。或者用 EventBus 之類的工具進行溝通。
停止服務
完成任務後,我們可以停止服務。節省系統資源。 前面是用
startService
方法啟動的服務,後面用
stopService(Intent)
來停止服務。
方法 | 介紹 |
---|---|
stopService(Intent) |
Activity或其他元件呼叫這個方法,停止目標service |
stopSelf() |
Service呼叫這個方法來停止自己 |
例如在Activity中
stopService(Intent(applicationContext, ServiceStartDemo::class.java))
在Service中
stopSelf()
一旦請求使用
stopSelf()
或
stopService()
來停止服務,服務會走
onDestroy()
方法。 系統會盡快銷燬服務。
繫結服務
當應用元件透過呼叫
bindService()
繫結到服務時,服務即處於繫結狀態。
繫結服務會提供客戶端-伺服器介面,以便元件與服務進行互動、傳送請求、接收結果,甚至是利用程式間通訊 (IPC) 跨程式執行這些操作。僅當與另一個應用元件繫結時,繫結服務才會執行。多個元件可同時繫結到該服務,但全部取消繫結後,該服務即會被銷燬。
Service 相關面試題
1. Service是什麼
Service 是 Android 四大元件之一,它可以在後臺執行長時間執行操作而沒有使用者介面的應用元件。
Service 的啟動方式有兩種:startService 啟動和 bindService 啟動。
注意:服務與其他應用程式物件一樣,在其託管程式的主執行緒中執行。這意味著,如果你的服務要執行任何CPU密集型(例如 MP3 播放)或阻塞(例如網路)操作,它應該在Service中再建立一個子執行緒,然後在這裡去處理耗時操作就沒問題了。
2. 註冊Service需要注意什麼
Service 還是執行在主執行緒當中的,所以如果需要執行一些複雜的邏輯操作,最好在服務的內部手動建立子執行緒進行處理,否則會出現UI執行緒被阻塞的問題。
3. Service與Activity怎麼實現通訊
方法一:
- 新增一個繼承Binder的內部類,並新增相應的邏輯方法
- 重寫Service的onBind方法,返回我們剛剛定義的那個內部類例項
- 重寫ServiceConnection,onServiceConnected時呼叫邏輯方法 繫結服務
方法二
- 透過介面Iservice呼叫Service方法,使用介面呼叫service和直接呼叫其實本質都是一樣的,只不過多了藉口一個步驟
4. IntentService與Service的區別(intentservice的優點)
IntentService是Service的子類,是一個非同步的,會自動停止的服務,很好解決了傳統的Service中處理完耗時操作忘記停止並銷燬Service的問題
- 會建立獨立的 worker 執行緒來處理所有的 Intent 請求;
- 會建立獨立的 worker 執行緒來處理 onHandleIntent() 方法實現的程式碼,無需處理多執行緒問題;
- 所有請求處理完成後,IntentService 會自動停止,無需呼叫 stopSelf() 方法停止 Service;
- 為 Service的onBind() 提供預設實現,返回 null;
- 為 Service 的 onStartCommand 提供預設實現,將請求 Intent 新增到佇列中;
- IntentService 不會阻塞UI執行緒,而普通 Serveice 會導致 ANR 異常
- Intentservice 若未執行完成上一次的任務,將不會新開一個執行緒,是等待之前的任務完成後,再執行新的任務,等任務完成後再次呼叫stopSelf()
5. Service 是否在 main thread 中執行, service 裡面是否 能執行耗時的操作?
預設情況,如果沒有顯示的指 service 所執行的程式, Service 和 activity 是運 行在當前 app 所在程式的 main thread(UI 主執行緒)裡面。
service 裡面不能執行耗時的操作(網路請求,複製資料庫,大檔案 )
特殊情況 ,可以在清單檔案配置 service 執行所在的程式 ,讓 service 在另 外的程式中執行
<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" ></service>
6. Service的生命週期
Service 有繫結模式和非繫結模式,以及這兩種模式的混合使用方式。不同 的使用方法生命週期方法也不同。
- 非繫結模式:當第一次呼叫 startService 的時候執行的方法依次為 onCreate()、onStartCommand(),當 Service 關閉的時候呼叫 onDestory 方 法。
- 繫結模式:第一次 bindService()的時候,執行的方法為 onCreate()、 onBind()解除繫結的時候會執行 onUnbind()、onDestory()。
上面的兩種生命週期是在相對單純的模式下的情形。我們在開發的過程中還 必須注意 Service 例項只會有一個,也就是說如果當前要啟動的 Service 已經存 在了那麼就不會再次建立該 Service 當然也不會呼叫 onCreate()方法。
一個 Service 可以被多個客戶進行繫結,只有所有的繫結物件都執行了
onBind() 方法後該 Service 才會銷燬,不過如果有一個客戶執行了 onStart() 方法,那麼這個時候如果所有的 bind 客戶都執行了 unBind() 該 Service 也不會 銷燬。
Service 的生命週期圖如下所示,幫助大家記憶。
只使用 startService 啟動服務的生命週期
只使用BindService繫結服務的生命週期
同時使用 startService() 啟動服務、BindService() 繫結服務的生命週期
7. Activity、Intent、Service 是什麼關係
他們都是 Android 開發中使用頻率最高的類。其中 Activity 和 Service 都是 Android 四大元件之一。他倆都是 Context 類的子類 ContextWrapper 的子類, 因此他倆可以算是兄弟關係吧。不過兄弟倆各有各自的本領, Activity 負責使用者 介面的顯示和互動, Service 負責後臺任務的處理。Activity 和 Service 之間可 以透過 Intent 傳遞資料,因此可以把 Intent 看作是通訊使者。
8. Service 和 Activity 在同一個執行緒嗎?
對於同一 app 來說預設情況下是在同一個執行緒中的,main Thread (UI Thread)。
9. 如何提高service的優先順序?
- 在
AndroidManifest.xml
檔案中對於intent-filter可以透過android:priority = “1000”
這個屬性設定最高優先順序,1000 是最高值,如果數字越小則優先順序越低,同時實用於廣播。 - 在 onStartCommand 裡面呼叫
startForeground()
方法把Service提升為前臺程式級別,然後再onDestroy裡面要記得呼叫stopForeground ()
方法。 - onStartCommand方法,手動返回START_STICKY。
- 在 onDestroy 方法裡發廣播重啟 service。 service +broadcast 方式,就是當 service 走 ondestory 的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動 service。(第三方應用或是在setting裡-應用-強制停止時,APP 程式就直接被幹掉了,onDestroy方法都進不來,所以無法保證會執行)
- 監聽系統廣播判斷 Service 狀態。 透過系統的一些廣播,比如:手機重啟、介面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的Service 是否還存活。
- Application 加上 Persistent 屬性。
10. Service 的 onStartCommand 方法有幾種返回值?各代表什麼意思?
有四種返回值:
- START_STICKY:如果 service 程式被 kill 掉,保留 service 的狀態為開始狀態,但不保留遞送的 intent 物件。隨 後系統會嘗試重新建立 service,由於服務狀態為開始狀態,所以建立服務後一定會呼叫 onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到 service,那麼引數 Intent 將為 null。
- START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完 onStartCommand 後,服務被異常 kill 掉,系統不會自動重啟該服務。
- START_REDELIVER_INTENT:重傳 Intent。使用這個返回值時,如果在執行完 onStartCommand 後,服務被異 常 kill 掉,系統會自動重啟該服務,並將 Intent 的值傳入。
- START_STICKY_COMPATIBILITY: START_STICKY 的相容版本,但不保證服務被 kill 後一定能重啟。
11. Activity 呼叫 Service 中的方法都有哪些方式?
- Binder: 透過 Binder 介面的形式實現,當 Activity 繫結 Service 成功的時候 Activity 會在 ServiceConnection 的類 的 onServiceConnected()回撥方法中獲取到 Service 的 onBind()方法 return 過來的 Binder 的子類,然後透過物件呼叫方法。
- Aidl: aidl 比較適合當客戶端和服務端不在同一個應用下的場景。
- Messenger: 它引用了一個Handler物件,以便others能夠向它傳送訊息(使用mMessenger.send(Message msg)方法)。該類允許跨程式間基於Message的通訊(即兩個程式間可以透過Message進行通訊),在服務端使用Handler建立一個Messenger,客戶端持有這個Messenger就可以與服務端通訊了。一個Messeger不能同時雙向傳送,兩個就就能雙向傳送了
12. Service和Thread的區別
Service是安卓中系統的元件,它執行在獨立程式的主執行緒中,不可以執行耗時操作。
Thread是程式執行的最小單元,分配 CPU 的基本單位,可以開啟子執行緒執行耗時操作。
Service 在不同 Activity 中可以獲取自身例項,可以方便的對 Service 進行操作。
Thread 在不同的 Activity 中難以獲取自身例項,如果 Activity 被銷燬,Thread例項就很難再獲取得到。
13. 使用IntentService
IntentService 是 Scrvice 的子類,因此它不是普通的 Service,它比普通的Service 增加了額外的功能。
先看 Service 本身存在的兩個問題。
- Service不會專門啟動一個單獨的程式,Service與它所在應用位於同一個程式中。
- Service不是一條新的執行緒,因此不應該在Service中直接處理耗時的任務。
IntentService正好彌補了Service的不足。
IntentService的特點:
- IntentService會建立單獨的worker執行緒來處理所有的Intent請求。
- IntentService會建立單獨的worker執行緒來處理onHandleIntent()方法實現的程式碼,因此開發者無須處理多執行緒問題。
IntentService例項
- 建立 SccIntentService.java 繼承自 IntentService 類,重寫 onHandleIntent() 方法、建立一個無參建構函式,其程式碼如下:
public class SccIntentService extends IntentService { public SccIntentService() { super("SccIntentService"); } @Override protected void onHandleIntent(Intent intent) { MLog.e(getClass().getName(), "onHandleWork"); for (int i = 0; i < 3; i++) { try { MLog.e(getClass().getName(), "Number:開始"+i); Thread.sleep(10000); MLog.e(getClass().getName(), "Number:結束"+i); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void onDestroy() { super.onDestroy(); MLog.e(getClass().getName(), "onDestroy"); } }
-
新增 IntentService 元件宣告,在 AndroidManifest.xml 檔案中宣告一個 Service 元件,其程式碼如下:
<service android:name=".service.SccIntentService"/>
-
啟動 SccIntentService
startService(new Intent(ServiceActivity.this, SccIntentService.class));</pre>
-
執行結果
07-07 18:00:39.505 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonCreate07-07 18:00:39.531 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonStart07-07 18:00:39.531 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonResume07-07 18:01:12.690 E/-SCC-com.scc.demo.service.SccIntentService: onHandleWork07-07 18:01:12.690 E/-SCC-com.scc.demo.service.SccIntentService: Number:開始007-07 18:01:22.691 E/-SCC-com.scc.demo.service.SccIntentService: Number:結束007-07 18:01:22.697 E/-SCC-com.scc.demo.service.SccIntentService: Number:開始107-07 18:01:32.698 E/-SCC-com.scc.demo.service.SccIntentService: Number:結束107-07 18:01:32.698 E/-SCC-com.scc.demo.service.SccIntentService: Number:開始207-07 18:01:42.699 E/-SCC-com.scc.demo.service.SccIntentService: Number:結束207-07 18:01:42.716 E/-SCC-com.scc.demo.service.SccIntentService: onDestroy
普通 Service 直接執行 20S 的的耗時操作,會阻塞主執行緒,造成 ANR (程式無響應)異常。
IntentService 執行30S的耗時操作,不會阻塞主執行緒,更不會產生ANR。如上圖開始18:01:12>18:01:42長達30S,正常執行未產生ANR。
IntentService還有個好處就是 「用完即走」。執行完onHandleIntent()方法裡面的耗時操作後,自行呼叫onDestroy()方法,進行關閉。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2809902/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android SERVICE後臺服務程式的守護Android
- Android 使用前臺服務Android
- 高效管理 Android 前臺服務Android
- 仿掘金前臺 vue 服務端渲染(ssr)後臺 react (spa) 後臺服務是 koa 的一個專案Vue服務端React
- Android Service 服務(一)—— ServiceAndroid
- Android四大元件之服務————服務的生命週期和啟動方式Android元件
- 打造跨平臺.NET Core後臺服務
- Mac Redis 服務後臺執行MacRedis
- 資料中臺即服務——資料中臺的四大支柱
- 網站重構-後臺服務篇網站
- BaaS後端即服務 - 中臺篇後端
- [NODEJS]後臺執行某服務NodeJS
- Android中後臺的服務和多執行緒Android執行緒
- 安卓開發之服務Service安卓
- 【go-web服務端】入門教程GoWeb服務端
- 細說TF服務鏈丨服務鏈後臺的路由實現路由
- Android Service 服務(二)—— BroadcastReceiverAndroidAST
- Android8.0 後臺服務保活的一種思路Android
- Windows 下配置 Logstash 為後臺服務Windows
- centos7後臺服務部署jar包CentOSJAR
- Egg.js搭建後臺服務APIJSAPI
- 期待微軟平臺即服務技術Service Fabric 開源微軟
- Flask服務入門案例Flask
- 小白入門微服務(4) – 服務註冊與服務發現微服務
- 小白入門微服務(4) - 服務註冊與服務發現微服務
- ABP框架—後臺:應用服務ApplicationServices(9)框架APP
- 服務網格service mesh 之 Linkerd
- SOA企業服務平臺
- Docker入門(三):nodejs後端服務部署DockerNodeJS後端
- android service,服務的正確姿勢Android
- Android Service 服務(三)—— bindService與remoteServiceAndroidREM
- 服務網格 Service Mesh
- Android 四大元件之 ServiceAndroid元件
- Android 四大元件之" Service "Android元件
- 負載均衡服務之HAProxy基礎入門負載
- 高可用服務之Keepalived基礎入門
- 【Azure 應用服務】Azure App Service 自帶 FTP服務APPFTP
- Dubbo Mesh - 從服務框架到統一服務控制平臺框架