欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解在Android中Service和AIDL的使用

 更新時間:2023年04月27日 09:12:09   作者:Heart-Beats  
Service是Android四大組件之一,它是不依賴于用戶界面的,就是因?yàn)镾ervice不依賴與用戶界面,本文將詳細(xì)介紹在Android中Service和AIDL的使用,感興趣的同學(xué)可以參考本文

Service 和 AIDL 的使用

[TOC]

1. Service

  • Service (服務(wù)) 是一個一種可以在后臺執(zhí)行長時間操作而沒有用戶界面的應(yīng)用組件。
  • 服務(wù)可由其他應(yīng)用組件啟動(如 Activity ),若沒進(jìn)行綁定,服務(wù)一旦啟動將在后臺一直運(yùn)行,即使啟動服務(wù)的組件( Activity )已銷毀也不受影響。
  • 組件也可以綁定到服務(wù),以與之進(jìn)行交互,甚至是執(zhí)行進(jìn)程間通信 ( IPC ),這時組件銷毀時服務(wù)也會停止。

該類中的常用方法如下:

public abstract class Service extends ContextWrapper implements ComponentCallbacks2, ContentCaptureManager.ContentCaptureClient {
        
    // 創(chuàng)建時回調(diào)
    public void onCreate() {
    }

    // startService 時回調(diào)
    public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }
    
    // bindService 時的回調(diào)
    @Nullable
    public abstract IBinder onBind(Intent intent);
    
    // unbindService 時的回調(diào)
    public boolean onUnbind(Intent intent) {
        return false;
    }
    
    // 當(dāng) onUnbind 返回 true 時,重新綁定時的回調(diào)
    public void onRebind(Intent intent) {
    }
    
    // 銷毀停止時的回調(diào)
    public void onDestroy() {
    }
    
    // 內(nèi)部的停止方法,若想停止并獲取結(jié)果使用 stopSelfResult(startId)
    public final void stopSelf() {
        stopSelf(-1);
    }
}

這里 onStartCommand() 的返回值很重要,共有如下四種不同的取值:

  • START_STICKY

    • 粘性的

    • onStartCommand() 使用這個返回值執(zhí)行后,如果 service 進(jìn)程被 kill 掉,保留 service 的狀態(tài)為開始狀態(tài),但不保留遞送的 intent 對象;系統(tǒng)隨后會嘗試重新創(chuàng)建 service,由于服務(wù)狀態(tài)為開始狀態(tài),所以創(chuàng)建服務(wù)后一定會調(diào)用 onStartCommand (Intent, int, int) 方法。如果在此期間沒有任何啟動命令被傳遞到 service , 那么參數(shù) Intent 將為 null

  • START_NOT_STICKY

    • 非粘性的

    • 使用這個返回值時 , 如果在執(zhí)行完 onStartCommand() 后 , 服務(wù)被異常 kill 掉 ,系統(tǒng)不會自動重啟該服務(wù)。

  • START_REDELIVER_INTENT

    • 重傳 Intent

    • 使用這個返回值時,如果在執(zhí)行完 onStartCommand() 后,服務(wù)被異常 kill 掉,系統(tǒng)會自動重啟該服務(wù) , 并將 Intent 的值傳入。

  • START_STICKY_COMPATIBILITY

    • START_STICKY 的兼容版本 , 但不保證服務(wù)被 kill 后一定能重啟。

1.1 Service 的基本生命周期

提到生命周期就不得不說到關(guān)于 Service 的兩種啟動方法:

  • startService
  • bindService

1.1.1 startService

此方式啟動的 Service 會一直無限運(yùn)行,只有調(diào)用了它的 stopService()stopSelf() 方法時,才會停止運(yùn)行并銷毀。

生命周期:

  • startService:

 若 service 沒被創(chuàng)建,調(diào)用 startService() 后會執(zhí)行 onCreate() ----> onStartCommand(),若 service 已創(chuàng)建,startService() 只會執(zhí)行 onStartCommand()。

  • stopService:

1.1.2 bindService

啟動的服務(wù)和調(diào)用者之間是典型的 client-server 模式,調(diào)用者是 client,Service 則是 server 端。

         Service 只有一個,但綁定到 Service 上面的 client 可以有多個。
client 可以通過 IBinder 接口獲取 Service 實(shí)例,實(shí)現(xiàn) client 端調(diào)用 Service 中的方法以實(shí)現(xiàn)交互。
         啟動的 Service 的生命周期與其綁定的 client 息息相關(guān)。當(dāng) client 銷毀時,會自動與 Service 解除綁定,當(dāng)然,也可以調(diào)用 Context 的 `unbindService()` 方法手動與 Service 解除綁定。當(dāng)沒有任何 client 與 Service 綁定時,Service 就會自行銷毀。

生命周期:

  • bindService:

 沒啥特殊的,就是 bindService 時 Service 若沒創(chuàng)建會創(chuàng)建 Service 示例,并在綁定成功后收到 onBind() 回調(diào)。

  • unbindService:
  • unbindService 將組件與 Service 解綁后會收到 onUnbind() 回調(diào),此時該 Service 若無任何組件與其綁定,則會自行銷毀。

1.2 Service 的啟動方式

通過上節(jié)介紹,可以了解到啟動 Service 有 startService()bindService() 兩種方式。

1.2.1 startService

該啟動方式,app 殺死、Activity 銷毀沒有任何影響,服務(wù)都不會銷毀停止運(yùn)行,所以此方式適合后臺一直運(yùn)行的任務(wù),但無法調(diào)用 Service 中的方法進(jìn)行交互。

比如:播放音樂、下載文件、進(jìn)程保活…

停止方式:主動 stopService()

1.2.2 bindService

該啟動方式依賴于客戶端生命周期,當(dāng)客戶端 Activity 銷毀時,即使沒有調(diào)用 unbindService() 方法,Service 也會銷毀停止運(yùn)行。所以此方式適合短時使用同時與 Service 產(chǎn)生交互的任務(wù)。

比如:通過 Service 跨進(jìn)程傳輸數(shù)據(jù)…

停止方式:Service 無任何綁定即會自動停止

1.2.3 startService + bindService

該啟動方式 Service 可以在后臺一直運(yùn)行,同時還可以與 Service 產(chǎn)生交互,調(diào)用它的方法。

比如:播放并控制音樂、下載文件并更新進(jìn)度…

停止方式:需要解除綁定并 stopService()

1.3 Service 和 Thread 的區(qū)別

既然提到 Service 可以執(zhí)行后臺任務(wù),那么也可以使用線程呀?那么看看有啥不同吧!

