什么是 AIDL
AIDL 全稱?Android Interface Definition Language,即?安卓接口描述語(yǔ)言。聽起來(lái)很深?yuàn)W,其實(shí)它的本質(zhì)就是生成進(jìn)程間通信接口的輔助工具。它的存在形式是一種?.aidl?文件,開發(fā)者需要做的就是在該文件中定義進(jìn)程間通信的接口,編譯的時(shí)候 IDE 就會(huì)根據(jù)我們的?.aidl?接口文件生成可供項(xiàng)目使用的?.java?文件,這和我們說(shuō)的“語(yǔ)法糖”有些類似。
AIDL 的語(yǔ)法就是 java 的語(yǔ)法,就是導(dǎo)包上有點(diǎn)細(xì)微差別。java 中如果兩個(gè)類在相同的包中,是不需要進(jìn)行導(dǎo)包操作的,但是在 AIDL 中,則必須進(jìn)行導(dǎo)包聲明。
?
AIDL 詳解
構(gòu)想一個(gè)場(chǎng)景:我們有一個(gè)圖書管理系統(tǒng),這個(gè)系統(tǒng)的通過(guò) CS 模式來(lái)實(shí)現(xiàn)。具體的管理功能由服務(wù)端進(jìn)程來(lái)實(shí)現(xiàn),客戶端只需要調(diào)用相應(yīng)的接口就可以。
那么首先定義這個(gè)管理系統(tǒng)的 ADIL 接口。
我們?cè)?/rc 新建?aidl?包,包中有三個(gè)文件 Book.java 、Book.aidl、IBookManager.aidl 三個(gè)文件。
package com.example.aidl book public class Book implements Parcelable { int bookId; String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } ... }
package com.example.aidl;
Parcelable Book;
package com.example.aidl; import com.example.aidl.Book; inteface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
下面對(duì)這三個(gè)文件分別進(jìn)行說(shuō)明:
Book.java 是我們定義的實(shí)體類,它實(shí)現(xiàn)了 Parcelable 接口,這樣 Book 類才能在進(jìn)程間傳輸。
Book.aidl 是這個(gè)實(shí)體類在 AIDL 中的聲明。
IBookManager 是服務(wù)端和客戶端通信的接口。(注意,在 AIDL 接口中除基本類型外,參數(shù)前須加方向,in 表示輸入型參數(shù),out 表示輸出型參數(shù),inout 表示輸入輸出型參數(shù))
編譯器編譯后,android studio 為我們的項(xiàng)目自動(dòng)生成了一個(gè) .java 文件,這個(gè)文件包含三個(gè)類,這三個(gè)類分別是 IBookManager, Stub 和 Proxy,這三個(gè)類都是靜態(tài)類型,我們完全可以把他們分開來(lái),三個(gè)類定義如下:
IBookManager
public interface IBookManager extends android.os.IInterface { public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException; public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException; }
Stub
public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager { private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager"; static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an net.bingyan.library.IBookManager interface, * generating a proxy if needed. http://www.manongjc.com/article/1501.html */ public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) { return ((net.bingyan.library.IBookManager) iin); } return new net.bingyan.library.IBookManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); net.bingyan.library.Book _arg0; if ((0 != data.readInt())) { _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.bingyan.library.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); } }
Proxy
private static class Proxy implements net.bingyan.library.IBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. http://www.manongjc.com/article/1500.html */ @Override public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<net.bingyan.library.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } }
對(duì)生成的這三個(gè)類的說(shuō)明如下:
IBookManager 這個(gè)類是我們定義的接口,android studio 給它添加了一個(gè)父類,讓它繼承自 android.os.interface 這個(gè)接口,這個(gè)接口只有一個(gè)方法 IBinder asBinder(),這樣 IBookManager 中就有三個(gè)帶實(shí)現(xiàn)的方法了,它是服務(wù)端進(jìn)程和客戶端進(jìn)程通信的窗口。
Stub 是個(gè)抽象類,這個(gè)類繼承自 android.os.Binder 類,并且實(shí)現(xiàn)的了 IBookManager 這個(gè)接口。在 Stub 中,已經(jīng)實(shí)現(xiàn)了 asBinder() 這個(gè)接口方法,還有兩個(gè)是我們定義的 AIDL 接口方法留給繼承它的子類去實(shí)現(xiàn)。它用在服務(wù)端,因此服務(wù)端需要實(shí)現(xiàn)這兩個(gè)方法。
Proxy 顧名思義是一個(gè)代理類,它是服務(wù)端在客戶端的一個(gè)代理,它也實(shí)現(xiàn)了 IBookManager接口,并且實(shí)現(xiàn)了 IBookManager 中的所有方法。它用在客戶端,是服務(wù)端在客戶端的代理。
現(xiàn)在我們對(duì)這三個(gè)類逐個(gè)分析:
IBookManager 這個(gè)類沒(méi)什么好說(shuō)的,它只是簡(jiǎn)單繼承了 asInterface 這個(gè)接口,作用就是將 IBookManager 轉(zhuǎn)換成 IBinder。
Proxy 這個(gè)類上面已經(jīng)提到過(guò)了,它就是進(jìn)程間通信機(jī)制的一個(gè)封裝類,他的內(nèi)部實(shí)現(xiàn)機(jī)制就是 Binder,通過(guò)構(gòu)造方法我們也容易看出來(lái)。它的構(gòu)造方法接受一個(gè) IBinder 類型的參數(shù),參數(shù)名為 remote,顯然,它代表著服務(wù)端。我們看看這個(gè)類中的方法 addBook() 和 getBookList():
@Override public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR) if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override /* http://www.manongjc.com/article/1547.html */ public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<net.bingyan.library.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; }
它們是編譯器自動(dòng)實(shí)現(xiàn)的,這兩個(gè)方法有很多類似之處,可以現(xiàn)在這里透露下:這兩個(gè)方法就是客戶端進(jìn)程調(diào)用服務(wù)端進(jìn)程的窗口。在這兩個(gè)方法的開始,它們都定義了兩個(gè) Parcel(中文譯名:包裹)對(duì)象。Parcel 這個(gè)類我們看上去很眼熟,是的,Book 類中的 writeToParcel() 和 CREATOR中的 createFromParcel() 的參數(shù)就是 Parcel 類型的,關(guān)于這個(gè)類文檔中解釋如下:
Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general {@link Parcelable} interface), and references to live {@link IBinder} objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.
翻譯一下:Proxy 是一個(gè)可以通過(guò) IBinder 進(jìn)行消息傳遞的一個(gè)容器。一個(gè) Parcel 可以包含可序列化的數(shù)據(jù),這些數(shù)據(jù)會(huì)在 IPC 的另一端被反序列化;它也可以包含指向 IBinder 對(duì)象的引用,這會(huì)使得另一端接收到一個(gè) IBinder 類型的代理對(duì)象,這個(gè)代理對(duì)象連接著 Parcel 中的原始 IBinder 對(duì)象。
下面用圖來(lái)直觀的說(shuō)明:
如圖,我們可以很直觀的看到服務(wù)端以 Parcel 作為數(shù)據(jù)包裹依靠 Binder 和客戶端進(jìn)行通信。數(shù)據(jù)包裹就是序列化之后的對(duì)象。
如上所述,這兩個(gè)方法都定義了兩個(gè) Parcel 對(duì)象,分別叫做 _data 和 _reply,形象的來(lái)說(shuō),從客戶端的角度來(lái)看,_data 就是客戶端發(fā)送給服務(wù)端的數(shù)據(jù)包裹,_reply 服務(wù)端發(fā)送給客戶端的數(shù)據(jù)包裹。
之后便開始用這兩個(gè)對(duì)象來(lái)和服務(wù)端進(jìn)行通信了,我們能夠觀察到,兩個(gè)方法中都有這么個(gè)方法調(diào)用 mRemote.transact(),它有四個(gè)參數(shù),第一個(gè)參數(shù)的意義我們后面再講,第二個(gè)參數(shù) _data 負(fù)責(zé)向服務(wù)端發(fā)送數(shù)據(jù)包裹比如接口方法的參數(shù),第三個(gè)參數(shù) _reply 負(fù)責(zé)從服務(wù)端接收數(shù)據(jù)包裹比如接口方法的返回值。這行代碼只有一句簡(jiǎn)單的方法調(diào)用,但是卻是 AIDL 通信的最核心部分,它其實(shí)進(jìn)行了一次遠(yuǎn)程方法調(diào)用(客戶端通過(guò)本地代理 Proxy 暴露的接口方法調(diào)用服務(wù)端 Stub 同名方法),所以能想到它是一個(gè)耗時(shí)操作。
在我們的例子中:
void addBook(Book book) 需要借助 _data 向服務(wù)端發(fā)送參數(shù) Book:book,發(fā)送的方式就是把 Book 通過(guò)其實(shí)現(xiàn)的 writeToParcel(Parcel out) 方法打包至 _data 中,正如你能想到的,_data 其實(shí)就是參數(shù) out,還記得 Book 中的這個(gè)方法的實(shí)現(xiàn)嗎? 我們是將 Book 的字段一個(gè)個(gè)打包至 Parcel 中的。
List
當(dāng)然這兩個(gè)方法中的 _data 和 _reply 不僅傳遞了對(duì)象,還傳遞了一些校驗(yàn)信息,這個(gè)我們可以不必深究,但應(yīng)注意的是,Parcel 打包順序和解包順序要嚴(yán)格對(duì)應(yīng)。例如,第一個(gè)打包的是 int:i,那么第一解包的也應(yīng)該是這個(gè)整型值。也即打包時(shí)第一次調(diào)用的如果是 Parcel.writeInt(int),解包時(shí)第一次調(diào)用的應(yīng)該是 Parcel.readInt()。
到此,客戶端的 Proxy 講解完了,下面我們看看服務(wù)端的 Stub。
Stub 中實(shí)現(xiàn)了 IBookManager 的其中一個(gè)方法,這個(gè)很簡(jiǎn)單,就是簡(jiǎn)單的將自身返回,因?yàn)?Stub 本身就繼承自 Binder,而 Binder 繼承自 IBinder,所以沒(méi)有任何問(wèn)題。你會(huì)問(wèn):還有兩個(gè)方法沒(méi)實(shí)現(xiàn)呢?這兩個(gè)方法就是我們定義的接口方法,它們留給服務(wù)端進(jìn)程去實(shí)現(xiàn),也就是說(shuō),到時(shí)候我們?cè)诜?wù)端進(jìn)程中需要定義一個(gè) Stub 的實(shí)現(xiàn)者。下面對(duì) Stub 中的兩個(gè)重要方法進(jìn)行分析:
IBookManager asInterface(IBinder obj)
public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) { return ((net.bingyan.library.IBookManager) iin); } return new net.bingyan.library.IBookManager.Stub.Proxy(obj); }
這個(gè)方法的作用是將 Stub 類轉(zhuǎn)換成 IBookManager 這個(gè)接口,方法中有個(gè)判斷:如果我們的服務(wù)端進(jìn)程和客戶端進(jìn)程是同一進(jìn)程,那么就直接將 Stub 類通過(guò)類型轉(zhuǎn)換轉(zhuǎn)成 IBookManager;如果不是同一進(jìn)程,那么就通過(guò)代理類 Proxy 將 Stub 轉(zhuǎn)換成 IBookManager。為什么這么做,我們知道如果服務(wù)端進(jìn)程和客戶端進(jìn)程不是同一進(jìn)程,那么它們的內(nèi)存就不能共享,就不能通過(guò)一般的方式進(jìn)行通信,但是我們?nèi)绻约喝?shí)現(xiàn)進(jìn)程間通信方式,對(duì)于普通開發(fā)者來(lái)說(shuō)成本太大,因此編譯器幫我們生成了一個(gè)封裝了了進(jìn)程間通信的工具,也就是這個(gè) Proxy,這個(gè)類對(duì)底層的進(jìn)程通信機(jī)制進(jìn)行了封裝只同時(shí)暴露出接口方法,客戶端只需要調(diào)用這兩個(gè)方法實(shí)現(xiàn)進(jìn)程間通信(其實(shí)就是方法的遠(yuǎn)程調(diào)用)而不需要了解其中的細(xì)節(jié)。
有了這個(gè)方法,我們?cè)诳蛻舳丝梢越柚鋵⒁粋€(gè) IBinder 類型的變量轉(zhuǎn)換成我們定義的接口 IBookManager,它的使用場(chǎng)景我們會(huì)在后面的實(shí)例中進(jìn)行講解。
onTransact(int code, Parcel data, Parcel reply, int flags)
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); net.bingyan.library.Book _arg0; if ((0 != data.readInt())) { _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; /* http://www.manongjc.com/article/1499.html */ } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.bingyan.library.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); }
這個(gè)方法我們是不是也很熟悉呢?我們?cè)?Proxy 中也看到一個(gè)類似得方法 transact(int, Parcel, Parcel, int),它們的參數(shù)一樣,而且它們都是 Binder 中的方法,那么它們有什么聯(lián)系呢?
前面說(shuō)了,transact() 執(zhí)行了一個(gè)遠(yuǎn)程調(diào)用,如果說(shuō) transact() 是遠(yuǎn)程調(diào)用的發(fā)起,那么 onTransact() 就是遠(yuǎn)程調(diào)用的響應(yīng)。真實(shí)過(guò)程是客戶端發(fā)器遠(yuǎn)程方法調(diào)用,android 系統(tǒng)通過(guò)底層代碼對(duì)這個(gè)調(diào)用進(jìn)行響應(yīng)和處理,之后回調(diào)服務(wù)端的 onTransact() 方法,從數(shù)據(jù)包裹中取出方法參數(shù),交給服務(wù)端實(shí)現(xiàn)的同名方法調(diào)用,最后將返回值打包返回給客戶端。
需要注意的是 onTransact() 是在服務(wù)端進(jìn)程的 Binder 線程池中進(jìn)行的,這就意味著如果我們的要在 onTransact() 方法的中更新 UI,就必須借助 Handler。
這兩個(gè)方法的第一個(gè)參數(shù)的含義是 AIDL 接口方法的標(biāo)識(shí)碼,在 Stub 中,定義了兩個(gè)常量作為這兩個(gè)方法的標(biāo)示:
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
如果 code == TRANSACTION_addBook,那么說(shuō)明客戶端調(diào)用的是 addBook();如果 code == TRANSACTION_getBookList,那么客戶端調(diào)用的是 getBookList(),然后交由相應(yīng)的服務(wù)端方法處理。 用一張圖來(lái)表示整個(gè)通信過(guò)程:
了解了 AIDL 的整個(gè)過(guò)程,接下來(lái)就是 AIDL 在安卓程序中的應(yīng)用了。
AIDL 的使用
相信大家應(yīng)該都和清楚 Service 的使用了吧,Service 雖然稱作“服務(wù)”,并且運(yùn)行于后臺(tái),但是它們默認(rèn)還是運(yùn)行在默認(rèn)進(jìn)程的主線程中。其實(shí)讓 Service 運(yùn)行在默認(rèn)進(jìn)程中,有點(diǎn)大材小用了。android 的很多系統(tǒng)服務(wù)都運(yùn)行于單獨(dú)的進(jìn)程中,供其他應(yīng)用調(diào)用,比如窗口管理服務(wù)。這樣做的好處是可以多個(gè)應(yīng)用共享同一個(gè)服務(wù),節(jié)約了資源,也便于集中管理各個(gè)客戶端,要注意問(wèn)題的就是線程安全問(wèn)題。
那么接下來(lái)我們就用 AIDL 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 CS 架構(gòu)的圖書管理系統(tǒng)。
首先我們定義服務(wù)端:
BookManagerService
public class BookManagerService extends Service { private final List<Book> mLibrary = new ArrayList<>(); private IBookManager mBookManager = new IBookManager.Stub() { @Override public void addBook(Book book) throws RemoteException { synchronized (mLibrary) { mLibrary.add(book); Log.d("BookManagerService", "now our library has " + mLibrary.size() + " books"); } } @Override public List<Book> getBookList() throws RemoteException { return mLibrary; } }; @Override /* http://www.manongjc.com/article/1496.html */ public IBinder onBind(Intent intent) { return mBookManager.asBinder(); } }
服務(wù)端我們定義了 BookManagerService 這個(gè)類,在它里面我們創(chuàng)建了服務(wù)端的 Stub 對(duì)象,并且實(shí)現(xiàn)了需要實(shí)現(xiàn)的兩個(gè) AIDL 接口方法來(lái)定義服務(wù)端的圖書管理策略。在 onBind() 方法中我們將 IBookManager 對(duì)象作為 IBinder 返回。我們知道,當(dāng)我們綁定一個(gè)服務(wù)時(shí),系統(tǒng)會(huì)調(diào)用 onBinder() 方法得到服務(wù)端的 IBinder 對(duì)象,并將其轉(zhuǎn)換成客戶端的 IBinder 對(duì)象傳給客戶端,雖然服務(wù)端的 IBinder 和 客戶端的 IBinder 是兩個(gè) IBinder 對(duì)象,但他們?cè)诘讓佣际峭粋€(gè)對(duì)象。我們?cè)?xml 中注冊(cè) Service 時(shí)給它指定了進(jìn)程名,這樣 Service 就能運(yùn)行在單獨(dú)的進(jìn)程中了。
接下來(lái)看看客戶端的實(shí)現(xiàn):
Client
public class Client extends AppCompatActivity { private TextView textView; private IBookManager bookManager; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.library_book_manager_system_client); Intent i = new Intent(Client.this, BookManagerService.class); bindService(i, conn, BIND_AUTO_CREATE); Button addABook = (Button) findViewById(R.id.button); addABook.setOnClickListener(v -> { if (bookManager == null) return; try { bookManager.addBook(new Book(0, "book")); textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size()))); } catch (RemoteException e) { e.printStackTrace(); } }); textView = (TextView) findViewById(R.id.textView); } private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("Client -->", service.toString()); bookManager = IBookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.d("Client", name.toString()); } }; }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:weightSum="1" android:gravity="center"> <Button android:text="http://www.manongjc.com/article/1495.html" android:layout_width="111dp" android:layout_height="wrap_content" android:id="@+id/button" /> <TextView android:layout_marginTop="10dp" android:text="@string/book_management_system_book_count" android:layout_width="231dp" android:gravity="center" android:layout_height="wrap_content" android:id="@+id/textView" /> </LinearLayout>
我們的客戶端就是一個(gè)?Activity,onCreate()?中進(jìn)行了服務(wù)的綁定,bindService()?方法中有一參數(shù)?ServiceConnection:conn,因?yàn)榻壎ǚ?wù)是異步進(jìn)行的,這個(gè)參數(shù)的作用就是綁定服務(wù)成功后回調(diào)的接口,它有兩個(gè)回調(diào)方法:一個(gè)是連接服務(wù)成功后回調(diào),另一個(gè)在與服務(wù)端斷開連接后回調(diào)。我們現(xiàn)在關(guān)心的主要是?onServiceConnected()?方法,在這里我們只做了一件事:將服務(wù)端轉(zhuǎn)換過(guò)來(lái)的?IBinder?對(duì)象轉(zhuǎn)換成 AIDL 接口,我們定義?IBookManager:bookManager?字段來(lái)保持對(duì)其的引用。這樣的話,我們就可以通過(guò)這個(gè)?bookManager?來(lái)進(jìn)行方法的遠(yuǎn)程調(diào)用。我們給客戶端的?Button?注冊(cè)事件:每一次點(diǎn)擊都會(huì)向服務(wù)端增加一本書,并且將圖書館現(xiàn)有的圖書數(shù)量顯示出來(lái)。
現(xiàn)在我們看看程序的運(yùn)行效果:
每當(dāng)我們點(diǎn)擊按鈕,我們就成功的向服務(wù)端添加了一本書,說(shuō)明我們通過(guò) AIDL 跨進(jìn)程通信成功了。

