写一个合适好用的SharedPreferences工具类

KaelLi 2019年3月8日15:56:3527,557

在Android开发中,SharedPreferences大概是我们常用的最简单的一种键值对(Key-Value)数据存储方式了,任何一个Android开发者都几乎不可能完全不用SharedPreferences。而它的使用确实很简单,不需要去自己操作读写文件,也不需要去写SQLite数据库,只是简单的put/get操作而已。但是,你的使用方法真的科学合理吗?

原始而古老的SharedPreferences使用方法

不经过任何封装的话,原始的SharedPreferences使用起来大概是这样子的:

SharedPreferences sp = getSharedPreferences("SharedPreferencesFileName", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("test_key", "test_value");
editor.commit();
sp.getString("test_key", "default_value");

单纯的看这么一小段代码,似乎不觉得有什么问题,而且用起来确实能得到想要的结果。但如果一个项目里,到处是这样的代码,甚至一个Activity里都有许多这样的地方,那就非常难看了,我说的难看,相信大家都理解是什么意思。所以,我们有必要进行一下封装,写一个SharedPreferences工具类,这样读写数据就不用这么“大张旗鼓”了。

如何设计一个好用的SharedPreferences工具类?

一个好用的SharedPreferences工具类,一般要满足这么几个条件才算是合格的:

  • 不需要每次使用都去获取SharedPreferences的实例。getSharedPreferences这个方法,调用一次就可以了。
  • 最好不需要获取SharedPreferences.Editor实例,也不需要调用什么commit、apply等方法,直接通过put/get来进行读写。
  • 最好能实现一些数据异常处理,如传入的key是null,或者传入的value不符合实际需求等情况下,能有一定的处理。
  • 能够保存对象数据,如List和Map这些常用的数据结构,尤其是Json形式的数据。

根据这几个条件,我们已经可以初步写出一个SharedPreferencesUtils工具类了:

public class SpUtils {
    // 整个工具类没有考虑过懒加载,主要因为这个工具类在App启动后基本上会马上使用,所以懒加载的实际意义不大
    private final static String SHARED_PREFERENCES_NAME = "TestSharedPreferencesFile";
    // 这里的App.getInstance()就是获取Application的实例
    private static SharedPreferences mSharedPreferences = App.getInstance().getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
    private static Gson mGson = new Gson();

    public static void applyValue(String key, Object value) {
        if (TextUtils.isEmpty(key)) return;
        SharedPreferences.Editor mEditor = mSharedPreferences.edit();
        if (value instanceof Integer) {
            mEditor.putInt(key, (Integer) value).apply();
        } else if (value instanceof Long) {
            mEditor.putLong(key, (Long) value).apply();
        } else if (value instanceof Float) {
            mEditor.putFloat(key, (Float) value).apply();
        } else if (value instanceof Boolean) {
            mEditor.putBoolean(key, (Boolean) value).apply();
        } else if (value instanceof String) {
            mEditor.putString(key, (String) value).apply();
        } else {
            try {
                mEditor.putString(key, mGson.toJson(value)).apply();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void commitValue(String key, Object value) {
        if (TextUtils.isEmpty(key)) return;
        SharedPreferences.Editor mEditor = mSharedPreferences.edit();
        if (value instanceof Integer) {
            mEditor.putInt(key, (Integer) value).commit();
        } else if (value instanceof Long) {
            mEditor.putLong(key, (Long) value).commit();
        } else if (value instanceof Float) {
            mEditor.putFloat(key, (Float) value).commit();
        } else if (value instanceof Boolean) {
            mEditor.putBoolean(key, (Boolean) value).commit();
        } else if (value instanceof String) {
            mEditor.putString(key, (String) value).commit();
        } else {
            try {
                mEditor.putString(key, mGson.toJson(value)).commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static Object getValue(String key, Object defValue) {
        if (TextUtils.isEmpty(key)) return null;
        if (defValue instanceof Integer) {
            return mSharedPreferences.getInt(key, (Integer) defValue);
        } else if (defValue instanceof Long) {
            return mSharedPreferences.getLong(key, (Long) defValue);
        } else if (defValue instanceof Float) {
            return mSharedPreferences.getFloat(key, (Float) defValue);
        } else if (defValue instanceof Boolean) {
            return mSharedPreferences.getBoolean(key, (Boolean) defValue);
        } else if (defValue instanceof String) {
            return mSharedPreferences.getString(key, (String) defValue);
        } else {
            try {
                return mGson.fromJson(mSharedPreferences.getString(key, ""), defValue.getClass());
            } catch (Exception e) {
                e.printStackTrace();
                return defValue;
            }
        }
    }
}

这里我通过Gson来实现对象与字符串的互转,从而实现了对象的数据保存。也因此,没有额外写putStringSet与getStringSet方法,因为直接将Set<String>作为对象来处理就足够了。

SharedPreferences的Key值该如何处理?

刚才写的工具类,使用起来基本上没有问题了。但实际上还存在一个非常现实的问题:各种Key值该怎么处理(或者说如何保存)?

有的人可能不太理解这个问题的含义,说白了,我们通过SharedPreferences保存的数据是持久化的,无论是保存还是读取,都依赖于那个String型的Key,而这个Key该如何保存,就很值得我们思考了。通常这些Key值可以认定为常量(偶尔个别的Key除了原来的字符串,还会带点别的东西如版本号等)。

很多开发习惯很差的同学,Key都是随遇而安的,如直接使用这样的代码:

// 写入的时候就很随意
editor.putString("nickname", "笑哈哈").commit;
// 在另外一个地方同样随意的读取
mSharedPreferences.getString("nickname", "默认昵称");

这样做的结果就是,用不了多久他就可能会忘了很多东西,如果在别的页面还要读写该Key的时候,要么全局搜索该Key,要么就出错:是nickname?还是Nickname?还是NickName?这样做的人,绝对不是一个合格的开发者。

所以有经验的同学,都知道最好把SharedPreferences所使用的所有Key,都保存在一起,有新的Key就添加一下,如此显得十分干净简练,不容易出错。是的,这样做确实是对的,也是应该的,但实际上还存在一个问题:你能记得每个Key对应的value类型吗?尤其是,团队开发,有的值是整个App里很多页面都会用到的,你保存的这个值,其他同事知道该怎么读取吗?总不能,我们每添加一个Key,就要去通知所有人吧?不现实,实际上大家很可能也会因为忙碌而忽略你的消息。

那给每个常量再添加一个注释怎么样?例如这样的:

// 昵称,String型,默认值为"尊贵用户"
public final static String NICKNAME = "Nickname";

看起来问题解决了,但大部分人的习惯恐怕不太容易改,注释写得再好也得有人看啊。相信Key值一多,团队里就会有人不太注意到这些注释了,所以这种方法可行,但对开发人员无法形成强力的约束,最终效果要看团队的整体素质。

那么有没有更强力一点的约束呢?办法当然是有的,那就是整个工具类对外只暴露set/get方法,连字符串常量也不对外暴露,约束团队使用SharedPreferences必须通过工具类,就这么简单:

// 昵称,String型,默认为"尊贵用户"
private final static String TEST_SCORE = "TestScore";

public static void setTestScore(int score) {
    if (score < 0 || score > 100)
        score = 0;
    applyValue(TEST_SCORE, score);
}

public static int getTestScore() {
    return (Integer)getValue(TEST_SCORE, 0);
}

在这段代码里,只写了一个常量TEST_SCORE,即考试分数。在它的set方法里,会对给定值进行一次判断,因为你一定知道它的取值范围是多少(我这里写成了0到100),所以当遇到一些非法值时就可以直接改成一个默认的值。而get方法直接表明了该Key对应的value是整数型的,避免了因数据类型弄混导致的各种异常。

也许有人会问:这样做会不会导致这个工具类代码特别多?实际上这是一个见仁见智的问题,的确,每个Key键除了一个常量外,还多了一个set方法和一个get方法,看起来繁琐,但带来的好处是所有成员都能对此清晰明了,不容易出错,相比较之下的缺点基本可以忽略了。也有人认为这样会让APK包的方法数增加,但现实情况是一个有一定规模的App已经完全不可能将方法数控制在64K之内了,我觉得这已经不是一个需要太过于操心的问题了。

KaelLi
  • 本文由 发表于 2019年3月8日15:56:35
  • 转载请务必保留本文链接:https://www.kaelli.com/36.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:2   其中:访客  1   博主  1
    • 取悦 取悦 0

      applyValue和commitValue看起来是一样的啊

        • KaelLi KaelLi

          @ 取悦 区别在于最终是commit还是apply的方式去保存数据。