首先看看定義:

  • Thread

    線程為程序執(zhí)行的最小單元,Android 中的 UI 線程就是其中一種。

  • Service

    安卓中的四大組件之一,為一種特殊的機(jī)制,其實(shí)是一種輕量級的 IPC 通信。

其次,需要了解一下 Thread 的局限性:

Thread 的運(yùn)行是獨(dú)立于 Activity 的但依賴于進(jìn)程,也就是說當(dāng)應(yīng)用進(jìn)程被殺死或者線程體運(yùn)行完畢時就會停止運(yùn)行。

當(dāng) Activityfinish 后,是無法對其進(jìn)行管理的。同時 Thread 運(yùn)行過程中對其進(jìn)行控制操作也非常麻煩,而且也無法通過它實(shí)現(xiàn) IPC(跨進(jìn)程)通信。

那么,就可以了解到 Service 是可以做到這些的。

默認(rèn)情況下, Service 是運(yùn)行在當(dāng)前 app 進(jìn)程的 UI 主線程中,可以在 AndroidManifest 文件中配置 android:process 指定它所在的進(jìn)程。

因此,與 Activity 一樣,Service 是無法直接在其內(nèi)部執(zhí)行耗時任務(wù)的,需要開啟子線程去執(zhí)行,否則就會產(chǎn)生 ANR。

1.4 IntentService

在介紹 IntentService 之前先說明一下使用傳統(tǒng)的 Service 會有何問題:

  • 無法直接處理耗時任務(wù),需要內(nèi)部開啟子線程
  • startService() 啟動之后需要手動去停止

那么 IntentService 就是為了解決這些問題而生的,看下該類的主要內(nèi)容:

@Deprecated
public abstract class IntentService extends Service {

    // 該 Handler 在子線程中創(chuàng)建
    private final class ServiceHandler extends Handler {
        
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            // 處理完任務(wù)后自動停止
            stopSelf(msg.arg1);
        }
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);    
    }
    
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        
        // startService() 時將信息發(fā)送到 ServiceHandler 中
        mServiceHandler.sendMessage(msg);
    }
    
    /**
	 * 該方法的返回值為 Service 的標(biāo)志位:
	 *
	 *  @see Service.START_STICKY_COMPATIBILITY: START_STICKY 的兼容版本,但不保證服務(wù)被kill后一定能重啟
	 *
	 *  @see Service.START_STICKY: 粘性的,如果 service 進(jìn)程被 kill 掉,保留 service 的狀態(tài)為開始狀態(tài),但不保留傳送的 intent 對象。隨后系統(tǒng)會嘗試重新創(chuàng)建 service,創(chuàng)建后即會重新調(diào)用 onStartCommand(Intent,int,int) 方法。如果在此期間沒有任何啟動命令被傳遞到 service,那么參數(shù) Intent 將為null
	 *
	 *  @see Service.START_NOT_STICKY: 非粘性的,在執(zhí)行完 onStartCommand 后,服務(wù)被異常 kill 掉,系統(tǒng)不會自動重啟該服務(wù)
	 *
	 *  @see Service.START_REDELIVER_INTENT: 重傳 Intent,在執(zhí)行完 onStartCommand 后,服務(wù)被異常 kill 掉,系統(tǒng)會自動重啟該服務(wù),并將 Intent 的值傳入
	 *
	 */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    
    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }
    
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    // startService() -----> ServiceHandler 的 handleMessage() ------> onHandleIntent()
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
    
}

從上述代碼中就可以看出它的主要特征:

  • 會創(chuàng)建獨(dú)立的 worker 線程來處理所有的 Intent 請求,并最終執(zhí)行復(fù)寫的 onHandleIntent() 方法;
  • 請求任務(wù)處理完成后,IntentService 會自動停止,無需調(diào)用 stopSelf() 方法停止 Service ;

因此,對于那種需要后臺處理某些任務(wù),處理完成即退出是非常適合使用 IntentService 的,如:下載文件。

1.5 Android 8.0 對后臺服務(wù)的限制

上節(jié)中可以看到 IntentService 被標(biāo)記廢棄了,這是由于 Android 8.0(API 26) 以后系統(tǒng)不允許后臺應(yīng)用創(chuàng)建后臺服務(wù),創(chuàng)建后臺服務(wù)需要使用 JobScheduler 來由系統(tǒng)進(jìn)行調(diào)度任務(wù)的執(zhí)行。那么怎樣應(yīng)用會被認(rèn)定為后臺呢?

如果滿足以下任意條件,應(yīng)用將被視為處于前臺:

  • 具有可見 Activity (不管該 Activity 已啟動還是已暫停);
  • 具有前臺服務(wù);
  • 另一個前臺應(yīng)用已關(guān)聯(lián)到該應(yīng)用(不管是通過綁定到其中一個服務(wù),還是通過使用其中一個內(nèi)容提供程序);
  • IME;
  • 壁紙服務(wù);
  • 通知偵聽器;
  • 語音或文本服務(wù)。

如果以上條件均不滿足,應(yīng)用將被視為處于后臺。

因此,Android 8.0 引入了一種全新的方法,即 Context.startForegroundService() 以在前臺啟動新服務(wù)。系統(tǒng)創(chuàng)建服務(wù)后應(yīng)在五秒的時間內(nèi)調(diào)用該服務(wù)的 startForeground() 方法以顯示新服務(wù)的用戶可見通知。如果未在此時間限制內(nèi)未調(diào)用 startForeground() 方法,則系統(tǒng)將停止服務(wù)并聲明此應(yīng)用為 ANR

但在一些特殊情況下,還是可以創(chuàng)建后臺服務(wù)的:

  • 處理對用戶可見的任務(wù)時,后臺應(yīng)用將被置于一個臨時白名單中并持續(xù)數(shù)分鐘。位于白名單中時,應(yīng)用可以無限制地啟動服務(wù),并且其后臺服務(wù)也可以運(yùn)行。
    • 處理一條高優(yōu)先級 Firebase 云消息傳遞 (FCM) 消息;
    • 接收廣播,例如短信/彩信消息;
    • 從通知執(zhí)行 PendingIntent。
  • bindService() 方法不受后臺限制。

1.5.1 前臺服務(wù)

使用步驟:

  • 添加權(quán)限

    創(chuàng)建一個前臺服務(wù),首先需要請求前臺服務(wù)權(quán)限(Android 9 - API 級別 28 及以上 ),如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

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

    <application ...>
        ...
    </application>
</manifest>
  • 這是一個正常的權(quán)限,系統(tǒng)會自動將其授予請求的應(yīng)用程序。

  • 創(chuàng)建前臺服務(wù)

    創(chuàng)建一個前臺服務(wù)與創(chuàng)建一個正常服務(wù)沒太大區(qū)別,只是需要在 Service 創(chuàng)建之后調(diào)用 startForeground() 來啟動前臺服務(wù),如下:

