Lets say I have a SDK in form of Android Library (aar) that offers some basic media processing (it has its own UI as a single activity). Currently, any client Android app, when invoking my SDK sends required data via Bundle.
Now, for various reasons some extra info for the data being sent may be required after my SDK is invoked so I would need a two-way communication with the caller app.
In short, from within the SDK I need to be able to check if the client app has implemented some interface so that SDK can use it to communicate with the client app (the client may choose not to provide the implementation in which case the SDK will fallback to internal, the default implementation..).
Anyway, the way I've done it initialy, is as following:
Within SDK I have exposed the data provider interface:
public interface ISDKDataProvider {
void getMeSomething(Params param, Callback callback);
SomeData getMeSomethingBlocking(Params param);
}
a Local binder interface that should return an instance of the implemented interface:
public interface LocalBinder {
ISDKDataProvider getService();
}
Then, on the client side, an application using the SDK, must provide a service that does the job and implements those interfaces:
public class SDKDataProviderService extends Service implements ISDKDataProvider {
private final IBinder mBinder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void getMeSomething(Params param, Callback callback) {
// ... do something on another thread
// once done, invoke callback and return result to the SDK
}
@Override
public SomeData getMeSomethingBlocking(Params param);{
// do something..
// return SomeData
}
public class MyBinder extends Binder implements LocalBinder {
@Override
public ISDKDataProvider getService() {
return ISDKDataProvider.this;
}
}
}
Additionally, when invoking SDK, the clinet app passes the ComponentName via bundle options:
sdkInvokationOptions.put("DATA_PROVIDER_EXTRAS", new ComponentName(getPackageName(), SDKDataProviderService.class.getName()));
..from the SDK, I then check whether the service exists and whether we can bind to it:
final ComponentName componentName = // get passed componentname "DATA_PROVIDER_EXTRAS"
if (componentName != null) {
final Intent serviceIntent = new Intent(componentName.getClassName());
serviceIntent.setComponent(componentName);
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
}
where mConnection is:
private boolean mBound;
private ISDKDataProvider mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
This seem to work ok and it looks clean but my question is there a better way\practice to accomplish the same type of a communication?
Your API should be simple, for example a static class/singleton:
MyAPI.start()
MyAPI.stop()
MyAPI.sendMessgage(mgs,callback)
MyAPI.setCallback(callback)
About the service, I think you should decide who is in charge of it.
If its the user - leave him the implementation, just give the API.
If you always want your API to run as a service, implement it yourself and inside the singleton handle the messaging (you can do so with intents, for example).
I used this architecture for image processing service too :)
My API wrapping class looked like:
public class MyAPI {
public static final String TAG = "MyAPI";
public MyAPI() {
}
public static MyAPI.Result startMyAPI(ScanParams scanParams) {
try {
Log.d("MyAPI", "in startMyAPI");
if (scanParams.ctx == null || scanParams.appID == null || scanParams.api_key == null) {
Log.d("MyAPI", "missing parameters");
return MyAPI.Result.FAILED;
}
if (scanParams.userID == null) {
scanParams.userID = "no_user";
}
if (scanParams.minBatteryThreshold == null) {
scanParams.minBatteryThreshold = Consts.DEFAULT_BATTERY_THRESHOLD;
}
if (scanParams.minCpuThreshold == null) {
scanParams.minCpuThreshold = Consts.DEFAULT_CPU_THRESHOLD;
}
if (!DeviceUtils.checkBatteryLevel(scanParams.ctx, (float)scanParams.minBatteryThreshold)) {
ReportUtils.error("low battery");
return MyAPI.Result.FAILED;
}
if (MyAPIUtils.isRunning(scanParams.ctx)) {
return MyAPI.Result.FAILED;
}
Intent intent = new Intent(scanParams.ctx, MyAPIService.class);
ServiceParams serviceParams = new ServiceParams(scanParams.appID, scanParams.api_key, scanParams.userID, scanParams.minBatteryThreshold, scanParams.minCpuThreshold);
intent.putExtra("SERVICE_PARAMS", serviceParams);
scanParams.ctx.startService(intent);
} catch (Exception var3) {
var3.printStackTrace();
}
return MyAPI.Result.SUCCESS;
}
public static void getBestCampaignPrediction(Context ctx, String apiKey, String appID, String creativeID, AppInterface appInterface) {
try {
String deviceID = DeviceUtils.getDeviceID(ctx);
GetBestCampaignTask getBestCampaignTask = new GetBestCampaignTask(ctx, apiKey, deviceID, appID, creativeID, appInterface);
getBestCampaignTask.execute(new Void[0]);
} catch (Exception var7) {
var7.printStackTrace();
}
}
public static boolean sendAdEvent(Context ctx, String apiKey, Event event) {
boolean res = false;
try {
boolean isValid = Utils.getIsValid(ctx);
if (isValid) {
Long timeStamp = System.currentTimeMillis();
event.setTimeStamp(BigDecimal.valueOf(timeStamp));
event.setDeviceID(DeviceUtils.getDeviceID(ctx));
(new SendEventTask(ctx, apiKey, event)).execute(new Void[0]);
}
} catch (Exception var6) {
var6.printStackTrace();
}
return res;
}
public static enum PredictionLevel {
MAIN_CATEGORY,
SUB_CATEGORY,
ATTRIBUTE;
private PredictionLevel() {
}
}
public static enum Result {
SUCCESS,
FAILED,
LOW_BATTERY,
LOW_CPU,
NOT_AUTHENTICATED;
private Result() {
}
}
}
You can see that startMyAPI actually starts a service and getBestCampaignPrediction runs an async task that communicates with the service behind the scenes and returns its result to appInterface callback. This way the user get a very simple API