Alat AI Hot

Undress AI Tool
Gambar buka pakaian secara percuma

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Untuk mengendalikan transaksi JDBC dengan betul, anda mesti terlebih dahulu mematikan mod komit automatik, kemudian melakukan pelbagai operasi, dan akhirnya melakukan atau mengembalikan semula hasilnya; 1. Panggil Conn.SetAutOcommit (palsu) untuk memulakan transaksi; 2. Melaksanakan pelbagai operasi SQL, seperti memasukkan dan mengemaskini; 3. Panggil Conn.Commit () jika semua operasi berjaya, dan hubungi conn.rollback () jika pengecualian berlaku untuk memastikan konsistensi data; Pada masa yang sama, cuba-dengan-sumber harus digunakan untuk menguruskan sumber, mengendalikan pengecualian dengan betul dan menutup sambungan untuk mengelakkan kebocoran sambungan; Di samping itu, adalah disyorkan untuk menggunakan kolam sambungan dan menetapkan mata simpan untuk mencapai rollback separa, dan menyimpan urus niaga sesingkat mungkin untuk meningkatkan prestasi.

Gunakan kelas dalam pakej Java.Time untuk menggantikan kelas lama dan kelas kalendar; 2. Dapatkan tarikh dan masa semasa melalui LocalDate, LocalDateTime dan Tempatan Tempatan; 3. Buat tarikh dan masa tertentu menggunakan kaedah (); 4. Gunakan kaedah tambah/tolak untuk meningkatkan dan mengurangkan masa; 5. Gunakan zoneddatetime dan zonid untuk memproses zon waktu; 6. Format dan parse date string melalui DateTimeFormatter; 7. Gunakan segera untuk bersesuaian dengan jenis tarikh lama apabila perlu; pemprosesan tarikh di java moden harus memberi keutamaan untuk menggunakan java.timeapi, yang memberikan jelas, tidak berubah dan linear