class TestService : Service() {
    
   override fun onCreate() {
      super.onCreate()
       
        // Android 8.0 調(diào)用 startForefround() 方法啟動服務(wù)
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            setForegroundService();
        }
   }
    
   private fun  setForegroundService() {
       
       // 創(chuàng)建通知渠道
        val CHANNEL_ID = 1
        val channelName = getString(R.string.channel_name)
       // 設(shè)置通知的優(yōu)先級
        val importance = NotificationManager.IMPORTANCE_LOW
        val channel = NotificationChannel(CHANNEL_ID, channelName, importance)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)

       // 設(shè)置前臺服務(wù)通知的點(diǎn)擊事件
       val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }
       
       // 創(chuàng)建通知
        val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle(getText(R.string.notification_title))
                .setContentText(getText(R.string.notification_message))
                .setSmallIcon(R.drawable.icon)
                .setContentIntent(pendingIntent)
                .setTicker(getText(R.string.ticker_text))
                .build()

        // Notification ID cannot be 0.
        startForeground(NOTIFICATION_ID, notification)
   }

   override fun onBind(intent: Intent): IBinder? {
		return null
   }
}
  • 在這里有兩個點(diǎn)需要注意一下:

    Android 8.0 以上創(chuàng)建通知需要先創(chuàng)建通知渠道,再使用渠道 id 創(chuàng)建通知

    前臺服務(wù)的通知優(yōu)先級必須為 PRIORITY_LOW 或更高,通知優(yōu)先級詳細(xì)見設(shè)置渠道的重要性級別

  • 注冊服務(wù)

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

    <application ...>

        <service
            android:name="com.hl.myplugin.TestService"
            android:enabled="true"
            android:exported="false" />
    </application>

</manifest>

聲明前臺服務(wù)類型

如果在 Android 10(API 級別 29)或更高版本訪問前臺服務(wù)中的位置信息,則需要聲明 <service> 組件的前臺服務(wù)類型為 location;

如果在 Android 11(API 級別 30)或更高版本訪問前臺服務(wù)中的攝像頭或麥克風(fēng),則需要聲明 <service> 組件的前臺服務(wù)類型為 camera 或 microphone。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

    <application ...>

        <service ...
            android:foregroundServiceType="location|camera|microphone"/>
    </application>

</manifest>

在運(yùn)行時,如果前臺服務(wù)只需要訪問清單中聲明的類型的子集,則可以使用以下代碼片段中的邏輯來限制服務(wù)的訪問:

val notification: Notification = ...
// 開啟前臺服務(wù)的重載方法
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_LOCATION or FOREGROUND_SERVICE_TYPE_CAMERA)

但 Android 11(API 級別 30)為了幫助保護(hù)用戶隱私,對前臺服務(wù)何時可以訪問設(shè)備的位置、攝像頭或麥克風(fēng)進(jìn)行了限制。當(dāng)應(yīng)用程序在后臺運(yùn)行啟動前臺服務(wù)時,前臺服務(wù)有以下限制:

  • 除非用戶已授予應(yīng)用程序 ACCESS_BACKGROUND_LOCATION 權(quán)限,否則 前臺服務(wù)無法訪問位置。

  • 前臺服務(wù)無法訪問麥克風(fēng)或攝像頭。

如果啟動的服務(wù)對位置、麥克風(fēng)和攝像頭的訪問受到限制,那么在調(diào)試時 Logcat 中會顯示以下消息:

Foreground service started from background can not have location/camera/microphone access: service SERVICE_NAME
  • 限制豁免:

    在某些情況下,即使應(yīng)用程序在后臺運(yùn)行時啟動了前臺服務(wù) ,它仍然可以在應(yīng)用程序在前臺運(yùn)行時(“使用中”)訪問位置、相機(jī)和麥克風(fēng)信息。在這些情況下,如果服務(wù)聲明了一個 前臺服務(wù)類型location,并且服務(wù)由一個具有ACCESS_BACKGROUND_LOCATION 權(quán)限的應(yīng)用程序啟動,那么該服務(wù)即使在后臺啟動但在前臺運(yùn)行時仍可訪問位置信息。

    以下包含這些情況:

    • 該服務(wù)由系統(tǒng)組件啟動。

    • 該服務(wù)通過與應(yīng)用小部件交互啟動。

    • 該服務(wù)通過與通知交互來啟動。

    • 該服務(wù)作為PendingIntent從不同的可見應(yīng)用程序發(fā)送的啟動 。

    • 該服務(wù)由在設(shè)備所有者模式下運(yùn)行的設(shè)備策略控制器應(yīng)用程序啟動。

    • 該服務(wù)由提供VoiceInteractionService.

    • 該服務(wù)由具有START_ACTIVITIES_FROM_BACKGROUND特權(quán)權(quán)限的應(yīng)用程序啟動 。

  • 啟動前臺服務(wù)

// Android 8.0使用 startForegroundService 在前臺啟動服務(wù)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
   startForegroundService(Intent(this,TestService::class.java)
}else{
  startService(Intent(this,TestService::class.java))
}
  • 移除前臺服務(wù)

    調(diào)用 stopForeground(removeNotification: Boolean) 方法即可移除前臺服務(wù),參數(shù)代表是否刪除狀態(tài)欄通知。

    注意: stopForeground() 并不會使服務(wù)停止運(yùn)行,若想停止服務(wù),仍需調(diào)用 stopService()

1.5.2 JobIntentService

由于前臺服務(wù)在通知欄上會顯示該 Service 正在運(yùn)行,這可能會帶來不好的用戶體驗(yàn)。如果還是希望使用服務(wù)在后臺默默工作、通過使用服務(wù)開啟子進(jìn)程等等,那么就可以使用 JobIntentService。

JobIntentService 用于處理被加入到隊(duì)列的 job/service 任務(wù)。當(dāng)運(yùn)行在 Android O 或更高版本時,任務(wù)將作為 job 通過 JobScheduler.enqueue() 進(jìn)行分發(fā);當(dāng)運(yùn)行在較老版本的平臺上時,任務(wù)仍舊會使用 Context.startService() 執(zhí)行。

使用步驟:

  • 在 Manifest 中聲明 Permission

    JobIntentService 處理了亮屏/鎖屏,因此需要在 AndroidManifest.xml 中添加 android.Manifest.permission.WAKE_LOCK 權(quán)限,如下:

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

在 Manifest 中聲明 Service

JobIntentService 本質(zhì)上也是一個 Service,因此需要在 AndroidManifest.xml 聲明,以便系統(tǒng)與之交互,如下:

<service android:name="SimpleJobIntentService" 
         android:permission="android.permission.BIND_JOB_SERVICE" > 
    ... 
