4

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 }
}
bergermeister
  • 134
  • 2
  • 10

1 Answers1

3

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.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • That extra data was throwing me for a loop. I assumed writeValue() would call the respective function for the primitive's type. My code works like a charm now, thanks! – bergermeister Nov 12 '13 at 16:52
  • `writeValue()` is used to write generic "things" to the `Parcel`. It first writes an integer that indicates the kind of "thing" that follows, then writes the data. In this way, it can also read a generic "thing" back from the `Parcel`. It first reads an integer, which it then examines to decide what kind of "thing" comes next, then it reads and creates the appropriate "thing". – David Wasser Nov 12 '13 at 19:38
  • @DavidWasser this is a bit off topic but speaking of Parcelable and broadcast receivers, what do you think is the way to pass array of 'things' to your, for example, activity? I mean 'Parcelable' concept is mainly designed for IPC, right? – stdout Dec 31 '17 at 14:39
  • @zgulser `Parcelable` is a serialization technique. It is used to pass objects from one component to another. Those components could be in different processes (IPC), or they could be in the same process. That is irrelevant. Android uses the `Parcelable` concept to save `Intent`s to a persistent storage (so it can kill your process and start a new one later, when the user returns to it, and restore the original `Intent`). You can use `Parcelable` to send an array of things between components. If you have a large number of things, there are better methods... – David Wasser Dec 31 '17 at 16:04
  • ...like using shared memory (`static` member variables), `SharedPreferences`, SQLite database, or plain files. Each mechanism has its pros and cons. – David Wasser Dec 31 '17 at 16:05
  • @DavidWasser Sure it's the way how Parcelable is used. That's also the source of my question actually :). If we use an intent (say, list of objects) to send a broadcast, then that eventually means IPC, right? So, passing too much stuff is no good. And as you pointed out, there're better methods which I was wondering. So as far as I see, static is one way to do that. – stdout Dec 31 '17 at 17:40