Pra-formancetartuptimemoryusage, quarkusandmicronautleadduetocompile-timeprocessingandgraalvsupport, withquarkusoftenperforminglightbetterine serverless scenarios.tyvelopecosyste,

NetworkPortsandFireWallSworkTogethertoenableCommunicationWileensuringsecurity.1.networkportsarevirtualendpointsNumbered0-655 35, Withwell-KnownportsLike80 (http), 443 (https), 22 (ssh), dan25 (smtp) identitispecificservices.2.portsoperateovertcp (boleh dipercayai, c

Koleksi Sampah Java (GC) adalah mekanisme yang secara automatik menguruskan ingatan, yang mengurangkan risiko kebocoran ingatan dengan menuntut semula objek yang tidak dapat dicapai. 1.GC menghakimi kebolehcapaian objek dari objek akar (seperti pembolehubah stack, benang aktif, medan statik, dan lain -lain), dan objek yang tidak dapat dicapai ditandakan sebagai sampah. 2. Berdasarkan algoritma penandaan tanda, tandakan semua objek yang dapat dicapai dan objek yang tidak ditandai. 3. Mengamalkan strategi pengumpulan generasi: Generasi Baru (Eden, S0, S1) sering melaksanakan MinorGC; Orang tua melakukan kurang tetapi mengambil masa lebih lama untuk melakukan MajorGC; Metaspace Stores Metadata kelas. 4. JVM menyediakan pelbagai peranti GC: SerialGC sesuai untuk aplikasi kecil; ParallelGC meningkatkan throughput; CMS mengurangkan

GradleisthebetterChoiceFormostNewProjectSduetoitSsuperiorflexibility, Prestasi, danModernToolingSupport.1.Gradle'sGroovy/KOT lindslismoreconciseandexpressivethanmaven'sverbosexml.2.GradleOutPerformsMaveninBuildSpeedWithIncrementalcompilation, BuildCac

Defer digunakan untuk melaksanakan operasi tertentu sebelum fungsi pulangan, seperti sumber pembersihan; Parameter dinilai dengan serta-merta apabila menangguhkan, dan fungsi-fungsi dilaksanakan mengikut urutan terakhir (LIFO); 1. Pelbagai penahanan dilaksanakan dalam urutan terbalik pengisytiharan; 2. Biasanya digunakan untuk pembersihan yang selamat seperti penutupan fail; 3. Nilai pulangan yang dinamakan boleh diubah suai; 4. Ia akan dilaksanakan walaupun panik berlaku, sesuai untuk pemulihan; 5. Elakkan penyalahgunaan menangguhkan gelung untuk mengelakkan kebocoran sumber; Penggunaan yang betul boleh meningkatkan keselamatan kod dan kebolehbacaan.

Memilih jenis htmlinput yang betul dapat meningkatkan ketepatan data, meningkatkan pengalaman pengguna, dan meningkatkan kebolehgunaan. 1. Pilih jenis input yang sepadan mengikut jenis data, seperti teks, e -mel, tel, nombor dan tarikh, yang secara automatik boleh menyemak dan menyesuaikan diri dengan papan kekunci; 2. Gunakan HTML5 untuk menambah jenis baru seperti URL, Warna, Julat dan Carian, yang dapat memberikan kaedah interaksi yang lebih intuitif; 3. Gunakan pemegang tempat dan sifat -sifat yang diperlukan untuk meningkatkan kecekapan dan ketepatan pengisian bentuk, tetapi harus diperhatikan bahawa pemegang tempat tidak dapat menggantikan label.