</service>

創(chuàng)建 JobIntentService 的實(shí)現(xiàn)類

與 IntentService 的 onHandleIntent() 方法相似,只需重寫 onHandleWork() 方法處理相應(yīng)的邏輯即可:

class SimpleJobIntentService : JobIntentService() {

	companion object {
		private const val JOB_ID = 1

		fun enqueueWork(context: Context, work: Intent) {
			enqueueWork(context, SimpleJobIntentService::class.java, JOB_ID, work)
		}
	}

	override fun onHandleWork(intent: Intent) {
        // 具體邏輯
	}
}

啟動服務(wù)

SimpleJobIntentService.enqueueWork(context, new Intent());

可以看出,它使用起來非常簡單,因?yàn)橐呀?jīng)封裝了大量的內(nèi)部邏輯,只需要調(diào)用 enqueueWork() 靜態(tài)方法就可以了。

2. Service 的保活

Android 系統(tǒng)會盡可能長的延續(xù)一個應(yīng)用程序進(jìn)程,但在內(nèi)存過低的時候,仍然會不可避免需要移除舊的進(jìn)程。為了決定哪些進(jìn)程留下,哪些進(jìn)程被殺死,系統(tǒng)根據(jù)在進(jìn)程中在運(yùn)行的組件及組件的狀態(tài),為每一個進(jìn)程分配了一個優(yōu)先級等級。優(yōu)先級最低的進(jìn)程首先被殺死。這個進(jìn)程重要性的層次結(jié)構(gòu)主要有五個等級。

2.1 進(jìn)程的五個常用等級:

主要分為:

  • 前臺進(jìn)程(Foreground process)

  • 可見進(jìn)程(Visible process)

  • 服務(wù)進(jìn)程 (Service process)

  • 后臺進(jìn)程 (Background process)

  • 空進(jìn)程

了解這些以后,你就能明白為啥不建議在 Activity 中開線程處理耗時任務(wù)?

主要原因如下:

  • Activity 中開線程做耗時操作,切到桌面會變成后臺進(jìn)程
  • 啟動 Service 新建線程處理耗時任務(wù),這時會變?yōu)榉?wù)進(jìn)程

因?yàn)榉?wù)進(jìn)程的優(yōu)先級比后臺進(jìn)程的優(yōu)先級高,所以此方式處理耗時任務(wù)更好。 同時,使用 Service 將會保證 app 至少有服務(wù)進(jìn)程的優(yōu)先級。

2.1.1 前臺進(jìn)程

前臺進(jìn)程是用戶當(dāng)前做的事所必須的進(jìn)程,如果滿足下面各種情況中的一種,一個進(jìn)程被認(rèn)為是在前臺:

  • 進(jìn)程持有一個正在與用戶交互的 Activity。

  • 進(jìn)程持有一個 Service,這個 Service 處于這幾種狀態(tài):

    • Service 與用戶正在交互的 Activity 綁定。

    • Service 是在前臺運(yùn)行的,即它調(diào)用了 startForeground()

    • Service 正在執(zhí)行它的生命周期回調(diào)函數(shù) —— onCreate()、 onStart()onDestroy()

  • 進(jìn)程持有一個 BroadcastReceiver,這個 BroadcastReceiver 正在執(zhí)行它的 onReceive() 方法。

殺死前臺進(jìn)程需要用戶交互,因?yàn)榍芭_進(jìn)程的優(yōu)先級是最高的。

2.1.2 可見進(jìn)程

如果一個進(jìn)程不含有任何前臺的組件,但仍可被用戶在屏幕上所見。當(dāng)滿足如下任一條件時,進(jìn)程被認(rèn)為是可見的:

  • 進(jìn)程持有一個 Activity,這個 Activity 不在前臺,但是仍然被用戶可見,即處于 onPause() 狀態(tài)。

  • 進(jìn)程持有一個 Service,這個 Service 和一個可見的 Activity 綁定。

可見的進(jìn)程也被認(rèn)為是很重要的,一般不會被銷毀,除非是為了保證所有前臺進(jìn)程的運(yùn)行而不得不殺死可見進(jìn)程的時候。

2.1.3 服務(wù)進(jìn)程

如果一個進(jìn)程中運(yùn)行著一個 Service,這個 Service 是通過 startService() 開啟的,并且不屬于上面兩種較高優(yōu)先級的情況(未進(jìn)行任何綁定),這個進(jìn)程就是一個服務(wù)進(jìn)程。

盡管服務(wù)進(jìn)程沒有和用戶可以看到的東西綁定,但是它們一般在做的事情是用戶關(guān)心的,比如后臺播放音樂,后臺下載數(shù)據(jù)等。所以系統(tǒng)會盡量維持它們的運(yùn)行,除非系統(tǒng)內(nèi)存不足以維持前臺進(jìn)程和可見進(jìn)程的運(yùn)行需要。

2.1.4 后臺進(jìn)程

如果進(jìn)程不屬于上面三種情況,但是它持有一個用戶不可見的activity(Activity的 onStop() 被調(diào)用,但是 onDestroy() 沒有調(diào)用的狀態(tài)),就認(rèn)為進(jìn)程是一個后臺進(jìn)程。

后臺進(jìn)程不直接影響用戶體驗(yàn),系統(tǒng)會為了前臺進(jìn)程、可見進(jìn)程、服務(wù)進(jìn)程而任意殺死后臺進(jìn)程。

通常會有很多個后臺進(jìn)程存在,它們會被保存在一個 LRU (least recently used) 列表中,這樣就可以確保用戶最近使用的 Activity 最后被銷毀,即最先銷毀時間最遠(yuǎn)的 Activity。

2.1.5 空進(jìn)程

如果一個進(jìn)程不包含任何活躍的應(yīng)用組件,則認(rèn)為是空進(jìn)程。

例如:一個進(jìn)程當(dāng)中已經(jīng)沒有數(shù)據(jù)在運(yùn)行,但是內(nèi)存當(dāng)中還為這個應(yīng)用駐留了一個進(jìn)程空間。

保存這種進(jìn)程的唯一理由是為了緩存的需要,為了加快下次要啟動這個進(jìn)程中的組件時的啟動時間。系統(tǒng)為了平衡進(jìn)程緩存和底層內(nèi)核緩存的資源,經(jīng)常會殺死空進(jìn)程。

2.2 Service ?;畹某S眉记?/h3>
  • 設(shè)置最高優(yōu)先級
<service  
     android:name="com.dbjtech.acbxt.waiqin.UploadService"  
     android:enabled="true" >  
     <intent-filter android:priority="1000" >  
         <action android:name="xxxx" />  
     </intent-filter>  
