You need to pass data to another app so that it can perform some operation on that data and return a result back to your app. You don’t want to give up control flow to this other application; you want to interact with it unseen.
The key technology is to use an Android Service.remote procedure call (RPC).
the ImageMash appmust expose a Service that can be called by the GoodShares app’s Activity.
synchronously. The GoodShares app calls the ImageMash Service and waits for a response.After receiving a response, GoodShares updates the UI. To enable this kind of interaction, the GoodShares Activity must bind to the Service and directly invoke an operation on the Service.
To share data with another app’s Service in a synchronous manner; your app must have the AIDL that describes that Service. Similarly, if you want to allow other apps to integrate with a Service in your app, you must provide an AIDL.
create ShareRpcActivity class:
public class ShareRpcActivity extends Activity {
Uri photoUri0;
IMashService mashService;//generated interface representing remote service
Button mashButton;
int bindCount=0;
ServiceConnection conn=new ServiceConnection(){
@Override
//callback once service is bound
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
mashService=IMashService.Stub.asInterface(service);
mashButton.setEnabled(true);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
mashService=null;
mashButton.setEnabled(false);
}
};
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.share_rpc);
Button button=(Button)findViewById(R.id.btn0);
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent request=new Intent(Intent.ACTION_GET_CONTENT);
request.setType("image/*");
startActivityForResult(request,0);
}
});
mashButton=(Button)findViewById(R.id.button);
CheckBox syncBox=(CheckBox)findViewById(R.id.syncBox);
syncBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// TODO Auto-generated method stub
if(isChecked){
mashButton.setEnabled(false);
//bind to remote service
bindService(new Intent("example.mash.ACTION"),conn,Context.BIND_AUTO_CREATE);
bindCount++;
}else{
bindCount--;
if(bindCount==0){
unbindService(conn);
}
mashButton.setEnabled(true);
}
}
});
mashButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
EditText input0 = (EditText) findViewById(R.id.input0);
float scaleX = Float.parseFloat(input0.getText().toString());
EditText input1 = (EditText) findViewById(R.id.input1);
float scaleY = Float.parseFloat(input1.getText().toString());
EditText input2 = (EditText) findViewById(R.id.input2);
float angle = Float.parseFloat(input2.getText().toString());
Uri result;
if(bindCount>0){
try{
//synchronous
//invoke remote service
result=mashService.mash(photoUri0, scaleX, scaleY, angle);
//use result to update UI
ImageView image=(ImageView)findViewById(R.id.image);
image.setImageURI(result);
}catch(RemoteException e){
e.printStackTrace();
}
}else{
//asynchronous
Intent request=new Intent("example.mash.ACTION");
request.putExtra("example.mash.EXTRA_PHOTO", photoUri0);
request.putExtra("example.mash.EXTRA_SCALE_X", scaleX);
request.putExtra("example.mash.EXTRA_SCALE_Y", scaleY);
request.putExtra("example.mash.EXTRA_ANGLE", angle);
startService(request);
/*
* The Service will receive it and
* respond by broadcasting an Intent with the response data in it.
*/
}
}
});
mashButton.setEnabled(true);
//implement BroadcastReceiver
BroadcastReceiver receiver=new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
//pull data from extras
Uri result=intent.getParcelableExtra("example.mash.EXTRA_RESULT");
ImageView image=(ImageView)findViewById(R.id.image);
image.setImageURI(result);
}
};
IntentFilter filter=new IntentFilter();
//the action that will be used by Service
filter.addAction("example.mash.ACTION_RESPONSE");
this.registerReceiver(receiver, filter);//register with IntentFilter
}
protected void onActivityResult(int requestCode, int resultCode, Intent data ){
if(requestCode==0){
photoUri0=(Uri)data.getParcelableExtra(Intent.EXTRA_STREAM);
if(photoUri0==null&&data.getData()!=null){
photoUri0=data.getData();
}
ImageView imgView0=(ImageView)findViewById(R.id.pic0);
imgView0.setImageURI(photoUri0);
}
}
public static void start(Context context){
Intent i=new Intent(context,ShareRpcActivity.class);
context.startActivity(i);
}
}
create IMashService.aidl
package example.mash;
import android.net.Uri;
interface IMashService{
Uri mash(in Uri uri, float scaleX, float scaleY, float angle);
}
create MashService class in ImageMash app:
public class MashService extends Service {
private IMashService.Stub stub;
public int onStartCommand(Intent intent,int flags, int startId) {
Uri imageUri=intent.getParcelableExtra("example.mash.EXTRA_PHOTO");
float scaleX=intent.getFloatExtra("example.mash.EXTRA_SCALE_X",1.0f);
float scaleY=intent.getFloatExtra("example.mash.EXTRA_SCALE_Y",1.0f);
float angle=intent.getFloatExtra("example.mash.EXTRA_ANGLE", 0.0f);
try{
Uri resultUri=stub.mash(imageUri, scaleX, scaleY, angle);
//put the appropriate action on the intent
Intent response=new Intent("example.mash.EXTRA_RESPONSE");
response.putExtra("example.mash.EXTRA_RESULT", resultUri);
sendBroadcast(response);//broadcast the intent
}catch(RemoteException e){
Log.e("MashService", "Exception mashing image async", e);
}
return START_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
In this case, only one type of Intent is being sent in, but if there were more than one then you could check what the action is to figure out what kind of request it is. Once you know the request type, you can pull out the appropriate data from the Intent.
In the synchronous mode, the interface is explicit and defined—in the AIDL. You know exactly how to call the Service and the response is immediate (in the sense that your thread will block until the Service gets a response—be careful about doing this on the main UI thread). In the asynchronous case, nothing is as explicit. You still must know the names and types of the data that the Service expects and produces, but this doesn’t come in the form of code (AIDL). This arrangement can be more error prone. Furthermore, you also need to know the name of the action to use to send it to the Service, as well as the name of the action to use to register a BroadcastReceiver to get the response back from the Service.
Synchronous and asynchronous don’t need to be mutually exclusive. For example, let’s say you expose a Service for synchronous usage via AIDL. But suppose that one
of your operations could take a long time. Now the Activity that binds to the Service could do so from an AsyncTask or similar, so that the UI thread isn’t blocked while your Service does all of its work. So it might be okay for this operation to take a long time. But you could alternatively return a message saying that request was received, but that some or all of the response will come later. Then your Service could broadcast an Intent with more data later on as it becomes available.
This is a common technique to use if your Service contains a local cache of data that’s ultimately stored somewhere in the cloud.