How to pass an object from one activity to another on Android

后端 未结 30 3954
遇见更好的自我
遇见更好的自我 2020-11-21 04:03

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

30条回答
  •  执念已碎
    2020-11-21 05:09

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

提交回复
热议问题