5

I am trying to send some work from IntentService to BroadcastReceiver by using .putExtra() and sendBroadcast(), so I have own class called "Message", which extends HashMap< String,String> and implements Serializable.

public class Message extends HashMap<String,String> implements Serializable{
    public MessageID ID;
    public int Encode(byte[] buff,int off);
    public int Decode(byte[] buff,int off);
    //...
}

And I am sending it like this:

public static void ProcessMessage(Message msg) {
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(Receiver.BROADCAST);
    broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
    broadcastIntent.putExtra("MESSAGE",(Serializable)msg);
    parentService.sendBroadcast(broadcastIntent);
    Print("Broadcasting intent to receiver ("+Receiver.BROADCAST+") from: "+parentService.toString());
}

And receiving like this:

public void onReceive(Context context, Intent intent) {
    Sys.Print("Receiver handling: "+intent.getAction());
    if(intent.getAction().equals(BROADCAST)){
        try {
            Message msg = (Message) intent.getSerializableExtra("MESSAGE");
            Sys.Print("Receiver handling " + msg.ID.toString());
        } catch(Exception ex){
            Sys.Print("Failed handling message, reason: "+ex.getStackTrace().toString());
        }
    }
}

But here I always get this: "Failed handling message, reason: java.lang.ClassCastException: java.util.HashMap"

Any idea what could be wrong?

Stack-trace:

com.myapp.Receiver.onReceive(Receiver.java:24)
android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:709)
android.os.Handler.handleCallback(Handler.java:587)
android.os.Handler.dispatchMessage(Handler.java:92)
android.os.Looper.loop(Looper.java:138)
android.app.ActivityThread.main(ActivityThread.java:3701)
java.lang.reflect.Method.invokeNative(Native Method)
java.lang.reflect.Method.invoke(Method.java:507)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
dalvik.system.NativeStart.main(Native Method)

So finally, if anyone had similar problem, I solved it like this:

public class Message implements Parcelable {
    public HashMap<String,String> Data;
    public Message(){
        Data=new HashMap<>();
    }
    public int Encode(byte[] buff,int off);
    public int Decode(byte[] buff,int off);
    public void Add(String i,String v);
    public String At(String i);
    public boolean ContainsKey(String i);
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeMap(this.Data);
    }
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public Message createFromParcel(Parcel in) {
            return new Message(in);
        }
        public Message[] newArray(int size) {
            return new Message[size];
        }
    };
    public Message(Parcel in) {
        Data=new HashMap<>();
        in.readMap(this.Data,String.class.getClassLoader());
    }
}

2 Answers2

2

You won't like the answer.

Extras are stored in a Bundle. Android does some "optimizations" on the content of Bundles and it tries to be smart about how to serialize/deserialize an entry if it knows its type. So if you put anything that implements the Map interface into a Bundle, when you get it back out you will have a HashMap :-(

See my answer to this question for a detailed explanation of the mechanics.

To solve your problem, you'll need to have your custom class use (ie: contain) a HashMap and not be (ie: inherit from) a HashMap.

Community
  • 1
  • 1
David Wasser
  • 93,459
  • 16
  • 209
  • 274
0

This is a bug (yet, not fixed) in the Android SDK. In the bug report it is an ArrayList, but it also applies to classes that implement Map or List.

In the bug report, someone proposed using the following holder as a fix:

public class SerializableHolder implements Serializable {
    private Serializable content;
    public Serializable get() {
        return content;
    }
    public SerializableHolder(Serializable content) {
        this.content = content;
    }
}

but my suggestion would be to instead implement Parcelable, which is made for android and is a whole lot quicker than Serializable.

ddmps
  • 4,350
  • 1
  • 19
  • 34
  • Now I always run into, that getParcelableExtra("MESSAGE") always returns null, and I do `broadcastIntent.putExtra("MESSAGE", (Parcelable) msg);` –  Apr 12 '15 at 11:32
  • The "bug" has nothing to do with implementing `Serializable`. The bug is related to Android optimizations for classes that implement the `Map` or `List` interfaces. See [my answer to this question.](http://stackoverflow.com/a/12305459/769265) – David Wasser Apr 12 '15 at 13:56
  • @DavidWasser So you're saying that implementing `Parcelable` instead of `Serializable` won't fix it? – ddmps Apr 12 '15 at 14:19
  • @jakubinf Did you implement the necessary methods for `Parcelable`? See http://stackoverflow.com/a/7181792/1690982 – ddmps Apr 12 '15 at 14:20
  • Nope. Even if the class implements `Parcelable` instead of `Serializable`, any attempt to get an instance of this class out of a `Bundle` will result in a `HashMap`. – David Wasser Apr 12 '15 at 14:36
  • Yes, but even if I implemented HashMap<> and Parcelable together, Parcelable never called actual Message constructor and it attempted to call unexisting HashMap constructor, so it is probably definitely better to use HashMap only as a member and implement some few supplementary methods of it to main class. –  Apr 12 '15 at 15:05