利用ContentProvider實現同步Binder
在Android中跨程序通訊的方式有好多種,比如
- Intent
- Messenger
- AIDL(Android 介面定義語言)
- ContentProvider
- Socket
以AIDL為例,在使用AIDL實現安卓跨程序通訊的時候,通常分為3步:
- 定義AIDL介面檔案,在
Service
的onBind
方法中返回binder
給客戶端 - 客戶端與服務端繫結,在回撥函式
onServiceConnected
中獲取binder
- 通過
Stub
的asInterface
方法轉換為我們定義的介面,然後呼叫服務端邏輯。
這是一種典型的CS(客戶端-服務端)架構。下面我們就用AIDL來實現跨程序通訊,首先我們來定義一個問題:
假如小王是一家連鎖超市的老闆,他最關心的是自己的超市目前的規模以及自己超市的營業額的情況。他是需要服務的一端,所以把小王定義為客戶端。針對老闆的需求,我們需要提供兩個服務,一是查詢連鎖超市的數量而是查詢超市的營業額。
既然需求有了,現在我們就來實現它:
客戶端就定義一個BossActivity
用於顯示連鎖超市目前的規模以及營業額。
服務端為了解耦就定義兩個Service
,OrderService
(查詢營業額)和StoreService
(查詢超市規模)
接下來按照上面的三步走,我們依次來實現一下
1. 定義AIDL
- 定義
IOrderService.aidl
package qiwoo.android.sync.binder;
interface IOrderService {
// 獲取營業額
int getOrderAmount();
}
- 定義
IStoreService.aidl
包括查詢超市規模的的服務,其實就是store的數量
package qiwoo.android.sync.binder; import qiwoo.android.sync.binder.Store; interface IStoreService { // 獲取超市的列表 List<Store> getStores(); }
2. 在Service中實現介面並作為binder返回
OrderService
具體實現如下
public class OrderService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
IOrderService.Stub mOrderService = new IOrderService.Stub() {
@Override
public int getOrderAmount() throws RemoteException {
// 方便演示這裡就不涉及過多邏輯,簡單返回資料
return 100;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回 OrderService binder
return mOrderService;
}
}
StoreService
具體實現如下
public class StoreService extends Service {
private List<Store> stores;
@Override
public void onCreate() {
super.onCreate();
// 方便演示這裡就不涉及過多邏輯,簡單建立資料
Store store1 = new Store(1, "qiwoo", "123", "beijing");
Store store2 = new Store(2, "mobile", "123", "beijing");
stores = new ArrayList<>();
stores.add(store1);
stores.add(store2);
}
IStoreService.Stub mStoreService = new IStoreService.Stub() {
@Override
public List<Store> getStores() throws RemoteException {
return stores;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 返回 StoreService binder
return mStoreService;
}
}
3. 在BossActivity
中 bindService
- 繫結服務
Intent orderIntent = new Intent();
orderIntent.setClass(this, OrderService.class);
bindService(orderIntent, mOrderServiceConnection, Context.BIND_AUTO_CREATE);
Intent storeIntent = new Intent();
storeIntent.setClass(this, StoreService.class);
bindService(storeIntent, mStoreServiceConnection, Context.BIND_AUTO_CREATE);
- 傳入的ServiceConnection
private ServiceConnection mOrderServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 在這裡,繫結成功之後,我們就拿到了binder
mOrderService = IOrderService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
- 呼叫遠端服務
try {
// 在 onServiceConnected 中拿到的binder
int amount = mOrderService.getOrderAmount();
Toast.makeText(BossActivity.this, "恭喜老闆營業額是:" + amount + " 億", Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
到目前為止,一切都很順利。我們通過AIDL實現了跨程序通訊,來測試一下結果和我們預期的也一樣。
非常棒,老闆很有錢,晚上又可以給我們加雞腿了。但是有沒有發現一個問題:在BossActivity
中想要呼叫另一個程序的服務,必須要等 bindService
中傳入的ServiceConnection
拿到onServiceConnected
的回撥才能使用,也就是我們非同步的獲取了binder
。有的時候我們並不想這樣做,有沒有一個辦法可以直接通過一個get
方法就拿到binder
呢?
答案當然是肯定的啦,現在我們再回到文章開頭看看實現跨程序通訊中常見的幾種方式,有一個ContentProvider
接下來它就是我們的主角了,對ContentProvider
不熟的同學可以去查一下它的用法。我們就通過它來實現在客戶端同步獲取binder
,怎麼去做呢,同樣三步走。
- 定義AIDL介面檔案和實現類
- 定義一個
ContentProvider
根據查詢引數的不同返回具體的服務binder
- 查詢
ContentProvider
獲得Cursor
然後通過Stub
的asInterface
方法轉換為我們定義的介面,然後呼叫服務端邏輯。
1. 定義AIDL和實現類
AIDL和上面完全一樣,不再重複
OrderServiceImpl
實現
public class OrderServiceImpl extends IOrderService.Stub {
@Override
public int getOrderAmount() throws RemoteException {
return 100;
}
}
StoreServiceImpl
實現
public class StoreServiceImpl extends IStoreService.Stub {
private List<Store> stores;
public StoreServiceImpl() {
Store store1 = new Store(1, "qiwoo", "123", "beijing");
Store store2 = new Store(2, "mobile", "123", "beijing");
stores = new ArrayList<>();
stores.add(store1);
stores.add(store2);
}
@Override
public List<Store> getStores() throws RemoteException {
return stores;
}
}
2. 定義BinderProvider
主要程式碼如下:
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
IBinder binder;
if (selectionArgs[0].equals(SERVICE_ORDER)) {
binder = new OrderServiceImpl();
Log.d(TAG, "Query OrderServiceImpl");
} else if (selectionArgs[0].equals(SERVICE_STORE)) {
binder = new StoreServiceImpl();
Log.d(TAG, "Query StoreServiceImpl");
} else {
return null;
}
BinderCursor cursor = new BinderCursor(new String[]{"service"}, binder);
return cursor;
}
ContentProvider
的query
方法返回的是一個Cursor
,現在的場景不像查詢資料庫一樣可以通過SQLiteDatabase
的query
方法直接返回一個Cursor
,而Cursor
又是一個介面,沒有辦法直接例項化。所以我們需要找一個可以例項化一個Cursor
,這裡用到了MatrixCursor
。有了Cursor
之後就可以把根據查詢引數的不同我們返回了不同的binder
放到Cursor
中返回。下面我們來看一下BinderCursor
public class BinderCursor extends MatrixCursor {
static final String KEY_BINDER = "binder";
Bundle mBinderExtra = new Bundle();
public static class BinderParcelable implements Parcelable {
public IBinder mBinder;
public static final Creator<BinderParcelable> CREATOR = new Creator<BinderParcelable>() {
@Override
public BinderParcelable createFromParcel(Parcel source) {
return new BinderParcelable(source);
}
@Override
public BinderParcelable[] newArray(int size) {
return new BinderParcelable[size];
}
};
BinderParcelable(IBinder binder) {
mBinder = binder;
}
BinderParcelable(Parcel source) {
mBinder = source.readStrongBinder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(mBinder);
}
}
public BinderCursor(String[] columnNames, IBinder binder) {
super(columnNames);
if (binder != null) {
Parcelable value = new BinderParcelable(binder);
mBinderExtra.putParcelable(KEY_BINDER, value);
}
}
@Override
public Bundle getExtras() {
return mBinderExtra;
}
}
可以看到它繼承自MatrixCursor
,然後通過Bundle
包裝了binder
,這樣就可以 new
一個 MatrixCursor
的物件返回了。
3. 查詢ContentProvider
獲得cursor
呼叫服務端邏輯。
final ContentResolver resolver = MainActivity.this.getContentResolver();
final Cursor cu = resolver.query(CONTENT_URI, null, null, new String[]{SERVICE_ORDER}, null);
if (cu == null) {
return;
}
IBinder binder = getBinder(cu);
try {
IOrderService orderService = IOrderService.Stub.asInterface(binder);
int amount = orderService.getOrderAmount();
Toast.makeText(MainActivity.this, "恭喜老闆營業額是:" + amount + " 億", Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
cu.close();
這裡,我們為了獲得營業額和超市規模的資料傳入的查詢引數是SERVICE_ORDER
和SERVICE_STORE
,假如有很多個服務就可以把這部分程式碼再進行封裝,寫一個管理類,根據不同的引數返回不同的service。
這樣我們獲取binder
就是同步的了,不需要再等待回撥,query
出來直接使用。列印的結果和上面是一樣的,不再展示。