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.

Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • `on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib]]` but you say that your class stored in `bin` folder? – BNK Aug 27 '15 at 09:04
  • That is pecuilar, according to the documentation of Enum it says its serializable. http://developer.android.com/reference/java/lang/Enum.html – JoxTraex Aug 27 '15 at 09:10
  • @JoxTraex not only that - I successfully use another enum in a Bundle (but it is a parameter to a fragment) – Boris Strandjev Aug 27 '15 at 09:15
  • Hmm is there a difference in the size? Could it be related to that? – JoxTraex Aug 27 '15 at 09:16
  • @BNK 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. – Boris Strandjev Aug 27 '15 at 09:16
  • @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. – Boris Strandjev Aug 27 '15 at 09:17
  • 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 issue – JoxTraex Aug 27 '15 at 09:19
  • @BorisStrandjev: I think some links can help you [here](http://stackoverflow.com/questions/16603002/classnotfoundexception-on-androidannotations-generated-classes-since-update-to-a/23577697#23577697) and [here](http://stackoverflow.com/questions/6014806/android-classnotfoundexception-when-passing-serializable-object-to-activity) – BNK Aug 27 '15 at 09:19
  • @BNK - neither. BTW the second one is already linked in my question – Boris Strandjev Aug 27 '15 at 09:28
  • Sorry can't help you. Goodluck – BNK Aug 27 '15 at 09:30
  • @JoxTraex Good catch. Replacing in the same context does not work - it fails with the same error for the other enum. – Boris Strandjev Aug 27 '15 at 09:31
  • Great, well now we have a particular condition, interesting :) @BorisStrandjev – JoxTraex Aug 27 '15 at 09:36
  • 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. http://developer.android.com/reference/android/os/Bundle.html#Bundle(java.lang.ClassLoader) If you don't want to indicate specific classloaders the better is to avoid enums and simply use constants. – Martin Revert Aug 27 '15 at 10:26
  • @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? – Boris Strandjev Aug 27 '15 at 10:32
  • 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. http://developer.android.com/reference/android/content/Context.html#getClassLoader() – Martin Revert Aug 27 '15 at 11:25
  • are you using any proguard options while running app? Maybe you sould check if something is proguarded and causing this problem. – deadfish Aug 27 '15 at 20:48
  • @deadfish 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) – Boris Strandjev Aug 28 '15 at 11:11

5 Answers5

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];
Helden
  • 451
  • 4
  • 10
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.

Community
  • 1
  • 1
Ashton Engberg
  • 5,949
  • 2
  • 19
  • 14
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.

Community
  • 1
  • 1
Derek Fung
  • 8,171
  • 1
  • 25
  • 28
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);
Martin Revert
  • 3,242
  • 2
  • 30
  • 33
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"));
Smalls
  • 352
  • 3
  • 13