</service>
  • 如上,Service 對于 intent-filter 可以通過 android:priority = “1000” 這個屬性設(shè)置最高優(yōu)先級,1000是最高值,如果數(shù)字越小則優(yōu)先級越低,同時適用于廣播。

  • 使用前臺服務(wù)

    Service 創(chuàng)建時通過 startForeground() 方法把 Service 提升為前臺進(jìn)程級別,在 onDestroy() 里面要記得調(diào)用 stopForeground() 方法。

  • 復(fù)寫onStartCommand() 方法,返回 START_STICKY

    當(dāng) Service 因內(nèi)存不足被 kill,當(dāng)內(nèi)存又有的時候,Service 就會被重新創(chuàng)建啟動。

    注意:但是不能保證任何情況下都被重建,比如進(jìn)程被干掉了….

  • onDestroy() 方法里發(fā)廣播重啟 Service

    Service + Broadcast 方式,就是當(dāng) Service 走 onDestory() 的時候,發(fā)送一個自定義的廣播,當(dāng)收到廣播的時候,重新啟動 Service。

    注意:第三方應(yīng)用或是在 setting 里-應(yīng)用-強(qiáng)制停止時,APP 進(jìn)程就直接被干掉了,onDestroy() 方法都進(jìn)不來,所以無法保證會執(zhí)行

  • 監(jiān)聽系統(tǒng)廣播判斷 Service 狀態(tài)

    通過系統(tǒng)的一些廣播,比如:手機(jī)重啟、界面喚醒、應(yīng)用狀態(tài)改變等等監(jiān)聽并捕獲,然后判斷我們的 Service 是否還存活決定是否重新啟動。

  • Application 加上 Persistent 屬性

    該屬性相當(dāng)于將該進(jìn)程設(shè)置為常駐內(nèi)存進(jìn)程,即系統(tǒng)應(yīng)用。一般為安裝在/system/app下的 app,正常的三方應(yīng)用安裝在 /data/app 下。

3. AIDL 的使用

Android 接口定義語言 (AIDL),利用它定義客戶端與服務(wù)均認(rèn)可的編程接口,以便二者使用進(jìn)程間通信 (IPC) 進(jìn)行相互通信。

跨進(jìn)程通信 (IPC) 的方式很多,AIDL 是其中一種。還有 Binder、文件共享、Messenger、ContentProviderSocket 等進(jìn)程間通信的方式。AIDL 是接口定義語言,只是一個工具。具體通信還是得用Binder 來進(jìn)行。Binder 是 Android 獨(dú)有的跨進(jìn)程通信方式,只需要一次拷貝,更快速和安全。

官方推薦用 Messenger 來進(jìn)行跨進(jìn)程通信,但是 Messenger 是以串行的方式來處理客戶端發(fā)來的消息,如果大量的消息同時發(fā)送到服務(wù)端,服務(wù)端仍然只能一個個處理。因此對于大量的并發(fā)請求,這種情況就得用 AIDL 。其實(shí) Messenger 的底層也是 AIDL,只不過系統(tǒng)做了層封裝,簡化使用。

3.1 Messenger (串行處理)

3.1.1 服務(wù)端

  • 創(chuàng)建一個 Handler 對象,并實(shí)現(xiàn) hanlemessage 方法,用于接收來自客戶端的消息,并作處理
  • 創(chuàng)建一個 Messenger,封裝 Handler
  • messenger.getBinder() 方法獲取一個 IBinder 對象,通過 onBind 返回給客戶端

使用示例如下:

public class MessengerService extends Service {
    
    // 存儲客戶端發(fā)送的 Messenger 對象
    ArrayList<Messenger> mClients = new ArrayList<Messenger>();
    
    int mValue = 0;
    
    /**
     *  客戶端請求注冊 Messenger 
     */
    static final int MSG_REGISTER_CLIENT = 1;
    
    /**
     * 客戶端請求反注冊 Messenger 
     */
    static final int MSG_UNREGISTER_CLIENT = 2;
    

    /**
     * 客戶端請求設(shè)值,相當(dāng)于請求其他命令
     */
    static final int MSG_SET_VALUE = 3;
    

    class IncomingHandler extends Handler {
        
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    mClients.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    break;
                case MSG_SET_VALUE:
                    mValue = msg.arg1;
                    for (int i=mClients.size()-1; i>=0; i--) {
                        try {
                            // 取得客戶端傳送的 Messenger,發(fā)送消息回 Messenger 實(shí)現(xiàn)雙向通信
                            mClients.get(i).send(Message.obtain(null, MSG_SET_VALUE, mValue, 0));
                        } catch (RemoteException e) {
							// 客戶端有可能在此過程中死了產(chǎn)生異常,需要移除
                            mClients.remove(i);
                        }
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    
    final Messenger mMessenger = new Messenger(new IncomingHandler());
    
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

注意:該Service 在聲明時必須對外開放,即 android:exported="true"

3.1.2 客戶端

  • 在 Activity 中綁定服務(wù)
  • 創(chuàng)建 ServiceConnection ,在其 onServiceConnected() 方法中通過參數(shù) IBinder 將 Messenger 實(shí)例化
  • 使用 Messenger 向服務(wù)端發(fā)送命令,或需要接收服務(wù)器端的返回信息,則還要創(chuàng)建一個 Messenger(handler),并將這個 Messenger 傳遞給服務(wù)端,在handler 中接收處理服務(wù)端的消息,這就實(shí)現(xiàn)了客戶端和服務(wù)端的雙向通信

使用示例如下:

public class MessengerServiceActivities extends Activity{

    // 向服務(wù)端發(fā)送命令的 Messenger
    private Messenger mService = null;
    
    private boolean mIsBound;
    
    private TextView mCallbackText;
    
    private class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MessengerService.MSG_SET_VALUE:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    
    // 接收服務(wù)端返回消息的 Messenger
    private final Messenger mMessenger = new Messenger(new IncomingHandler());

    private ServiceConnection mConnection = new ServiceConnection() {
        
        public void onServiceConnected(ComponentName className, IBinder service) {
			// 連接時獲取與服務(wù)端交互的 Messenger
            mService = new Messenger(service);
            mCallbackText.setText("Attached.");

            try {
                // 將需要接收服務(wù)端返回消息的 Messenger 發(fā)送在消息體中
                Message msg = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT);
                msg.replyTo = mMessenger;
                mService.send(msg);
                
                // 向服務(wù)端發(fā)送設(shè)值命令
                msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
                mService.send(msg);
            } catch (RemoteException e) {
                // xxx
            }

            Toast.makeText(this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show();
        }
        
        public void onServiceDisconnected(ComponentName className) {
            mService = null;
            mCallbackText.setText("Disconnected.");
            Toast.makeText(this, R.string.remote_service_disconnected,Toast.LENGTH_SHORT).show();
        }
    };
    
    void doBindService() {
        bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
        mCallbackText.setText("Binding.");
    }
    
    void doUnbindService() {
        if (mIsBound) {
            if (mService != null) {
                try {
                    // 解綁時移除服務(wù)端中添加的 Messenger,取消消息接收
                    Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT);
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                } catch (RemoteException e) {
                    //xxx
                }
            }
            
            unbindService(mConnection);
            mIsBound = false;
            mCallbackText.setText("Unbinding.");
        }
    }
}

3.2 AIDL(并行處理)

步驟:

