Android 介面定義語言 (AIDL) 與其他 IDL 類似:可讓您定義用戶端和服務同意的程式設計介面,以便透過進程間通訊 (IPC) 彼此通訊。
在 Android 上,一個程序無法正常存取另一個程序的記憶體。為了進行對話,他們需要將物件分解為作業系統可理解的基礎元素,並為您在該邊界上進行物件對齊。執行管理作業的程式碼相當繁瑣,因此 Android 會使用 AIDL 為您處理。
注意:只有在您讓不同應用程式的用戶端存取服務以進行 IPC,且想在服務中處理多執行緒時,才需要使用 AIDL。如果不需要在不同應用程式中執行並行 IPC,您可以實作 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
介面。接著,用戶端應用程式就能繫結至服務,並呼叫 IBinder
中的各項方法,執行 IPC。
如要使用 AIDL 建立受限服務,請按照下列各節的說明操作:
- 建立
.aidl
檔案這個檔案會定義含有方法簽名的程式設計介面。
- 實作介面
Android SDK 工具會根據
.aidl
檔案,以 Java 程式設計語言產生介面。這個介面具有名為Stub
的內部抽象類別,可擴充Binder
,並實作 AIDL 介面的方法。您必須擴充Stub
類別並實作方法。 - 向用戶端公開介面
注意:您在首次發布後對 AIDL 介面所做的任何變更,都必須保持向後相容,以免影響使用您服務的其他應用程式。也就是說,由於 .aidl
檔案必須複製到其他應用程式,才能存取服務的介面,因此您必須維持對原始介面的支援。
建立 .aidl 檔案
AIDL 採用簡單的語法,讓您可以使用一或多個可接受參數和傳回值的方法宣告介面。參數和傳回值可以是任何類型,甚至是其他 AIDL 產生的介面。
您必須使用 Java 程式設計語言建構 .aidl
檔案。每個 .aidl
檔案都必須定義單一介面,且只需要介面宣告和方法簽章。
根據預設,AIDL 支援下列資料類型:
- Java 程式設計語言中的所有原始類型 (例如
int
、long
、char
、boolean
等) - 任何類型的陣列,例如
int[]
或MyParcelable[]
String
CharSequence
List
List
中的所有元素都必須必須是這份清單中支援的資料類型,或是您宣告的其他 AIDL 產生的介面或 Parcelable。List
可視需要用做參數化型別類別,例如List<String>
。雖然方法是為了使用List
介面而產生,但另一端收到的實際具體類別一律是ArrayList
。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 工具會在專案的 gen/
目錄中產生 IBinder
介面檔案。產生的檔案名稱與 .aidl
檔案名稱相符,但副檔名為 .java
。例如,IRemoteService.aidl
會產生 IRemoteService.java
。
如果您使用 Android Studio,增量建構作業幾乎會立即產生繫結器類別。如果您未使用 Android Studio,Gradle 工具會在您下次建構應用程式時產生繫結器類別。寫入 .aidl
檔案後,請使用 gradle assembleDebug
或 gradle assembleRelease
建構專案,讓您的程式碼能連結至產生的類別。
實作介面
建構應用程式時,Android SDK 工具會產生以 .aidl
檔案命名的 .java
介面檔案。產生的介面包含名為 Stub
的子類別,這是其父項介面 (例如 YourInterface.Stub
) 的抽象實作,並宣告 .aidl
檔案中的所有方法。
注意: Stub
也定義了幾個輔助方法,其中最值得注意的是 asInterface()
,它會接收 IBinder
,通常是傳遞至用戶端 onServiceConnected()
回呼方法的 IBinder
,並傳回 Stub 介面的例項。如要進一步瞭解如何進行投放,請參閱「呼叫 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
例項。
用戶端也必須具備介面類別的存取權。因此,如果用戶端和服務位於不同的應用程式中,用戶端應用程式必須在其 src/
目錄中擁有 .aidl
檔案的副本,以便產生 android.os.Binder
介面,讓用戶端可以存取 AIDL 方法。
當用戶端在 onServiceConnected()
回呼中收到 IBinder
時,必須呼叫 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; } };
如需更多程式碼範例,請參閱
ApiDemos 中的
RemoteService.java
類別。
透過 IPC 傳遞物件
在 Android 10 (API 級別 29 以上) 中,您可以直接在 AIDL 中定義 Parcelable
物件。這裡也支援做為 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
、top
、right
和 bottom
的 Java 類別。系統會自動實作所有相關的對應程式碼,您可以直接使用物件,無須額外新增任何實作項目。
您也可以透過 IPC 介面,將自訂類別從一個程序傳送至另一個程序。不過,請務必確保您的類別程式碼可供 IPC 通道的另一端使用,且您的類別必須支援 Parcelable
介面。支援 Parcelable
非常重要,因為這可讓 Android 系統將物件分解為可在各個程序中配屬的基礎元素。
如要建立支援 Parcelable
的自訂類別,請按照下列步��操作:
- 讓類別實作
Parcelable
介面。 - 實作
writeToParcel
,該方法會採用物件的目前狀態,並將其寫入Parcel
。 - 將名為
CREATOR
的靜態欄位新增至類別,該類別是實作Parcelable.Creator
介面的物件。 - 最後,請建立宣告可分割類別的
.aidl
檔案,如以下Rect.aidl
檔案所示。如果您使用自訂建構程序,請不要將
.aidl
檔案新增至建構項目。與 C 語言中的標頭檔案類似,這個.aidl
檔案不會編譯。
AIDL 會在產生的程式碼中使用這些方法和欄位,以便將物件進行對齊和反對齊。
例如,以下是用來建立可分割的 Rect
類別的 Rect.aidl
檔案:
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
讀取四個數字,但您必須確保這些數字在呼叫端嘗試執行的任何操作中,都屬於可接受的值範圍。如要進一步瞭解如何保護應用程式免受惡意軟體侵害,請參閱安全性提示。
含有包含 Parcelable 的套件引數的方法
如果方法接受預期包含可分割的Bundle
物件,請務必在嘗試從 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()
的實作中,您會收到名為service
的IBinder
例項。呼叫YourInterfaceName.Stub.asInterface((IBinder)service)
將傳回的參數轉換為YourInterface
類型。 - 呼叫您在介面上定義的方法。請一律擷取
DeadObjectException
例外狀況,這些例外狀況會在連線中斷時擲回。此外,請擷取SecurityException
例外狀況,當 IPC 方法呼叫中涉及的兩個程序有衝突的 AIDL 定義時,系統會擲回此例外狀況。 - 如要中斷連線,請使用介面的例項呼叫
Context.unbindService()
。
呼叫 IPC 服務時,請留意以下幾點:
- 物件會在各個程序中計算參照計數。
- 您可以傳送匿名物件做為方法引數。
如要進一步瞭解如何繫結服務,請參閱「繫結服務總覽」。
以下是一些範例程式碼,說明如何呼叫 AIDL 建立的服務,這些程式碼取自 ApiDemos 專案中的遠端服務範例。
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); } } } }