Android "java.lang.RuntimeException: Parcelable encounteredClassNotFoundException reading a Serializable object"
Asked Answered
T

5

14

I have an Android application in which I add one more enum:

public enum RootNavigationOption {
    HOME(R.string.home, R.drawable.ic_home), SETTINGS(R.string.settings, R.drawable.ic_settings), LOGOUT(
            R.string.logout_label, R.drawable.ic_logout);

    private int navigationOptionLabelResId;
    private int navigationOptionImageResId;

    private RootNavigationOption(int navigationOptionLabelResId, int navigationOptionImageResId) {

        this.navigationOptionLabelResId = navigationOptionLabelResId;
        this.navigationOptionImageResId = navigationOptionImageResId;
    }

    public int getNavigationOptionLabelResId() {
        return navigationOptionLabelResId;
    }

    public int getNavigationOptionImageResId() {
        return navigationOptionImageResId;
    }

    public static int getValuePosition(RootNavigationOption filterOption) {
        int idx = 0;
        for (RootNavigationOption navigationOptionIter : values()) {
            if (navigationOptionIter.equals(filterOption)) {
                return idx;
            }
            idx++;
        }
        return 0;
    }
}

I put in this enum and placed it in couple of intent bundles for communication to my main activity. I already have one more such enum in my solution, which does not cause any problems. However, when I run my application with this new enum being defined, it immediately crashes with:

 java.lang.RuntimeException: Parcelable encounteredClassNotFoundException reading a Serializable object (name = com.pack1.pack2.pack3.helpers.RootNavigationOption)
    at android.os.Parcel.readSerializable(Parcel.java:2219)
    at android.os.Parcel.readValue(Parcel.java:2064)
    at android.os.Parcel.readArrayMapInternal(Parcel.java:2314)
    at android.os.Bundle.unparcel(Bundle.java:249)
    at android.os.Bundle.getString(Bundle.java:1118)
    at android.app.ActivityOptions.<init>(ActivityOptions.java:310)
    at com.android.server.am.ActivityRecord.updateOptionsLocked(ActivityRecord.java:668)
    at com.android.server.am.ActivityStack.startActivityLocked(ActivityStack.java:1778)
    at com.android.server.am.ActivityStackSupervisor.startActivityUncheckedLocked(ActivityStackSupervisor.java:1769)
    at com.android.server.am.ActivityStackSupervisor.startActivityLocked(ActivityStackSupervisor.java:1249)
    at com.android.server.am.ActivityStackSupervisor.startActivityMayWait(ActivityStackSupervisor.java:741)
    at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:3118)
    at com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:3104)
    at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:135)
    at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2071)
    at android.os.Binder.execTransact(Binder.java:404)
    at dalvik.system.NativeStart.run(Native Method)
 Caused by: java.lang.ClassNotFoundException: com.pack1.pack2.pack3.helpers.RootNavigationOption
    at java.lang.Class.classForName(Native Method)
    at java.lang.Class.forName(Class.java:251)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:2262)
    at java.io.ObjectInputStream.readEnumDescInternal(ObjectInputStream.java:1553)
    at java.io.ObjectInputStream.readEnumDesc(ObjectInputStream.java:1534)
    at java.io.ObjectInputStream.readEnum(ObjectInputStream.java:1579)
    at java.io.ObjectInputStream.readNonPrimitiveContent(ObjectInputStream.java:768)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1981)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1938)
    at android.os.Parcel.readSerializable(Parcel.java:2213)
    ... 16 more
 Caused by: java.lang.NoClassDefFoundError: com/pack1/pack2/pack3/helpers/RootNavigationOption
    ... 26 more
 Caused by: java.lang.ClassNotFoundException: Didn't find class "com.pack1.pack2.pack3.helpers.RootNavigationOption" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
    ... 26 more

I am not able to find the root casue of the problem. I also considered the following posts, without any result:

  • post1 - i am not using any cusotmclass loaders.
  • post2 - i am running the application directly from ADT, no proguard involved.