  • 創(chuàng)建 .aidl 文件:

    定義 AIDL 接口

  • 實(shí)現(xiàn)接口:

    Android SDK 工具會基于 .aidl 文件,使用 Java 編程語言生成繼承自 IInterface 接口的接口。生成的接口擁有一個繼承自 Binder 類名為 Stub 的內(nèi)部抽象類,并聲明 AIDL 接口中的抽象方法。大概結(jié)構(gòu)如下:

public interface IInterface{
    public IBinder asBinder();
}

public interface xxxInterface extends android.os.IInterface{

    public static abstract class Stub extends android.os.Binder implements xxxInterface{
        
        @Override 
        public android.os.IBinder asBinder() {
          	return this;
        }
        
        xxxx
    }
    
    // AIDL 中聲明的抽象方法
    xxxx
}
  • 向客戶端公開接口:

    實(shí)現(xiàn) Service 并重寫 onBind(),從而返回 Stub 類的實(shí)現(xiàn).

3.2.1 定義 AIDL 接口

src/main 下面創(chuàng)建 aidl 目錄,然后新建 IPersonManager.aidl 文件,里面聲明方法用于客戶端調(diào)用,服務(wù)端實(shí)現(xiàn)。如下:

package com.xfhy.allinone.ipc.aidl;
import com.xfhy.allinone.ipc.aidl.Person;
interface IPersonManager {
    List<Person> getPersonList();
    //in: 從客戶端流向服務(wù)端
    boolean addPerson(in Person person);
}

這個接口和平常我們定義接口時差別不是很大,需要注意的是即使 Person 和 PersonManager 在同一個包下面還是得導(dǎo)包,這是AIDL的規(guī)則。

  • AIDL 支持的數(shù)據(jù)類型

    在 AIDL 文件中,不是所有數(shù)據(jù)類型都是可以使用的,支持的數(shù)據(jù)類型如下:

    • Java 編程語言中的所有原語類型(如 int、long、char、boolean 等)

    • String 和 CharSequence

    • List:只支持 ArrayList,里面每個元素都必須能夠被 AIDL 支持

    • Map:只支持HashMap,里面的每個元素都必須被 AIDL 支持,包括 key 和 value

    • Parcelable:所有實(shí)現(xiàn)了Parcelable接口的對象

    • AIDL:所有的AIDL接口本身也可以在 AIDL 文件中使用

  • 定義傳輸?shù)膶ο?/strong>

    在 kotlin 或 Java 這邊需要定義好這個需要傳輸?shù)膶ο?Person,,或者定義在 aidl 目錄下, 但需要通過 sourceSet{} 將此目錄定義為 kotlin 或 java 源碼目錄,這里以在 kotlin 下為示例:

class Person(var name: String? = "") : Parcelable {
    constructor(parcel: Parcel) : this(parcel.readString())

    override fun toString(): String {
        return "Person(name=$name) hashcode = ${hashCode()}"
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
    }

    fun readFromParcel(parcel: Parcel) {
        this.name = parcel.readString()
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            return Person(parcel)
        }

        override fun newArray(size: Int): Array<Person?> {
            return arrayOfNulls(size)
        }
    }

然后得在 aidl 的相同目錄下也需要聲明一下這個 Person 對象,新建一個 Person.aidl:

package com.xfhy.allinone.ipc.aidl;

parcelable Person;

注意:當(dāng)需要傳遞對象時,則該對象必須實(shí)現(xiàn) Parcelable 接口并且需要指示數(shù)據(jù)走向的方向標(biāo)記

方向標(biāo)記意義
in數(shù)據(jù)只能由客戶端流向服務(wù)端,服務(wù)端修改數(shù)據(jù)不會同步返回
out數(shù)據(jù)只能由服務(wù)端流向客戶端,客戶端會新創(chuàng)建一個無參對象傳遞到服務(wù)端,服務(wù)端修改數(shù)據(jù)會同步返回
inout數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通,服務(wù)端和客戶端同步共用一個對象

 

  • 原語類型(基本類型)默認(rèn)是 in,inout 開銷很大,因此慎用。調(diào)用 AIDL 生成接口的為客戶端,實(shí)現(xiàn)接口方為服務(wù)端。

    都完成了之后,rebuild 一下,AS 會自動生成IPersonManager.java 接口文件。

3.2.2 服務(wù)端實(shí)現(xiàn)接口

定義一個 Service, 然后將其 process 設(shè)置成一個新的進(jìn)程,與主進(jìn)程區(qū)分開,模擬跨進(jìn)程訪問,它里面需要實(shí)現(xiàn) .aidl 生成的接口,如下:

class RemoteService : Service() {

    private val mPersonList = mutableListOf<Person?>()

    private val mBinder: Binder = object : IPersonManager.Stub() {
        
        override fun getPersonList(): MutableList<Person?> = mPersonList

        override fun addPerson(person: Person?): Boolean {
            return mPersonList.add(person)
        }
    }

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

    override fun onCreate() {
        super.onCreate()
        mPersonList.add(Person("Garen"))
        mPersonList.add(Person("Darius"))
    }
}

實(shí)現(xiàn)的 IPersonManager.Stub 是一個 Binder,需要通過 onBind() 返回,客戶端需要通過這個 Binder 來跨進(jìn)程調(diào)用 Service 這邊的服務(wù)。

3.2.3 客戶端與服務(wù)端進(jìn)行通信

客戶端這邊需要通過 bindService() 來連接此 Service,進(jìn)而實(shí)現(xiàn)通信??蛻舳说?onServiceConnected() 回調(diào)會接收 Service 的 onBind() 方法所返回的 binder 實(shí)例。再調(diào)用 XxxInterface.Stub.asInterface(service) 就能轉(zhuǎn)換取得 XxxInterface 實(shí)例。如下:

class AidlActivity : TitleBarActivity() {

    companion object {
        const val TAG = "xfhy_aidl"
    }

