Search code examples
androidandroid-intentandroid-broadcast

How to broadcast a parcelable object in an intent?


I'm new to Java / Android and trying to develop an adroid application which communicates with a USB CAN Bus adapter. I receive messages from the adapter in a string via my CANBusController class and I have built a parcelable class, CANMessage, which converts the string received into a form I can use (int Id, int Length, byte[] data). Now I'm trying to display the message I have received. I have a Fragment, CANBusControlFragment, which declares a BroadcastReceiver. When a message is a received, I build my CANMessage, place it and its string into a bundle, place the bundle into an Intent, and use sendBroadcast(intent):

CANBusController
{
    public void handleMessage(Message msg)
    {
        Bundle bundle = msg.getData();
        CANMessage canMsg = bundle.getParcelable("CAN_MESSAGE");
        if (canMsg != null)
        {
            Intent intent = new Intent();
            intent.setAction(BroadcastActions.CAN_MESSAGE_RECEIVED);
            //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.putExtra("CAN_MESSAGE", canMsg);
            intent.putExtra("STRING_MESSAGE", canMsg.toString());
            parentContext.sendBroadcast(intent);
        }
    }
}

CANBusControlFragment extends Fragment
{
    private final BroadcastReceiver canMsgReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            String action = intent.getAction();
            Bundle bundle = intent.getExtras();
            try
            {
                if (BroadcastActions.CAN_MESSAGE_RECEIVED.equals(action))
                {
                    synchronized(this)
                    {
                        String str = bundle.getString("STRING_MESSAGE"); // <-- Crash
                        CANMessage canMsg = bundle.getParcelable("CAN_MESSAGE"); // 
                        Append(str);
                        Append(canMsg.toString();
                    }
                }
            }
            catch (Exception ex)
                print exception
        }
    }
}

If I add the CANMessage to the bundle, my application will crash in the onReceived method of the BroadcastReceiver with a Null Pointer Exception when I try to getString or getParcelable. If I do not include the CANMessage, then the application will work fine. I believe CANMessage implements Parcelable correctly, since I've tested it by performing all of the same steps except for broadcasting the message: Build CANMessage, putParcelable in Bundle, put Bundle in Intent, grab the Bundle out of the Intent, getParcelable out of the bundle.

I appreciate any help. Thanks :)

EDIT - LogCat information: It appears the error is in my parcel methods. Trying to readByteArray(byte[]) crashes the app.

11-12 09:17:44.568: E/AndroidRuntime(3087): FATAL EXCEPTION: main

11-12 09:17:44.568: E/AndroidRuntime(3087): java.lang.RuntimeException: Error receiving broadcast Intent { act=com.sb2tablet.CAN_MESSAGE_RECEIVED flg=0x10000010 (has extras) } in com.sb2tablet.CANBusControlFragment$1@9e8f6e00
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:768)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Handler.handleCallback(Handler.java:725)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Handler.dispatchMessage(Handler.java:92)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Looper.loop(Looper.java:137)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.app.ActivityThread.main(ActivityThread.java:5041)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at java.lang.reflect.Method.invokeNative(Native Method)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at java.lang.reflect.Method.invoke(Method.java:511)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at dalvik.system.NativeStart.main(Native Method)
11-12 09:17:44.568: E/AndroidRuntime(3087): Caused by: java.lang.NullPointerException
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Parcel.readByteArray(Parcel.java:1594)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.sb2tablet.models.CANMessage.<init>(CANMessage.java:93)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.sb2tablet.models.CANMessage$1.createFromParcel(CANMessage.java:18)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.sb2tablet.models.CANMessage$1.createFromParcel(CANMessage.java:1)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Parcel.readParcelable(Parcel.java:2103)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Parcel.readValue(Parcel.java:1965)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Parcel.readMapInternal(Parcel.java:2226)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Bundle.unparcel(Bundle.java:223)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.os.Bundle.containsKey(Bundle.java:271)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.content.Intent.hasExtra(Intent.java:4121)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at com.sb2tablet.CANBusControlFragment$1.onReceive(CANBusControlFragment.java:51)
11-12 09:17:44.568: E/AndroidRuntime(3087):     at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:758)

Edit - writeToParcel and Parcel in:

public void writeToParcel(Parcel out, int flags)
{
    out.writeValue(id);    // int
    out.writeValue(len);   // int
    out.writevalue(data);  // byteArray, the value is not null here
    //out.writeByArray(data);
}

public CANMessage(Parcel in)
{
    id = in.readInt();
    len = in.readInt();
    in.readByteArray(data); // Actual crash is here
}

EDIT - Test Case 1 - It seems extra data is added to my parcel, I'm not sure how.

public void writeToParcel(Parcel out, int flags)
{
    out.writeValue(id);    // int = 256
    out.writeValue(len);   // int = 2, length of byte array
    out.writevalue(data);  // byte array = { 17, 51 }
}

public CANMessage(Parcel in)
{
    len = in.readInt();        // 1
    id = in.readInt();         // 256
    len = in.readInt();        // 1
    len = in.readInt();        // 2
    int i = in.readInt();      // 13
    if (data == null) data = new byte[len];
    in.readByteArray(data);    // data = { 17, 51 }
}

EDIT - Test Case 2

public void writeToParcel(Parcel out, int flags)
{
    out.writeValue(id);       // int = 256
    out.writeValue(len);      // int = 2, length of byte array
    out.writeByteArray(data); // byte array = { 17, 51 }
}

public CANMessage(Parcel in)
{
    len = in.readInt();        // 1
    id = in.readInt();         // 256
    len = in.readInt();        // 1
    len = in.readInt();        // 2
    len = in.readInt();        // 2
    if (data == null) data = new byte[len];
    // in.readByteArray(data); // Crashes
    int i = in.readInt();      // 13073 = 0x3311 = byte[] { 17, 51 }
}

Solution

  • When you read a byte array, you need to have previously allocated a byte array of (at least) the correct size. Like this:

    byte[] blah = new byte[100];
    in.readByteArray(blah);
    

    Also, you don't want to use writeValue() to write primitives to the Parcel. Use the appropriate methods. Example:

    out.writeInt(id);    // int = 256
    out.writeInt(len);   // int = 2, length of byte array
    out.writeByteArray(data);  // byte array = { 17, 51 }
    

    You must always pair the write() methods with the same type of read() methods on the parcel. For example, if you use writeValue() to write something into the Parcel, you must read it using readValue(). If you know that the variable is an int, then you should write it using writeInt() and read it using readInt(). If you write it using writeValue() then the "type" of the object is also written to the Parcel so that readValue() will know what type of object it is. This is why you are seeing extra data in the Parcel.