Android 介面定義語言 (AIDL) 與 IDL: 可讓您定義程式設計介面 用戶端與服務會取得同意,才能使用 處理序間通訊 (IPC)
在 Android 裝置上,某個程序通常無法存取 另一個程序的記憶體為了交談,他們需要將物體分解為 使作業系統能理解和管理橫跨該邊界的物件。要使用的程式碼 「搜尋」作業的繁瑣之處在於編寫作業時 Android 會使用 AIDL 為你處理
注意:只有在客戶從下列情況發生時,您才需要使用 AIDL:
不同的應用程式會存取您的服務以接收處理序間通訊 (IPC),而您想要在
課程中也會快速介紹 Memorystore
這是 Google Cloud 的全代管 Redis 服務如果跨
建構介面時,請實作
Binder
。
如果您要執行 IPC,但「不需要」處理多執行緒,
使用 Messenger
實作介面。
無論如何,請務必先瞭解「將服務繫結」
實作 AIDL
開始設計 AIDL 介面之前,請注意對 AIDL 介面的呼叫 直接函式呼叫。請勿假設呼叫內容的執行緒 會發生什麼事回應方式取決於呼叫是否來自 本機處理程序或遠端程序:
- 從本機程序發出的呼叫會在發出呼叫的執行緒中執行。如果
這是您的主要 UI 執行緒,該執行緒會繼續在 AIDL 介面中執行。如果是
也就是另一個執行緒,也就是在服務中執行程式碼的執行緒。因此,如果只有本機
執行緒正在存取服務,您可以完全控制其中正在執行的執行緒。但
如果是這種情況,完全不要使用 AIDL;請改為建立
導入
Binder
。 - 遠端程序呼叫會從平台維護的執行緒集區分派 自己的程序為不明執行緒的來電做好準備,避免重複呼叫 同時發生的問題換句話說,實作 AIDL 介面時必須 完全執行緒安全的執行緒從同一個遠端物件的一個執行緒呼叫 按照順序送達。
oneway
關鍵字會修改遠端呼叫的行為。使用這個元件時,遠端呼叫會 不會封鎖它會傳送交易資料並立即傳回。 對介面的實作方式最終會將此視為來自Binder
執行緒集區的一般呼叫,做為一般遠端呼叫。如果oneway
用於本機呼叫, 沒有影響,而且呼叫仍為同步狀態。
定義 AIDL 介面
使用 Java 在 .aidl
檔案中定義 AIDL 介面
然後儲存到原始碼的 src/
目錄 (
。
建構包含 .aidl
檔案的每個應用程式時,Android SDK 工具
根據 .aidl
檔案產生 IBinder
介面,並將其儲存至
專案的 gen/
目錄內。服務必須實作 IBinder
存取 API接著,用戶端應用程式即可繫結至服務,以及呼叫
執行 IPC 的 IBinder
。
如要使用 AIDL 建立受限服務,請按照下列步驟操作: 下文將深入說明
- 建立
.aidl
檔案此檔案使用方法簽章定義程式設計介面。
- 實作介面
Android SDK 工具會根據您的
.aidl
檔案。這個介麵包含名為Stub
的內部抽象類別,該類別可擴充Binder
,並實作 AIDL 介面中的方法。因此您必須擴充Stub
類別並實作這些方法。 - 向用戶端公開介面
注意:如果您在下列日期後對 AIDL 介面做出任何變更,
您的第一個版本必須保持回溯相容,以免破壞其他應用程式
使用您的服務這是因為 .aidl
檔案必須複製到其他應用程式
如要讓他們存取服務的介面,您必須維持原始版本的支援
存取 API
建立 .aidl 檔案
AIDL 使用簡單的語法,可讓您透過一或多個方法宣告介面 擷取參數並傳回值參數和傳回值可以是任何類型,即使是 AIDL 產生的介面。
您必須使用 Java 程式設計語言建構 .aidl
檔案。每.aidl
檔案必須定義單一介面,且只需要介面宣告和方法
簽章。
根據預設,AIDL 支援下列資料類型:
- Java 程式設計語言中的所有基本類型 (例如
int
、long
、char
、boolean
等) - 原始類型的陣列,例如
int[]
String
CharSequence
List
List
中的所有元素必須是此物件中支援的資料類型之一 或是您宣告的其他 AIDL 產生的介面或 Parcelable。A 罩杯 您可以選擇使用List
做為參數化類型類別,例如List<String>
。 另一端接收到的實際具體類別一律為ArrayList
,不過 方法,以便使用List
介面。Map
Map
中的所有元素必須是此物件中支援的資料類型之一 或是您宣告的其他 AIDL 產生的介面或 Parcelable。參數化類型對應 例如表單形式 系統不支援Map<String,Integer>
。另一端實際具體類別 接收的一律為HashMap
但由於系統產生的方法是使用Map
介面而產生。建議做法Bundle
做為Map
的替代選項。
您必須為先前未列出的每個類型加入 import
陳述式。
即使和介面位於同一個套件中也一樣。
定義服務介面時,請注意下列事項:
- 方法可採用零或多個參數,可以傳回值或 void。
- 所有非原始參數都需要有指示標記來指出資料流向:
in
、out
或inout
(請參閱以下範例)。基本、
String
、IBinder
和 AIDL 生成 介面預設為in
,在其他情況下則否。注意:請將導航範圍限制在真正的範圍內 因為管理參數非常昂貴
.aidl
檔案內含的所有程式碼註解都會包含在 產生的IBinder
介面除外 聲明。- 可在 AIDL 介面 (例如
const int VERSION = 1;
) 中定義字串和 int 常數。 - 方法呼叫會由
transact()
程式碼,一般以介面中的方法索引為基礎。因為這是 會造成版本管理困難 可以將交易代碼手動指派給void method() = 10;
方法。 - 可為空值的引數和傳回類型必須使用
@nullable
加上註解。
以下是 .aidl
的範例檔案:
// IRemoteService.aidl package com.example.android; // Declare any non-default types here with import statements. /** Example service interface */ interface IRemoteService { /** Request the process ID of this service. */ int getPid(); /** Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
將 .aidl
檔案儲存在專案的 src/
目錄中。當您
SDK 工具會在以下位置產生 IBinder
介面檔案:
專案的 gen/
目錄產生的檔案名稱與 .aidl
檔案名稱相符,但
且擴充功能為 .java
擴充功能舉例來說,IRemoteService.aidl
會產生 IRemoteService.java
。
如果使用 Android Studio,漸進式版本會立即產生繫結器類別。
如果您不使用 Android Studio,下次您存取 Gradle 工具時,Gradle 工具會產生繫結器類別
建構應用程式使用 gradle assembleDebug
建立專案
或 gradle assembleRelease
建立完成的 .aidl
檔案。
以便您的程式碼能連結至產生的類別
實作介面
建構應用程式時,Android SDK 工具會產生 .java
介面檔案。
以 .aidl
檔案命名產生的介麵包含一個名為 Stub
的子類別
是其父項介面的抽象實作 (例如 YourInterface.Stub
),並宣告 .aidl
檔案中的所有方法。
注意: Stub
也
定義幾個輔助方法 (特別是 asInterface()
),這些方法會採用 IBinder
(通常是傳遞至用戶端 onServiceConnected()
回呼方法的方法)。
會傳回虛設常式介面的例項。如要進一步瞭解如何進行這種投放程序,請參閱呼叫處理序間通訊 (IPC)
方法。
如要實作從 .aidl
產生的介面,請擴充產生的 Binder
介面 (例如 YourInterface.Stub
),然後實作
繼承自 .aidl
檔案。
以下實作名為 IRemoteService
的介面實作範例,由上述程式碼定義
IRemoteService.aidl
範例:使用匿名例項:
Kotlin
private val binder = object : IRemoteService.Stub() { override fun getPid(): Int = Process.myPid() override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } }
Java
private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } };
現在 binder
是 Stub
類別 (Binder
) 的例項。
,定義服務的 IPC 介面。在下一個步驟中,
才能與服務互動
實作 AIDL 介面時,請注意下列幾項規則:
- 來電並不保證會在主執行緒上執行,因此您需要 從一開始就設計多執行緒,並妥善建構能確保執行緒安全的服務。
- 根據預設,IPC 呼叫是同步的。如果您知道這項服務需要 以毫秒為單位完成要求,請不要從活動的主執行緒呼叫該要求。 它可能會停止運作,導致 Android 顯示「應用程式無回應」 對話方塊從用戶端的不同執行緒呼叫。
- 只有參照文件所列出的例外狀況類型
Parcel.writeException()
敬上 傳回給呼叫者。
向客戶公開介面
為服務實作介面後,您必須向
以便加以繫結。如要顯示介面
針對服務,擴充 Service
並實作 onBind()
,以傳回實作
如上節所述,產生的 Stub
。舉例來說
向用戶端公開 IRemoteService
範例介面的服務。
Kotlin
class RemoteService : Service() { override fun onCreate() { super.onCreate() } override fun onBind(intent: Intent): IBinder { // Return the interface. return binder } private val binder = object : IRemoteService.Stub() { override fun getPid(): Int { return Process.myPid() } override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } } }
Java
public class RemoteService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface. return binder; } private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } }; }
現在,當用戶端 (例如活動) 呼叫 bindService()
以連線至這項服務時,用戶端的 onServiceConnected()
回呼會收到
服務的 onBind()
傳回 binder
個例項
方法。
用戶端也必須有權存取介面類別。因此,如果用戶端和服務
如果是個別的應用程式,用戶端的應用程式必須有 .aidl
檔案的副本
位於 src/
目錄中,這樣會產生 android.os.Binder
介面,以便用戶端存取 AIDL 方法。
用戶端收到 IBinder
時
在 onServiceConnected()
回呼中,您必須呼叫
YourServiceInterface.Stub.asInterface(service)
即可投放
YourServiceInterface
類型的參數:
Kotlin
var iRemoteService: IRemoteService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service) } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "Service has unexpectedly disconnected") iRemoteService = null } }
Java
IRemoteService iRemoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); iRemoteService = null; } };
如需更多程式碼範例,請參閱
RemoteService.java
類別出現在
ApiDemos。
透過 IPC 傳遞物件
在 Android 10 (API 級別 29 以上版本) 中,您可以定義
直接存取 Parcelable
物件
AIDL。支援的類型做為 AIDL 介面引數和其他 parcelable
支援這裡。如此一來,就能避免手動編寫管理程式碼和自訂程式碼
類別然而,這也會建立裸體結構。如果自訂存取子或其他功能
但改為導入 Parcelable
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect { int left; int top; int right; int bottom; }
上述程式碼範例會自動產生含有整數欄位 left
的 Java 類別,
top
、right
和bottom
。所有相關管理程式碼為
因此您可以直接使用物件,不必新增任何設定
。
您也可以透過 IPC 介面,將自訂類別從一個程序傳送至另一個程序。不過
請確認類別的程式碼可供 IPC 通道的另一端取得
您的類別必須支援 Parcelable
介面。提供支援
Parcelable
重要
因為這樣可讓 Android 系統把物件分解為可進行管理的原始物件
跨處理程序
如要建立支援 Parcelable
的自訂類別,請按照下列步驟操作:
包括:
- 讓類別實作
Parcelable
介面。 - 實作
writeToParcel
,後者會採用 物件目前的狀態並將其寫入Parcel
。 - 在類別中新增名為
CREATOR
的靜態欄位,這是實作物件Parcelable.Creator
介面。 - 最後,建立
.aidl
檔案來宣告 parcelable 類別,如下所示Rect.aidl
檔案。如果您使用自訂建構程序,請「不要」將
.aidl
檔案新增至您的 建構應用程式與 C 語言的標頭檔案類似,這個.aidl
檔案未經編譯。
AIDL 會在產生的程式碼中使用這些方法和欄位,進行打包及解組 物件。
舉例來說,以下 Rect.aidl
檔案會建立 Rect
類別,且這個類別將
parcelable:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
以下舉例說明 Rect
類別如何實作
Parcelable
通訊協定。
Kotlin
import android.os.Parcel import android.os.Parcelable class Rect() : Parcelable { var left: Int = 0 var top: Int = 0 var right: Int = 0 var bottom: Int = 0 companion object CREATOR : Parcelable.Creator<Rect> { override fun createFromParcel(parcel: Parcel): Rect { return Rect(parcel) } override fun newArray(size: Int): Array<Rect?> { return Array(size) { null } } } private constructor(inParcel: Parcel) : this() { readFromParcel(inParcel) } override fun writeToParcel(outParcel: Parcel, flags: Int) { outParcel.writeInt(left) outParcel.writeInt(top) outParcel.writeInt(right) outParcel.writeInt(bottom) } private fun readFromParcel(inParcel: Parcel) { left = inParcel.readInt() top = inParcel.readInt() right = inParcel.readInt() bottom = inParcel.readInt() } override fun describeContents(): Int { return 0 } }
Java
import android.os.Parcel; import android.os.Parcelable; public final class Rect implements Parcelable { public int left; public int top; public int right; public int bottom; public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { public Rect createFromParcel(Parcel in) { return new Rect(in); } public Rect[] newArray(int size) { return new Rect[size]; } }; public Rect() { } private Rect(Parcel in) { readFromParcel(in); } public void writeToParcel(Parcel out, int flags) { out.writeInt(left); out.writeInt(top); out.writeInt(right); out.writeInt(bottom); } public void readFromParcel(Parcel in) { left = in.readInt(); top = in.readInt(); right = in.readInt(); bottom = in.readInt(); } public int describeContents() { return 0; } }
Rect
類別中的管理方式相當簡單。來看另一個
看看 Parcel
方法,看看您還可以寫入其他種類的值
至 Parcel
。
警告:提醒您,
擷取來自其他程序的資料在這種情況下,Rect
會從 Parcel
讀取四個數字,但您必須自行確保這些數字落在可接受的範圍內
值。如要進一步瞭解如何保護應用程式不受惡意軟體侵擾,請參閱安全性提示。
具有包含 Parcelables 的 Bundle 引數的方法
假設方法接受應包含的Bundle
物件
parcelables,請務必透過以下項目,設定 Bundle
的類別載入器:
在嘗試讀取前呼叫 Bundle.setClassLoader(ClassLoader)
來自 Bundle
。否則,即使 parcelable 已正確定義在應用程式中,您仍會執行 ClassNotFoundException
。
例如,請參考下列 .aidl
範例檔案:
// IRectInsideBundle.aidl package com.example.android; /** Example service interface */ interface IRectInsideBundle { /** Rect parcelable is stored in the bundle with key "rect". */ void saveRect(in Bundle bundle); }
ClassLoader
是
在讀取 Rect
之前,明確在 Bundle
中設定:
Kotlin
private val binder = object : IRectInsideBundle.Stub() { override fun saveRect(bundle: Bundle) { bundle.classLoader = classLoader val rect = bundle.getParcelable<Rect>("rect") process(rect) // Do more with the parcelable. } }
Java
private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { public void saveRect(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); Rect rect = bundle.getParcelable("rect"); process(rect); // Do more with the parcelable. } };
呼叫處理序間通訊 (IPC) 方法
如要呼叫以 AIDL 定義的遠端介面,請在以下項目中按照下列步驟操作: 呼叫類別:
- 在專案
src/
目錄中納入.aidl
檔案。 - 宣告
IBinder
介面的例項,這是依據 AIDL。 - 導入
ServiceConnection
。 - 撥打
Context.bindService()
: 傳入ServiceConnection
實作。 - 實作
onServiceConnected()
時, 您收到IBinder
稱為service
致電YourInterfaceName.Stub.asInterface((IBinder)service)
到 將傳回的參數轉換為YourInterface
類型。 - 呼叫您在介面中定義的方法。一律關閉通知
DeadObjectException
例外狀況,會在發生以下情況時擲回 連線中斷。此外,處理 IPC 方法呼叫的兩個程序有衝突的 AIDL 定義時,系統會擲回SecurityException
例外狀況。 - 如要中斷連線,請使用介面的執行個體呼叫
Context.unbindService()
。
呼叫處理序間通訊 (IPC) 服務時,請留意下列要點:
- 物件會跨程序計算的參考資料。
- 您可以傳送匿名物件 做為方法引數
如要進一步瞭解如何繫結至服務,請參閱「繫結服務總覽」。
下列程式碼範例示範如何呼叫 AIDL 建立的服務, 使用遠端服務範例
Kotlin
private const val BUMP_MSG = 1 class Binding : Activity() { /** The primary interface you call on the service. */ private var mService: IRemoteService? = null /** Another interface you use on the service. */ internal var secondaryService: ISecondary? = null private lateinit var killButton: Button private lateinit var callbackText: TextView private lateinit var handler: InternalHandler private var isBound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service) killButton.isEnabled = true callbackText.text = "Attached." // We want to monitor the service for as long as we are // connected to it. try { mService?.registerCallback(mCallback) } catch (e: RemoteException) { // In this case, the service crashes before we can // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_connected, Toast.LENGTH_SHORT ).show() } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null killButton.isEnabled = false callbackText.text = "Disconnected." // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_disconnected, Toast.LENGTH_SHORT ).show() } } /** * Class for interacting with the secondary interface of the service. */ private val secondaryConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service) killButton.isEnabled = true } override fun onServiceDisconnected(className: ComponentName) { secondaryService = null killButton.isEnabled = false } } private val mBindListener = View.OnClickListener { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. val intent = Intent(this@Binding, RemoteService::class.java) intent.action = IRemoteService::class.java.name bindService(intent, mConnection, Context.BIND_AUTO_CREATE) intent.action = ISecondary::class.java.name bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE) isBound = true callbackText.text = "Binding." } private val unbindListener = View.OnClickListener { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. try { mService?.unregisterCallback(mCallback) } catch (e: RemoteException) { // There is nothing special we need to do if the service // crashes. } // Detach our existing connection. unbindService(mConnection) unbindService(secondaryConnection) killButton.isEnabled = false isBound = false callbackText.text = "Unbinding." } } private val killListener = View.OnClickListener { // To kill the process hosting the service, we need to know its // PID. Conveniently, the service has a call that returns // that information. try { secondaryService?.pid?.also { pid -> // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app, as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid) callbackText.text = "Killed service process." } } catch (ex: RemoteException) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show() } } // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private val mCallback = object : IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ override fun valueChanged(value: Int) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)) } } /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.remote_service_binding) // Watch for button taps. var button: Button = findViewById(R.id.bind) button.setOnClickListener(mBindListener) button = findViewById(R.id.unbind) button.setOnClickListener(unbindListener) killButton = findViewById(R.id.kill) killButton.setOnClickListener(killListener) killButton.isEnabled = false callbackText = findViewById(R.id.callback) callbackText.text = "Not attached." handler = InternalHandler(callbackText) } private class InternalHandler( textView: TextView, private val weakTextView: WeakReference<TextView> = WeakReference(textView) ) : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}" else -> super.handleMessage(msg) } } } }
Java
public static class Binding extends Activity { /** The primary interface we are calling on the service. */ IRemoteService mService = null; /** Another interface we use on the service. */ ISecondary secondaryService = null; Button killButton; TextView callbackText; private InternalHandler handler; private boolean isBound; /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.remote_service_binding); // Watch for button taps. Button button = (Button)findViewById(R.id.bind); button.setOnClickListener(mBindListener); button = (Button)findViewById(R.id.unbind); button.setOnClickListener(unbindListener); killButton = (Button)findViewById(R.id.kill); killButton.setOnClickListener(killListener); killButton.setEnabled(false); callbackText = (TextView)findViewById(R.id.callback); callbackText.setText("Not attached."); handler = new InternalHandler(callbackText); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service); killButton.setEnabled(true); callbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { mService.registerCallback(mCallback); } catch (RemoteException e) { // In this case the service crashes before we can even // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null; killButton.setEnabled(false); callbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection secondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service); killButton.setEnabled(true); } public void onServiceDisconnected(ComponentName className) { secondaryService = null; killButton.setEnabled(false); } }; private OnClickListener mBindListener = new OnClickListener() { public void onClick(View v) { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. Intent intent = new Intent(Binding.this, RemoteService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); intent.setAction(ISecondary.class.getName()); bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE); isBound = true; callbackText.setText("Binding."); } }; private OnClickListener unbindListener = new OnClickListener() { public void onClick(View v) { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // crashes. } } // Detach our existing connection. unbindService(mConnection); unbindService(secondaryConnection); killButton.setEnabled(false); isBound = false; callbackText.setText("Unbinding."); } } }; private OnClickListener killListener = new OnClickListener() { public void onClick(View v) { // To kill the process hosting our service, we need to know its // PID. Conveniently, our service has a call that returns // that information. if (secondaryService != null) { try { int pid = secondaryService.getPid(); // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid); callbackText.setText("Killed service process."); } catch (RemoteException ex) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(Binding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ public void valueChanged(int value) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)); } }; private static final int BUMP_MSG = 1; private static class InternalHandler extends Handler { private final WeakReference<TextView> weakTextView; InternalHandler(TextView textView) { weakTextView = new WeakReference<>(textView); } @Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: TextView textView = weakTextView.get(); if (textView != null) { textView.setText("Received from service: " + msg.arg1); } break; default: super.handleMessage(msg); } } } }