這一篇記錄 AIDL(Android Interface Definition Language,安卓接口定義語言)。
AIDL 默認支持的數據類型包括:
- Java中的八種基本數據類型,包括 byte,short,int,long,float,double,boolean,char。
- String 類型。
- CharSequence類型。
- List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。List可以使用泛型。
- Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。Map是不支持泛型的。
AIDL接口的創建
兩個aidl文件// Book.aidl
package com.kevin.testaidl;
parcelable Book;
// IBookManager.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;//這裏一定要手動導入
interface IBookManager {
void addBook(in Book b);
List<Book> getBookList();
}
代碼中使用了自定義的對象Book,一定要在aidl文件中手動導入進來。同時一定要新建一個同名的aild文件,Book.aidl.
AIDL已經定義好了,找到Android Studio的make project進行編譯,會在generatedJava裏面
生成IBookManager。
服務端Service
/**
* Created by Kevin on 2019/3/21<br/>
* Blog:https://blog.csdn.net/student9128<br/>
* Describe:<br/>
* 服務端
*/
public class AIDLService extends Service {
private final String TAG = this.getClass().getSimpleName();
// private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步
private IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public void addBook(Book b) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
// mBooks = new ArrayList<Book>();
mBooks = new CopyOnWriteArrayList<>();
}
if (b == null) {
Log.e(TAG, "Book is null in In");
b = new Book();
}
b.setPrice(123);//在服務端修改了book的價格
if (!mBooks.contains(b)) {
mBooks.add(b);
}
//打印mBooks列表,觀察客戶端傳過來的值
StringBuilder sb = new StringBuilder();
for (Book boo : mBooks) {
sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
}
Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
}
}
@Override
public List<Book> getBookList() throws RemoteException {
// SystemClock.sleep(10000);
synchronized (this) {
if (mBooks != null) {
return mBooks;
}
}
return new CopyOnWriteArrayList<>();
}
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android ADIL 測試");
book.setPrice(33);
mBooks.add(book);
Log.w(TAG, "onCreate");
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
return mBookManager;
}
}
在onCrate中初始化添加了一本圖書信息,然後創建了一個Binder對象mBookManager並在onBind中返回它,這個對象實現了IBookManager.Stub內部的ADIL方法——addBook和getBookList方法。 可以看到addBook方法就是將添加的Book修改了price,添加到mBooks集合中,mBooks是CopyOnWriteArrayList對象,CopyOnWriteArrayList支持併發讀寫,保證多個客戶端連接是,線程同步。
清單文件配置:
<service
android:name=".AIDLService"
android:exported="true"
android:permission="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE"
android:process=":remote">
<intent-filter>
<action android:name="com.kevin.aidl" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
客戶端
客戶端綁定遠程服務,綁定成功後將服務端返回的Binder對象轉換成AIDL接口,然後通過這個接口去調用服務端的遠程方法。
public class MainActivity extends AppCompatActivity {
private IBookManager mBookManager = null;
private boolean mBound = false;
private List<Book> mBooks;
private final String TAG = getClass().getSimpleName();
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, AIDLService.class);
bindService(intent, mSC, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mSC);
super.onDestroy();
}
private ServiceConnection mSC = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(getLocalClassName(), "service connected");
mBookManager = IBookManager.Stub.asInterface(service);
if (mBookManager != null) {
try {
Book book = new Book();
book.setPrice(99);
book.setName("Test");
mBookManager.addBook(book);
mBooks = mBookManager.getBookList();
for (Book b : mBooks) {
Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(getLocalClassName(), "service disconnected");
}
};
}
可以看到,總共添加了兩本書,第一本是在服務端添加的,第二本是在客戶端添加的。
現在增加一種場景,用戶不希望每次都手動去獲取服務端增加的新書,而是希望,每次一有新書到來,用戶可以收到相關提醒。
添加一個aidl接口,aidl無法使用普通接口
// IOnNewBookArrivedListener.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;
// Declare any non-default types here with import statements
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.kevin.testaidl;
import com.kevin.testaidl.Book;
import com.kevin.testaidl.IOnNewBookArrivedListener;
// Declare any non-default types here with import statements
interface IBookManager {
void addBook(in Book b);
List<Book> getBookList();
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
服務端代碼修改:
public class AIDLService extends Service {
private final String TAG = this.getClass().getSimpleName();
// private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步
//使用RemoteCallbackList存儲 listener,這樣可以解註冊
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public void addBook(Book b) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
// mBooks = new ArrayList<Book>();
mBooks = new CopyOnWriteArrayList<>();
}
if (b == null) {
Log.e(TAG, "Book is null in In");
b = new Book();
}
b.setPrice(123);
if (!mBooks.contains(b)) {
mBooks.add(b);
}
//打印mBooks列表,觀察客戶端傳過來的值
StringBuilder sb = new StringBuilder();
for (Book boo : mBooks) {
sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
}
Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
}
}
@Override
public List<Book> getBookList() throws RemoteException {
// SystemClock.sleep(10000);
synchronized (this) {
if (mBooks != null) {
return mBooks;
}
}
return new CopyOnWriteArrayList<>();
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android ADIL 測試");
book.setPrice(33);
mBooks.add(book);
Log.w(TAG, "onCreate");
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
return mBookManager;
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBooks.add(book);
int N = mListenerList.beginBroadcast();
Log.d(TAG, "onNewBookArrived,notify listeners:" + N);
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookArrived(book);
}
Log.d(TAG, "onNewBookArrived,notify listener:" + listener);
mListenerList.finishBroadcast();
}
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBooks.size() + 1;
Book newBook = new Book(bookId, "new book#" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
從代碼中可以看到,創建了一個 RemoteCallbackList<>對象用來存儲IOnNewBookArrivedListener。(RemoteCallbackList是系統專門提供的專門用於刪跨進程listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口。
public class RemoteCallbackList<E extends IInterface>
從聲明中可以看出,所以的ADIL接口都繼承自IInterface。)
實現了IBookManager.Stub內部的registerListener,unRegisterListener在這兩個方法中去實現listener的註冊和解註冊。
同時添加了一個onNewBookArrived
方法,當有新書的時候,添加到mBooks集合,並調用IOnNewBookArrivedListener的onNewBookArrived進行通知用戶,新書到了。
注意這裏需要使用beginBroadcast
和finishBroadcast
。
客戶端代碼修改:
public class MainActivity extends AppCompatActivity {
private IBookManager mBookManager = null;
private boolean mBound = false;
private List<Book> mBooks;
private final String TAG = getClass().getSimpleName();
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "receive new book: " + msg.obj);
break;
default:
super.handleMessage(msg);
break;
}
}
};
private DeathRecipient deathRecipient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//開子線程防止服務器端進行耗時操作,app出現ANR
new Thread(new Runnable() {
@Override
public void run() {
try {
if (mBookManager != null) {
List<Book> bookList = mBookManager.getBookList();
for (Book b : bookList) {
Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
}
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
});
Intent intent = new Intent(this, AIDLService.class);
deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null) {
return;
}
mBookManager.asBinder().unlinkToDeath(deathRecipient, 0);
mBookManager = null;
attemptToBindService();//Binder die後,重新綁定服務
}
};
bindService(intent, mSC, Context.BIND_AUTO_CREATE);
}
public void addBook() {
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "當前與服務的處於未連接狀態,正在嘗試連", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null)
return;
Book book = new Book();
book.setName("App研發錄");
book.setPrice(30);
try {
mBookManager.addBook(book);
Log.d(getLocalClassName(), "添加的課本:name:" + book.getName() + "\tprice:" + book.getPrice());
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.kevin.aidl");
intent.setPackage("com.kevin.testaidl");
bindService(intent, mSC, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if (mBookManager != null && mBookManager.asBinder().isBinderAlive()) {
try {
Log.i(TAG, "unregister listner:" + mOnNewBookArrivedListener);
mBookManager.unRegisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mSC);
super.onDestroy();
}
private ServiceConnection mSC = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(getLocalClassName(), "service connected");
try {
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mBookManager = IBookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
Book book = new Book();
book.setPrice(99);
book.setName("Test");
mBookManager.addBook(book);
mBooks = mBookManager.getBookList();
for (Book b : mBooks) {
Log.d(TAG, "已存在的書本:名字:" + b.getName() + "價格:" + b.getPrice());
}
mBookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(getLocalClassName(), "service disconnected");
mBound = false;
}
};
//運行中Binder線程池中,不能在它裏面訪問UI相關的內容,如果要訪問,使用Handler切換到UI線程
// Binder可能會意外死亡,使用DeathRecipient監聽,當Binder死亡是binderDied方法中重新連接服務
//或者在onServiceDisconnected重新連接服務
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
//
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
}
};
}
可以看到代碼中爲了防止Binder以外死亡,使用了DeathRecipient,當Binder死亡了,可以在binderDied方法中監聽到,重新連接服務。
在使用進程間通信的過程中,還有一點是要去驗證應用的權限,這裏可以爲服務自己定義一個權限,在使用的過程中在清單文件去聲明下這個權限,服務端可以進行權限驗證。
兩種方法,
-
在onBind方法中驗證權限,如果驗證失敗,返回null
@Override public IBinder onBind(Intent intent) { //在這裏校驗權限,如果客戶端沒有聲明權限會一直連接不上,但是不知道爲什麼,不會有提示 int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE"); if (i == PackageManager.PERMISSION_DENIED) { return null; } Log.i(TAG, String.format("on bind,intent=%s", intent.toString())); return mBookManager; }
-
在服務端的onTransact方法中進行權限驗證,如果失敗就返回false,這樣服務端就不會終止執行AIDL中的方法從而達到保護服務端的效果。
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//驗證權限
int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
if (i == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "權限不被允許");
return false;
}
//驗證包名
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (!packageName.startsWith("com.kevin.testaidl")) {
Log.i(TAG, "包名不對");
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
清單文件的配置
服務端:
<permission
android:name="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="signature" />
客戶端:
<uses-permission android:name="com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE" />
最後貼一下服務端的完整代碼:
/**
* Created by Kevin on 2019/3/21<br/>
* Blog:https://blog.csdn.net/student9128<br/>
* Describe:<br/>
* 服務端
*/
public class AIDLService extends Service {
private final String TAG = this.getClass().getSimpleName();
// private List<Book> mBooks = new ArrayList<>();//aidl只支持arrayList
private CopyOnWriteArrayList<Book> mBooks = new CopyOnWriteArrayList<>();//保證線程同步
//使用RemoteCallbackList存儲 listener,這樣可以解註冊
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public void addBook(Book b) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
// mBooks = new ArrayList<Book>();
mBooks = new CopyOnWriteArrayList<>();
}
if (b == null) {
Log.e(TAG, "Book is null in In");
b = new Book();
}
b.setPrice(123);
if (!mBooks.contains(b)) {
mBooks.add(b);
}
//打印mBooks列表,觀察客戶端傳過來的值
StringBuilder sb = new StringBuilder();
for (Book boo : mBooks) {
sb.append("book: name is " + boo.getName() + ",price is" + boo.getPrice());
}
Log.e(TAG, "invoking addBooks() method , now the list is : " + sb.toString());
}
}
@Override
public List<Book> getBookList() throws RemoteException {
// SystemClock.sleep(10000);
synchronized (this) {
if (mBooks != null) {
return mBooks;
}
}
return new CopyOnWriteArrayList<>();
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
// if (!mListenerList.contains(listener)) {
// mListenerList.add(listener);
// }else {
// Log.d(TAG, "listener already exists...");
// }
// Log.d(TAG, "registerListener,size:" + mListenerList.size());
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
// if (mListenerList.contains(listener)) {
// mListenerList.remove(listener);
// } else {
// Log.d(TAG, "not found the listener,cannot unregister");
// }
// Log.d(TAG, "unRegisterListener,current size :" + mListenerList.size());
mListenerList.unregister(listener);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//驗證權限
int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
if (i == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "權限不被允許");
return false;
}
//驗證包名
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (!packageName.startsWith("com.kevin")) {
Log.i(TAG, "包名不對");
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android ADIL 測試");
book.setPrice(33);
mBooks.add(book);
Log.w(TAG, "onCreate");
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
//在這裏校驗權限,如果客戶端沒有聲明權限會一直連接不上,但是不知道爲什麼,不會有提示
// int i = checkCallingOrSelfPermission("com.kevin.testaidl.permission.ACCESS_BOOK_SERVICE");
// if (i == PackageManager.PERMISSION_DENIED) {
// return null;
// }
Log.i(TAG, String.format("on bind,intent=%s", intent.toString()));
return mBookManager;
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBooks.add(book);
int N = mListenerList.beginBroadcast();
Log.d(TAG, "onNewBookArrived,notify listeners:" + N);
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookArrived(book);
}
Log.d(TAG, "onNewBookArrived,notify listener:" + listener);
mListenerList.finishBroadcast();
}
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBooks.size() + 1;
Book newBook = new Book(bookId, "new book#" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
注:本文章知識點來自學習《Android開發藝術探索》一書