【问题标题】:Save enum to SharedPreference将枚举保存到 SharedPreference
【发布时间】:2013-01-22 22:19:57
【问题描述】:

我想知道,将Enum 保存到SharedPrefereces 的常用方法是什么?目前,我正在使用gson将枚举转换为String,然后将其保存到SharedPrefereces

    Gson gson = new Gson();
    // country is an enum.
    String json_country = gson.toJson(country);
    sharedPreferences.edit().putString(COUNTRY, json_country);

我想知道,这是一个好方法吗?有没有更好的办法?

【问题讨论】:

    标签: android


    【解决方案1】:

    这是我为此编写的一个不错的实用程序类。它非常通用,可以满足广泛的需求。

    import android.content.Context;
    import android.content.SharedPreferences;
    
    public class PreferencesUtils
    {
        enum StorageMode {
            /** Store enum by ordinal. */
            ORDINAL,
    
            /** Store enum by name. */
            NAME
        }
    
        private static final String PREFERENCES_NAME = "your_prefs_name";
    
        /**
         * Put an enum in SharedPreferences with the given key.
         * @param context The context
         * @param key The preference key
         * @param enumm The enum to store
         * @param mode The mode to store the enum - by name or ordinal
         */
        public static void putEnum(final Context context, final String key, final Enum enumm, final StorageMode mode)
        {
            final SharedPreferences sp = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
            final SharedPreferences.Editor editor = sp.edit();
            switch (mode)
            {
                default:
                case ORDINAL:
                    editor.putInt(key, enumm.ordinal());
                    break;
                case NAME:
                    editor.putString(key, enumm.name());
                    break;
            }
            editor.apply();
        }
    
        /**
         * Get the enum stored in SharedPreferences with the given key.
         * @param context The context
         * @param key The preference key
         * @param enumType The type of Enum stored
         * @param defaultEnumm Enum returned if a preference stored with key does not exist. Can be null.
         * @param mode The mode by which the enum was originally stored
         * @param <T> The type of Enum stored
         * @return The Enum stored as a preference with the given key
         */
        public static <T extends Enum<T>> Enum getEnum(final Context context, final String key, final Class<T> enumType,
                                                       final Enum defaultEnumm, final StorageMode mode)
        {
            final SharedPreferences sp = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
            final T[] values = enumType.getEnumConstants();
    
            switch (mode)
            {
                default:
                case ORDINAL:
                    final int ord = sp.getInt(key, -1);
                    return ord == -1 ? defaultEnumm : values[ord];
                case NAME:
                    final String name = sp.getString(key, null);
                    for (final T value : values)
                    {
                        if (value.name().equals(name))
                        {
                            return value;
                        }
                    }
            }
            return defaultEnumm;
        }
    }
    

    【讨论】:

      【解决方案2】:

      考虑到上述所有选项,我决定使用另一种解决方案。

      我只需要一组值,没有任何逻辑。在这种情况下,可以使用@IntDef@StringDef 等工具来代替enum

      该解决方案可以安全地根据变量名称和变量顺序进行重构。唯一不应该改变的——实际值。但是,默认的重构工具不会要求您在重命名时更改它们,因此要使其自动进行并不是那么容易(从这个角度来看,@IntDef@StringDef 更好)。

        @Retention(SOURCE)
        @IntDef({NOT_REQUESTED, GIVEN, PROHIBITED})
        public @interface CustomPermission {}
      
        public static final int NOT_REQUESTED = -1;
        public static final int PROHIBITED = 0;
        public static final int GIVEN = 1;
      
        @CustomPermission
        public int getPermission() {
          return getSharedPreferences(SHARED_PREFS_FILE)
                .getInt(STATISTICS_COLLECTION_PERMISSION, NOT_REQUESTED);
        }
      
        public void setPermission(@CustomPermission int flag) {
          getSharedPreferences(SHARED_PREFS_FILE)
            .edit()
            .putInt(STATISTICS_COLLECTION_PERMISSION, flag)
            .apply();
        }
      

      这里是official doc

      【讨论】:

        【解决方案3】:

        这与企业 Java 开发人员在将枚举持久保存到数据库时所面临的问题相同。现有答案的问题在于它们很脆弱并且不适合重构。这就是原因(底部有替代方案。)

        使用枚举的toString()valueOf() 方法意味着无法重命名枚举值。假设我有一个 VehicleType 枚举,其值为 CARTRUCK。如果我将字符串“TRUCK”存储为首选项值,然后在我的应用程序的下一个版本中将VehicleType.TRUCK 重命名为VehicleType.PICKUP_TRUCK,则存储的字符串不再有意义,valueOf() 将抛出一个IllegalArgumentException

        使用值的序数意味着枚举值无法重新排序,或者您存储的值将不再匹配。这种情况可能会更糟,因为您的应用程序将继续运行,但可能会以意想不到的方式运行,一旦最终发现问题就很难追踪并且可能无法纠正。除了结尾之外的任何地方添加新值也是如此。如果我在顶部添加一个新的 MOTORCYCLE 值,则在之前版本的应用程序中存储为序号 (0) 的 CAR 在更新后将返回为 MOTORCYCLE,而 TRUCK (序号 1)会变成CAR

        我使用的替代方法是将final 字段添加到枚举并使用其值,如下所示:

        public enum VehicleType {
            CAR("C"),
            TRUCK("T");
        
            private final String code;
            private static final Map<String,VehicleType> valuesByCode;
        
            static {
                valuesByCode = new HashMap<>(values().length);
                for(VehicleType value : values()) {
                    valuesByCode.put(value.code, value);
                }
            }
        
            VehicleType(String code) {
                this.code = code;
            }
        
            public static VehicleType lookupByCode(String code) { 
                return valuesByCode.get(code); 
            }
        
            public String getCode() {
                return code;
            }
        }
        

        使用类似preferences.putString("vehicle_type", vehicleType.getCode()) 的方式存储一个值并使用类似vehicleType = VehicleType.lookupByCode(preferences.getString("vehicle_type", null)) 的方式检索它。

        这种方法需要一些额外的代码,但在我看来,它是最强大的解决方案。

        【讨论】:

        • 我看不出前面的例子有多“脆弱”。它不会“泄漏”首选项使用的字符串。如果/当需要更改枚举名称时,为什么不能将更简单的版本转换为更复杂的版本?我对Java相当陌生,我错过了什么吗?我想唯一的缺点是您必须记住使用第一个示例需要重构...
        • @WayneJ 如果枚举的名称或序号没有写入持久内存并与您的 VM 一起死亡,我会同​​意。 但是当您的应用存储偏好时,它会“泄漏”该值,因为写入该值的应用版本可能不是您以后读取它的应用版本。您当然可以重构您的代码以在以后使用这种方法,但就像您说的那样,您必须记住这样做......其他开发人员必须知道重构它......并且还要记住。这是个人喜好,但我宁愿从一开始就遵循这个简单的模式。
        • 我更喜欢使用 Enum.name() 并在我的枚举声明中添加适当的注释以不更改字符串值。
        • @Yar ProGuard 怎么样?它的工作是重命名您的标识符。 :)
        • @spaarky21 到目前为止还没有,我关闭了 ProGuard。我正在写共享首选项 enum.name() 并阅读 enum.valueOf() 并且两个字符串都是相同的。但是感谢您指出这一点。
        【解决方案4】:

        您可以为它使用一个简单的字符串,然后使用 valueOf 方法提取该值。这是一个例子:

        public enum MyEnum {
            ENUM1, ENUM2, ENUM3, ENUM4;
        
            public static MyEnum toMyEnum (String myEnumString) {
                try {
                    return valueOf(myEnumString);
                } catch (Exception ex) {
                        // For error cases
                    return ENUM1;
                }
            }
        }
        
        public void setMyEnum(Context context, MyEnum myEnum) {
            SharedPreferences sp = context.getPreferences(this.MODE_PRIVATE);
            SharedPreferences.Editor editor = sp.edit();
            editor.putString("MyEnum", myEnum.toString());
            editor.commit();
        }
        
        public MyEnum getMyEnum(Context context) {
            SharedPreferences sp = context.getPreferences(this.MODE_PRIVATE);
            String myEnumString = sp.getString("MyEnum", MyEnum.ENUM1.toString());
            return MyEnum.toMyEnum(myEnumString);
        }
        

        这里是示例代码,您可以了解它是如何工作的。 https://github.com/jiahaoliuliu/SavingEnumToSharedPreferences

        【讨论】:

        • 枚举字符串值可能会被 ProGuard 更改,更糟糕的是它会在不同的发布版本中有所不同。
        • 我会使用myEnum.name() 而不是.toString(),因为它可以被覆盖
        【解决方案5】:

        您可以将枚举与整数相关联并存储简单的 int,请看:

        Cast Int to enum in Java(第二个答案)- 也可以用同样的方式 enumToInt

        【讨论】:

        • 作为一个选项,如果使用 Enum.name() 而不是 ordinal(),枚举也可以保存为字符串
        • 只有在不为枚举初始化不同值的情况下才能强制转换,因此它仅适用于 0...n
        猜你喜欢
        • 1970-01-01
        • 2019-07-24
        • 2014-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-03
        • 1970-01-01
        相关资源
        最近更新 更多