Also I have:

  • checked my classes in the bin folder and the needed class is in there.
  • decompiled the ready apk with apktool and the respective smali is in there too

EDIT

Now I am definitely sure that the exception is actually caused by putting the enum value in a bundle used to start the activity, although these lines are not mentioned in the stacktrace:

Bundle activityOptions = new Bundle();
activityOptions.putSerializable(Constants.VIEW_MODE, RootNavigationOption.HOME);
Intent intent = new Intent(this, MainActivity.class);

I have changed the logic of my application to not use this enum value in Bundle only e.g. as method parameter and now it runs without any exceptions. Does anyone have a clue why this happens?

I also place a bounty on the question now, because I am more perplexed.

Trawler answered 24/8, 2015 at 11:44 Comment(17)
on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib]] but you say that your class stored in bin folder?Repetitive
That is pecuilar, according to the documentation of Enum it says its serializable. developer.android.com/reference/java/lang/Enum.htmlRetina
@JoxTraex not only that - I successfully use another enum in a Bundle (but it is a parameter to a fragment)Trawler
Hmm is there a difference in the size? Could it be related to that?Retina
@Repetitive I use ADT for development and yes, the classes are in the bin folder of the project. However, I maybe am missing something you are pointing at with the path part of your comment.Trawler
@JoxTraex What size should I look at? The two enums are almost the same (both have three values with two parameters of the same type). Even the working enum defines the same method, along with one more method.Trawler
hmm for the sake of sanity, if you replace the enum that is failing with the one that is working, does it work? and not have that exception? Basically this is one of those issues where its a 'grasp at straws' kind of issueRetina
@BorisStrandjev: I think some links can help you here and hereRepetitive
@Repetitive - neither. BTW the second one is already linked in my questionTrawler
Sorry can't help you. GoodluckRepetitive
@JoxTraex Good catch. Replacing in the same context does not work - it fails with the same error for the other enum.Trawler
Great, well now we have a particular condition, interesting :) @BorisStrandjevRetina
Android has 2 classloaders and as Bundle is a framework class is using the system classloader (loaded by default when you invoke its empty constructor) which doesn't know nothing about where your enum is inside the APK. You will need to provide to the bundle the app classloader to find your enum. developer.android.com/reference/android/os/… If you don't want to indicate specific classloaders the better is to avoid enums and simply use constants.Classroom
@MartinRevert this is an interesting line of thinking, but I am not certain how should I specify working class loader. I tried new Bundle(RootNavigationOption.class.getClassLoader()); but the error persists. Do I have to specify class loaders somewhere else too?Trawler
Bundle is a framework class and it doesn't provide a context. Your enum neither provide a context. For parcelables or serializables you will need to obtain your context classloader from the thread you're executing. That classloader will provide you the access to the correct Java package. developer.android.com/reference/android/content/…Classroom
are you using any proguard options while running app? Maybe you sould check if something is proguarded and causing this problem.Babin
@Babin as far as I know I am not - I am using default run in ADT, and i believe this does not have Proguard enabled by default (and, yes, I have checked project.properties and progguard is not enabled)Trawler
T
7

Simply use the enum ordinal as extra using Enum.ordinal(); additionally, this should make your RootNavigationOption.getValuePosition() obsolete.

Example:

final Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(Constants.VIEW_MODE, RootNavigationOption.SETTINGS.ordinal());

Later in your MainActivity (or similar):

final int defaultViewModeOrdinal = RootNavigationOption.HOME.ordinal();
final int viewModeOrdinal = getIntent().getIntExtra(Constants.VIEW_MODE, defaultViewModeOrdinal);
final RootNavigationOption viewMode = RootNavigationOption.values()[viewModeOrdinal];
Tampon answered 2/9, 2015 at 14:20 Comment(0)
A
5

Looks like Android is unbundling on another process onto which the intent is passed and tried to be processed, where your enum doesn't live; read over: Passing enum or object through an intent (the best solution) for more information.