    private var remoteServer: IPersonManager? = null

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            log(TAG, "onServiceConnected")
            //在onServiceConnected調(diào)用IPersonManager.Stub.asInterface 獲取接口類型的實(shí)例
            //通過這個實(shí)例調(diào)用服務(wù)端的服務(wù)
            remoteServer = IPersonManager.Stub.asInterface(service)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            log(TAG, "onServiceDisconnected")
        }
    }

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

        btnConnect.setOnClickListener {
            connectService()
        }
        btnGetPerson.setOnClickListener {
            getPerson()
        }
        btnAddPerson.setOnClickListener {
            addPerson()
        }
    }

    private fun connectService() {
        val intent = Intent()
        //action 和 package(app的包名)
        intent.action = "com.xfhy.aidl.Server.Action"
        intent.setPackage("com.xfhy.allinone")
        val bindServiceResult = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        log(TAG, "bindService $bindServiceResult")

        //如果targetSdk是30,那么需要處理Android 11中的程序包可見性  具體參見:        https://developer.android.com/about/versions/11/privacy/package-visibility
    }

    private fun addPerson() {
        //客戶端調(diào)服務(wù)端方法時,需要捕獲以下幾個異常:
        //RemoteException 異常:
        //DeadObjectException 異常:連接中斷時會拋出異常;
        //SecurityException 異常:客戶端和服務(wù)端中定義的 AIDL 發(fā)生沖突時會拋出異常;
        try {
            val addPersonResult = remoteServer?.addPerson(Person("蓋倫"))
            log(TAG, "addPerson result = $addPersonResult")
        } catch (e: RemoteException) {
            e.printStackTrace()
        } catch (e: DeadObjectException) {
            e.printStackTrace()
        } catch (e: SecurityException) {
            e.printStackTrace()
        }
    }

    private fun getPerson() {
        val personList = remoteServer?.personList
        log(TAG, "person 列表 $personList")
    }

    override fun onDestroy() {
        super.onDestroy()
        //最后記得unbindService
        unbindService(serviceConnection)
    }
}

測試時先 getPerson 再 addPerson 最后 getPerson,輸出日志:

2020-12-24 12:41:00.170 24785-24785/com.xfhy.allinone D/xfhy_aidl: bindService true
2020-12-24 12:41:00.906 24785-24785/com.xfhy.allinone D/xfhy_aidl: onServiceConnected
2020-12-24 12:41:04.253 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius)]
2020-12-24 12:41:05.952 24785-24785/com.xfhy.allinone D/xfhy_aidl: addPerson result = true
2020-12-24 12:41:09.022 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius), Person(name=蓋倫)]

注意:在客戶端調(diào)用這些遠(yuǎn)程方法時是同步調(diào)用,在主線程調(diào)用可能會導(dǎo)致 ANR,應(yīng)該在子線程去調(diào)用。

3.2.4 oneway 關(guān)鍵字(異步)

將 aidl 接口的方法前加上 oneway 關(guān)鍵字則這個方法就是異步調(diào)用,不會阻塞調(diào)用線程。當(dāng)客戶端調(diào)用服務(wù)端的方法不需要知道返回結(jié)果時,使用異步調(diào)用可以提高客戶端的執(zhí)行效率。

3.2.5 線程安全

AIDL 的方法是在服務(wù)端的 Binder 線程池中執(zhí)行的,所以多個客戶端同時進(jìn)行連接且操作數(shù)據(jù)時可能存在多個線程同時訪問的情形。這時就需要在服務(wù)端 AIDL 方法中處理多線程同步問題。

先看下服務(wù)端的 AIDL 方法是在哪個線程中:

override fun addPerson(person: Person?): Boolean {
    log(TAG, "服務(wù)端 addPerson() 當(dāng)前線程 : ${Thread.currentThread().name}")
    return mPersonList.add(person)
}

//日志輸出
服務(wù)端 addPerson() 當(dāng)前線程 : Binder:3961_3

可以看到,確實(shí)是在非主線程中執(zhí)行的,那確實(shí)會存在多線程安全問題。這就需要將 mPersonList 的類型修改為 CopyOnWriteArrayList,以確保線程安全:

//服務(wù)端
private val mPersonList = CopyOnWriteArrayList<Person?>()

override fun getPersonList(): MutableList<Person?> = mPersonList

//客戶端
private fun getPerson() {
    val personList = remoteServer?.personList
    personList?.let {
        log(TAG, "personList ${it::class.java}")
    }
}

//輸出日志
personList class java.util.ArrayList

另外還有 ConcurrentHashMap 也是同樣的道理,這里就不驗(yàn)證了。

3.2.6 AIDL 監(jiān)聽器(觀察者? 雙向通信?)

上面的案例中,只能在客戶端每次去調(diào)服務(wù)端的方法然后獲得結(jié)果。若想服務(wù)端數(shù)據(jù)有變動就通知一下客戶端,這就需要添加監(jiān)聽器了。

因?yàn)檫@個監(jiān)聽器 Listener 是需要跨進(jìn)程的,這里首先就需要為這個 Listener 創(chuàng)建一個 aidl 的回調(diào)接口IPersonChangeListener.aidl

interface IPersonChangeListener {
 	 // 這里由服務(wù)端調(diào)用此接口,因此服務(wù)端其實(shí)充當(dāng) "Client",數(shù)據(jù)流通方向標(biāo)記為 in 更合理
	  void onPersonDataChanged(in Person person);
}

有了監(jiān)聽器,還需要在 IPersonManager.aidl 中加上注冊/反注冊監(jiān)聽的方法:

interface IPersonManager {
    ......
    void registerListener(IPersonChangeListener listener);
    void unregisterListener(IPersonChangeListener listener);
}

現(xiàn)在我們在服務(wù)端實(shí)現(xiàn)這個注冊/反注冊的方法,這還不簡單嗎? 搞一個 List<IPersonChangeListener> 來存放 Listener 集合,當(dāng)數(shù)據(jù)變化的時候遍歷這個集合,通知一下這些Listener就行。

仔細(xì)想想這樣真的行嗎? 這個 IPersonChangeListener 是需要跨進(jìn)程的,那么客戶端每次傳過來的對象是經(jīng)過序列化與反序列化的,服務(wù)端這邊接收到的根本不是客戶端傳過來的那個對象。 雖然傳過來的 Listener 不同,但是用來通信的 Binder 是同一個,利用這個原理 Android 提供了一個 RemoteCallbackList 的東西,專門用于存放監(jiān)聽接口的集合的。RemoteCallbackList 內(nèi)部將數(shù)據(jù)存儲于一個 ArrayMap 中,key 就是用來傳輸?shù)?binder,value 就是監(jiān)聽接口的封裝。如下:

//RemoteCallbackList.java  源碼有刪減
public class RemoteCallbackList<E extends IInterface> {
    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;

        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }
    }

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            IBinder binder = callback.asBinder();
            Callback cb = new Callback(callback, cookie);
            mCallbacks.put(binder, cb);
            return true;
        }
    }
}

