I am trying to work on sending an object of my customer class from one Activity
and display it in another Activity
.
The code for t
I found a simple & elegant method:
Code for the first activity:
final Object objSent = new Object();
final Bundle bundle = new Bundle();
bundle.putBinder("object_value", new ObjectWrapperForBinder(objSent));
startActivity(new Intent(this, SecondActivity.class).putExtras(bundle));
Log.d(TAG, "original object=" + objSent);
Code for the second activity:
final Object objReceived = ((ObjectWrapperForBinder)getIntent().getExtras().getBinder("object_value")).getData();
Log.d(TAG, "received object=" + objReceived);
you will find objSent
& objReceived
have the same hashCode
, so they are identical.
But why can we pass a java object in this way?
Actually, android binder will create global JNI reference for java object and release this global JNI reference when there are no reference for this java object. binder will save this global JNI reference in the Binder object.
*CAUTION: this method ONLY work unless the two activities run in the same process, otherwise throw ClassCastException at (ObjectWrapperForBinder)getIntent().getExtras().getBinder("object_value") *
class ObjectWrapperForBinder defination
public class ObjectWrapperForBinder extends Binder {
private final Object mData;
public ObjectWrapperForBinder(Object data) {
mData = data;
}
public Object getData() {
return mData;
}
}
But Method 2 has a little but serious issue, if the receiver fail to restore the java object (for example, some exception happen before restore the java object, or the receiver Activity does not exist at all), then the java object will become an orphan or memory leak, Method 1 don't have this issue, because android binder will handle this exception
To invoke the java object remotely, we will create a data contract/interface to describe the java object, we will use the aidl file
IDataContract.aidl
package com.example.objectwrapper;
interface IDataContract {
int func1(String arg1);
int func2(String arg1);
}
Code for the first activity
final IDataContract objSent = new IDataContract.Stub() {
@Override
public int func2(String arg1) throws RemoteException {
// TODO Auto-generated method stub
Log.d(TAG, "func2:: arg1=" + arg1);
return 102;
}
@Override
public int func1(String arg1) throws RemoteException {
// TODO Auto-generated method stub
Log.d(TAG, "func1:: arg1=" + arg1);
return 101;
}
};
final Bundle bundle = new Bundle();
bundle.putBinder("object_value", objSent.asBinder());
startActivity(new Intent(this, SecondActivity.class).putExtras(bundle));
Log.d(TAG, "original object=" + objSent);
Code for the second activity:
change the android:process attribute in AndroidManifest.xml to a non-empty process name to make sure the second activity run in another process
final IDataContract objReceived = IDataContract.Stub.asInterface(getIntent().getExtras().getBinder("object_value"));
try {
Log.d(TAG, "received object=" + objReceived + ", func1()=" + objReceived.func1("test1") + ", func2()=" + objReceived.func2("test2"));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
In this way, we can pass an interface between two activities even though they run in different process, and call the interface method remotely
method 3 seem not simple enough because we must implement an aidl interface. If you just want to do simple task and the method return value is unnecessary, we can use android.os.Messenger
Code for the first activity( sender):
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
public static final int MSG_OP1 = 1;
public static final int MSG_OP2 = 2;
public static final String EXTRA_MESSENGER = "messenger";
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
Log.e(TAG, "handleMessage:: msg=" + msg);
switch (msg.what) {
case MSG_OP1:
break;
case MSG_OP2:
break;
default:
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startActivity(new Intent(this, SecondActivity.class).putExtra(EXTRA_MESSENGER, new Messenger(mHandler)));
}
}
Code for the second activity ( receiver ):
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
final Messenger messenger = getIntent().getParcelableExtra(MainActivity.EXTRA_MESSENGER);
try {
messenger.send(Message.obtain(null, MainActivity.MSG_OP1, 101, 1001, "10001"));
messenger.send(Message.obtain(null, MainActivity.MSG_OP2, 102, 1002, "10002"));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
All the Messenger.send will execute in a Handler asynchronously and sequentially.
Actually, android.os.Messenger is also an aidl interface, if you have the android source code, you can find a file named IMessenger.aidl
package android.os;
import android.os.Message;
/** @hide */
oneway interface IMessenger {
void send(in Message msg);
}