How to pass an object from one activity to another

2018-12-31 00:23发布

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 the customer class:

public class Customer {

    private String firstName, lastName, Address;
    int Age;

    public Customer(String fname, String lname, int age, String address) {

        firstName = fname;
        lastName = lname;
        Age = age;
        Address = address;
    }

    public String printValues() {

        String data = null;

        data = "First Name :" + firstName + " Last Name :" + lastName
        + " Age : " + Age + " Address : " + Address;

        return data;
    }
}

I want to send its object from one Activity to another and then display the data on the other Activity.

How can I achieve that?

30条回答
有味是清欢
2楼-- · 2018-12-31 00:33

I found a simple & elegant method:

  • NO Parcelable
  • NO Serializable
  • NO Static Field
  • No Event Bus

Method 1

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

Method 2

  • for the sender,
    1. use custom native method to add your java object to JNI global reference table(via JNIEnv::NewGlobalRef)
    2. put the return integer (actually, JNIEnv::NewGlobalRef return jobject, which is a pointer, we can cast it to int safely) to your Intent(via Intent::putExtra)
  • for the receiver
    1. get integer from Intent(via Intent::getInt)
    2. use custom native method to restore your java object from JNI global reference table (via JNIEnv::NewLocalRef)
    3. remove item from JNI global reference table(via JNIEnv::DeleteGlobalRef),

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

Method 3

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 4

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);
}
查看更多
浮光初槿花落
3楼-- · 2018-12-31 00:36

If you choose use the way Samuh describes, remember that only primitive values can be sent. That is, values that are parcable. So, if your object contains complex objects these will not follow. For example, variables like Bitmap, HashMap etc... These are tricky to pass by the intent.

In general I would advice you to send only primitive datatypes as extras, like String, int, boolean etc. In your case it would be: String fname, String lname, int age, and String address.

My opinion: More complex objects are better shared by implementing a ContentProvider, SDCard, etc. It's also possible to use a static variable, but this may fastly lead to error-prone code...

But again, it's just my subjective opinion.

查看更多
回忆,回不去的记忆
4楼-- · 2018-12-31 00:36

We can pass the object from one activity to another activity:

SupplierDetails poSuppliersDetails = new SupplierDetails();

Inside poSuppliersDetails we have some values. Now I am sending this object to target activity:

Intent iPODetails = new Intent(ActivityOne.this, ActivityTwo.class);
iPODetails.putExtra("poSuppliersDetails", poSuppliersDetails);

How to get this in ACtivityTwo:

private SupplierDetails supplierDetails;
    supplierDetails =(SupplierDetails) getIntent().getSerializableExtra("poSuppliersDetails");
查看更多
忆尘夕之涩
5楼-- · 2018-12-31 00:36

Hello all I see a lot of good options but I was wondering why Binding hasn't been used?

Passing a reference to an object just seems more efficient to me than serializing and desterilizing objects, but I have not done a deep dive to see if that is what is going on behind the scenes.

Creating a Binder is simple enough...

public class MyBinder extends Binder {

    private Object myObject;

    public MyBinder(Object object) {
        myObject = object;
    }

    public Object getObject() {
        return myObject;
    }

}

And creating the parcelable to use it isn't that bad ether.

public class MyParcelable implements Parcelable {

    private Object myObject;

    public MyParcelable() {
    }

    public MyParcelable(Parcel parcel) {
        myObject = ((MyBinder)parcel.readStrongBinder()).getObject();
    }

    public void setObject(Object object) {
        myObject = object;
    }

    public Object getObject() {
        return myObject;
    }

    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeStrongBinder(new MyBinder(myObject));
    }

    public int describeContents() {
        return myObject == null ? 0 : 1;
    }

    public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {

        public MyParcelable createFromParcel(Parcel parcel) {
            return new MyParcelable(parcel);
        }

        public MyParcelable[] newArray(int length) {
            return new MyParcelable[length];
        }

    };
}

This logic is really cool because you are actually passing a reference from activity to activity.

I would advise checking for nulls and if the instanceof Binder is MyBinder!

and to implement this you just...

Send it off

Object myObject = "some object";
MyParcelable myParcelable = new MyParcelable();
myParcelable.setObject(myObject);

intent.putExtra("MyParcelable", myParcelable);

Get it back

myParcelable = (MyParcelable) getIntent().getExtras().getParcelable("MyParcelable");
myObject = myParcelable.getObject();

Heck someone could get all crazy and make this sucker a true generic.

查看更多
几人难应
6楼-- · 2018-12-31 00:38

Create your own class Customer as following:

import import java.io.Serializable;
public class Customer implements Serializable
{
    private String name;
    private String city;

    public Customer()
    {

    }
    public Customer(String name, String city)
    {
        this.name= name;
        this.city=city;
    }
    public String getName() 
    {
        return name;
    }
    public void setName(String name) 
    {
        this.name = name;
    }
    public String getCity() 
    {
        return city;
    }
    public void setCity(String city) 
    {
        this.city= city;
    }

}

In your onCreate() method

@Override
protected void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_top);

    Customer cust=new Customer();
    cust.setName("abc");
    cust.setCity("xyz");

    Intent intent=new Intent(abc.this,xyz.class);
    intent.putExtra("bundle",cust);
    startActivity(intent); 
}

In xyz activity class you need to use the following code:

Intent intent=getIntent();
Customer cust=(Customer)intent.getSerializableExtra("bundle");
textViewName.setText(cust.getName());
textViewCity.setText(cust.getCity());
查看更多
若你有天会懂
7楼-- · 2018-12-31 00:38

Crete a class like bean class and implement the Serializable interface. Then we can pass it through the intent method, for example:

intent.putExtra("class", BeanClass);

Then get it from the other activity, for example:

BeanClass cb = intent.getSerializableExtra("class");
查看更多
登录 后发表回答