Asiatic answered 28/8, 2015 at 17:52 Comment(5)
Thanks for the link. Can you speculate on the other process that unbundles?Trawler
What are you starting with the intent that contains the bundle causing the problem?Asiatic
i am starting an activityTrawler
An activity in the same process? What kind of activity--what I'm getting at is: can you think of any reason Android would start a seperate process for whatever activity is being started that this intent is being routed through?Asiatic
The launcher activity starts the second activity in the row with this intent. No reason for special processes, i thinkTrawler
S
2

I believe the problem lies on Android using multiple ClassLoader

You can try setting the class loader before getSerializable()

bundle.setClassLoader(getClass().getClassLoader());

Android E/Parcel﹕ Class not found when unmarshalling (only on Samsung Tab3)

Actually I would suggest not to use serializable, and implements your own parcellable.

Steelworks answered 28/8, 2015 at 17:15 Comment(0)
C
1

As I suggested in my comments (assuming that Constants.VIEW_MODE is a String key):

//Inside an activity or use getApplicationConext().getClassLoader()
ClassLoader loader = this.getClassLoader(); 
Bundle activityOptions = new Bundle(loader);
activityOptions.putSerializable(Constants.VIEW_MODE, RootNavigationOption.HOME);

EDIT:

Hmmm..so the public constructor is not working as per documentation. Shocking. Maybe we can force a change using this another method: http://developer.android.com/reference/android/os/Bundle.html#setClassLoader(java.lang.ClassLoader)

Try this instead and let me know what happens:

 Bundle activityOptions = new Bundle(); 
 activityOptions.setClassLoader(RootNavigationOption.class.getClassLoader());
 activityOptions.putSerializable(Constants.VIEW_MODE, RootNavigationOption.HOME);
Classroom answered 27/8, 2015 at 11:54 Comment(4)
Thank you for your suggestion, but the issue persists for me.Trawler
@BorisStrandjev see my edited answer, I refuse to surrender on this :)Classroom
Still the same result,Trawler
Definitevely is a ClassLoader problem, but I'm reaching my limits on this. My last try, forcing the ClassLoader in a more Java way: activityOptions.setClassLoader(Thread.currentThread().getContextClassLoader());Classroom
C
1

While the ordinals approach works, I would advise being more explicit about the values. This does add a bit more code to implement, but it makes your code more readable and explicit to protect against possible future issues like reordering or inserting new values in the middle of the list.

public enum RootNavigationOption {
    HOME(0, R.string.home, R.drawable.ic_home),
    SETTINGS(1, R.string.settings, R.drawable.ic_settings),
    LOGOUT(2, R.string.logout_label, R.drawable.ic_logout);

    // Note the added code instance variable here, used in the constructor as well.
    private final int code;
    private final int navigationOptionLabelResId;
    private final int navigationOptionImageResId;

    private RootNavigationOption(int code,
                                 int navigationOptionLabelResId,
                                 int navigationOptionImageResId) {
        this.code = code;
        this.navigationOptionLabelResId = navigationOptionLabelResId;
        this.navigationOptionImageResId = navigationOptionImageResId;
    }

    public int getNavigationOptionLabelResId() {
        return navigationOptionLabelResId;
    }

    public int getNavigationOptionImageResId() {
        return navigationOptionImageResId;
    }

    public int getCode() {
        return code;
    }

    public static RootNavigationOption fromCode(int code) {
        switch(code) {
            case 0:
                return HOME;
            case 1:
                return SETTINGS;
            case 2:
                return LOGOUT;
            default:
                throw new RuntimeException(
                    "Illegal RootNavigationOption: " + code);
        }
    }
}

This can then be used like so:

// Put
Bundle bundle = new Bundle();
bundle.putInt("key", RootNavigationOption.HOME.getCode());

// Get 
RootNavigationOption rootNavigationOption = RootNavigationOption.fromCode(bundle.getInt("key"));
Confiture answered 6/11, 2017 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.