Android入門教程 | 四大元件之Service(前臺服務,後臺服務)

Android_anzi發表於2021-10-14

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例項

  1. 建立 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");
 }
}
  1. 新增 IntentService 元件宣告,在 AndroidManifest.xml 檔案中宣告一個 Service 元件,其程式碼如下:

    <service android:name=".service.SccIntentService"/>
  2. 啟動 SccIntentService

    startService(new Intent(ServiceActivity.this, SccIntentService.class));</pre>
  3. 執行結果

    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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章