1. 程式人生 > >利用ContentProvider實現同步Binder

利用ContentProvider實現同步Binder

在Android中跨程序通訊的方式有好多種,比如
  • Intent
  • Messenger
  • AIDL(Android 介面定義語言)
  • ContentProvider
  • Socket
以AIDL為例,在使用AIDL實現安卓跨程序通訊的時候,通常分為3步:
  1. 定義AIDL介面檔案,在ServiceonBind方法中返回binder給客戶端
  2. 客戶端與服務端繫結,在回撥函式onServiceConnected中獲取binder
  3. 通過StubasInterface方法轉換為我們定義的介面,然後呼叫服務端邏輯。

這是一種典型的CS(客戶端-服務端)架構。下面我們就用AIDL來實現跨程序通訊,首先我們來定義一個問題:

假如小王是一家連鎖超市的老闆,他最關心的是自己的超市目前的規模以及自己超市的營業額的情況。他是需要服務的一端,所以把小王定義為客戶端。針對老闆的需求,我們需要提供兩個服務,一是查詢連鎖超市的數量而是查詢超市的營業額。

既然需求有了,現在我們就來實現它:

客戶端就定義一個BossActivity用於顯示連鎖超市目前的規模以及營業額。
服務端為了解耦就定義兩個ServiceOrderService(查詢營業額)和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. 在BossActivitybindService

  • 繫結服務
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然後通過StubasInterface方法轉換為我們定義的介面,然後呼叫服務端邏輯。

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;
}

ContentProviderquery方法返回的是一個Cursor,現在的場景不像查詢資料庫一樣可以通過SQLiteDatabasequery方法直接返回一個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_ORDERSERVICE_STORE,假如有很多個服務就可以把這部分程式碼再進行封裝,寫一個管理類,根據不同的引數返回不同的service。

這樣我們獲取binder就是同步的了,不需要再等待回撥,query出來直接使用。列印的結果和上面是一樣的,不再展示。

本文demo地址: github.com/77Y/SyncBinder