RemoteCallbackList 內(nèi)部在操作數(shù)據(jù)的時候已經(jīng)做了線程同步的操作,所以不需要單獨(dú)做額外的線程同步操作?,F(xiàn)在來實(shí)現(xiàn)一下這個注冊/反注冊方法:

private val mListenerList = RemoteCallbackList<IPersonChangeListener?>()

private val mBinder: Binder = object : IPersonManager.Stub() {
    .....
    override fun registerListener(listener: IPersonChangeListener?) {
        mListenerList.register(listener)
    }

    override fun unregisterListener(listener: IPersonChangeListener?) {
        mListenerList.unregister(listener)
    }
}

RemoteCallbackList 添加與刪除數(shù)據(jù)對應(yīng)著 register()/unregister()方法,然后我們模擬一下服務(wù)端數(shù)據(jù)更新的情況,開個線程每隔 5 秒添加一個 Person 數(shù)據(jù),然后通知一下觀察者:

//死循環(huán) 每隔5秒添加一次person,通知觀察者
private val serviceWorker = Runnable {
    while (!Thread.currentThread().isInterrupted) {
        Thread.sleep(5000)
        val person = Person("name${Random().nextInt(10000)}")
        log(AidlActivity.TAG, "服務(wù)端 onDataChange() 生產(chǎn)的 person = $person}")
        mPersonList.add(person)
        onDataChange(person)
    }
}
private val mServiceListenerThread = Thread(serviceWorker)

//數(shù)據(jù)變化->通知觀察者
private fun onDataChange(person: Person?) {
    //1. 使用RemoteCallbackList時,必須首先調(diào)用beginBroadcast(), 最后調(diào)用finishBroadcast(). 得成對出現(xiàn)
    //這里拿到的是監(jiān)聽器的數(shù)量
    val callbackCount = mListenerList.beginBroadcast()
    for (i in 0 until callbackCount) {
        try {
            //這里try一下避免有異常時無法調(diào)用finishBroadcast()
            mListenerList.getBroadcastItem(i)?.onPersonDataChanged(person)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }
    
    //3. 最后調(diào)用finishBroadcast()  必不可少
    mListenerList.finishBroadcast()
}

override fun onCreate() {
    .....
    mServiceListenerThread.start()
}

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

服務(wù)端實(shí)現(xiàn)好了,客戶端就比較好辦:

private val mPersonChangeListener = object : IPersonChangeListener.Stub() {
    override fun onPersonDataChanged(person: Person?) {
        log(TAG, "客戶端 onPersonDataChanged() person = $person}")
    }
}

private fun registerListener() {
    remoteServer?.registerListener(mPersonChangeListener)
}

private fun unregisterListener() {
    remoteServer?.asBinder()?.isBinderAlive?.let {
        remoteServer?.unregisterListener(mPersonChangeListener)
    }
}

因?yàn)槭切枰邕M(jìn)程通信的,所以需要繼承自 IPersonChangeListener.Stub 從而生成一個監(jiān)聽器對象。最后輸出日志如下:

服務(wù)端 onDataChange() 生產(chǎn)的 person = Person(name=name9398) hashcode = 130037351}
客戶端 onPersonDataChanged() person = Person(name=name9398) hashcode = 217703225}

3.2.7 Binder 死亡通知

服務(wù)端進(jìn)程可能隨時會被殺掉,這時需要在客戶端能夠被感知到 binder 已經(jīng)死亡,從而做一些收尾清理工作或者進(jìn)程重新連接。有如下 4 種方式能知道服務(wù)端是否已經(jīng)掛掉:

  • 調(diào)用 binder 的 pingBinder() 檢查,返回 false 則說明遠(yuǎn)程服務(wù)失效
  • 調(diào)用 binder 的 linkToDeath() 注冊監(jiān)聽器,當(dāng)遠(yuǎn)程服務(wù)失效時,就會收到回調(diào)
  • 綁定 Service 時用到的 ServiceConnection 有個 onServiceDisconnected() 回調(diào)在服務(wù)端斷開時也能收到回調(diào)
  • 客戶端調(diào)用遠(yuǎn)程方法時,拋出 DeadObjectException(RemoteException)

寫份代碼驗(yàn)證一下,在客戶端修改為如下:

private val mDeathRecipient = object : IBinder.DeathRecipient {
    
    override fun binderDied() {
        //監(jiān)聽 binder died
        log(TAG, "binder died")
        //移除死亡通知
        mService?.unlinkToDeath(this, 0)
        mService = null
        //重新連接
        connectService()
    }
}

private val serviceConnection = object : ServiceConnection {
    
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        this@AidlActivity.mService = service
        log(TAG, "onServiceConnected")

        //給binder設(shè)置一個死亡代理
        service?.linkToDeath(mDeathRecipient, 0)

        mRemoteServer = IPersonManager.Stub.asInterface(service)
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        log(TAG, "onServiceDisconnected")
    }
}

綁定服務(wù)之后,將服務(wù)端進(jìn)程殺掉,輸出日志如下:

//第一次連接
bindService true
onServiceConnected, thread = main

//殺掉服務(wù)端 
binder died, thread = Binder:29391_3
onServiceDisconnected, thread = main

//重連
bindService true
onServiceConnected, thread = main

確實(shí)是監(jiān)聽到服務(wù)端斷開連接的時刻,然后重新連接也是 ok 的。

注意:binderDied() 方法是運(yùn)行在子線程的,onServiceDisconnected()是運(yùn)行在主線程的,如果要在這里更新UI,得注意一下。

3.2.8 權(quán)限驗(yàn)證

有沒有注意到,目前的 Service 是完全暴露的,任何 app 都可以訪問這個 Service 并且遠(yuǎn)程調(diào)用 Service 的服務(wù),這樣不太安全??梢栽谇鍐挝募屑尤胱远x權(quán)限,然后在 Service 中校驗(yàn)一下客戶端有沒有這個權(quán)限即可。如下:

<permission
    android:name="com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE"
    android:protectionLevel="normal" />

客戶端需要在清單文件中聲明這個權(quán)限:

<uses-permission android:name="com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE"/>

服務(wù)端 Service 校驗(yàn)權(quán)限:

override fun onBind(intent: Intent?): IBinder? {
    val check = checkCallingOrSelfPermission("com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE")
    if (check == PackageManager.PERMISSION_DENIED) {
        log(TAG,"沒有權(quán)限")
        return null
    }
    log(TAG,"有權(quán)限")
    return mBinder
}

以上就是一文詳解在Android中Service和AIDL的使用的詳細(xì)內(nèi)容,更多關(guān)于Service和AIDL的使用的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論