获取SharedPreferences的两种方式:
1 调用Context对象的getSharedPreferences()方法
2 调用Activity对象的getPreferences()方法
两种方式的区别:
调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享.
调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用.
SharedPreferences的四种操作模式:
Context.MODE_PRIVATE
Context.MODE_APPEND
Context.MODE_WORLD_READABLE
Context.MODE_WORLD_WRITEABLE
Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件.
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件.
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取.
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入.
将数据保存至SharedPreferences:
SharedPreferences preferences=getSharedPreferences("user",Context.MODE_PRIVATE);
Editor editor=preferences.edit();
String name="xixi";
String age="22";
editor.putString("name", name);
editor.putString("age", age);
editor.commit();
从SharedPreferences获取数据:
SharedPreferences preferences=getSharedPreferences("user", Context.MODE_PRIVATE);
String name=preferences.getString("name", "defaultname");
String age=preferences.getString("age", "0");

SharedPreferences是Android之中的基础内容,是一种非常轻量化的存储工具。核心思想就是在xml文件中保存键值对。而正因为采用的是文件读写,所以它天生线程不安全。Google曾经想要对其进行一番扩展以令其实现线程安全读写,但最终以失败告终。后来于是有了民间替代方案,详细可以参考GitHub上这个项目。
笔者本身对SharedPreferences是否线程安全是没有需求的,我主要是觉得它——
限、制、太、多!使、用、太、麻、烦!

吐槽及预期

// get it
SharedPreferences p = mContext.getSharedPreferences("Myprefs", Context.MODE_PRIVATE);
// or
p = PreferenceManager.getDefaultSharedPreferences(mContext);// read
p.getString("preference_key", "default value");// write
p.edit().putString("preference_key", "new value").commit();
// or
p.edit().putString("preference_key", "new value").apply();

这里演示了String类型的情况,其他也是类似。
以上就是SharedPreferences的基本使用情况了,足以应付绝大部分情况,看上去也就那么几行,挺简单、挺好用的嘛!
那好,我们现在来看一下它究竟有哪些短板。

限制之一,使用之前必须拿到Context:

// get it
SharedPreferences p = mContext.getSharedPreferences("Myprefs", Context.MODE_PRIVATE);
// or
p = PreferenceManager.getDefaultSharedPreferences(mContext);

这里展示了两种方式,第一种的优势是可以自定义名称,并且如果需要的话可以指定全局读写(虽然Google不推荐用SharedPreferences来跨应用读写,相关字段早就被置上了deprecated),如果不需要则纯粹成了消耗多余体力的代码。
而且,Context并不是永远都那么好拿的,所以有一种最简单粗暴的作法就是做一个自己的Application类像是这样:

public class App extends Application {private static Context sMe;public static Context getInstance() {return sMe;}@Overridepublic void onCreate() {super.onCreate();sMe = this;}
}

但是杀鸡焉用牛刀,你做这样一个全局可得的ApplicationContext本就是为了不时之需,拿来用SharedPreferences,每次还得这样写App.getInstance(),逼格太低又很累啊。

限制之二,读值为什么会要这么多代码:

// read
p.getString("preference_key", "default value");

初看上去,这似乎是无比正常的代码:"default value"的存在确保了你永远可以取到值,但问题就出在这个"default value"上了,在某种情况下,你需要取某个值的地方很多,而且全都可能还没有初始化过,也就是说在这些地方实际第一次处理时使用到值的是"default value",假如某一天"default value"值需要变更,你就要细心谨慎地把每个地方都改一轮了。

限制之三,写值代码也很多:

// write
p.edit().putString("preference_key", "new value").commit();
// or
p.edit().putString("preference_key", "new value").apply();

先拿到Editor内部类,再操作,最后再提交,虽然IDE自带补全功能,但补全三次也不是那么方便吧?源码中的说法是,“so you can chain put calls together.”,因为每次putXXX()操作后仍旧返回同一个Editor内部类对象,所以你能一次性put许多下最后再提交。可实际情况中使用到链式调用的机会还是挺少的,毕竟很难出现Web上那种出现一整个表单给用户填写,最后一次性提交的情况。

总的来说,在不同的地方重复获取SharedPreferences是没有必要的,可以拿一个单例来解决;读值和写值太累赘了,要做下封装……
不,这还不够,作为一个名有追求的工程师——
我们需要一个强有力的Library来解决这些问题,力争达到一经写就,永久受益的效果。

常规解决方案

一般是做一个单例工具类,然后简单封装一下方法,这里截取了一下Notes中的部分代码如下:

/*** Created by lgp on 2014/10/30.*/
public class PreferenceUtils{private SharedPreferences sharedPreferences;private SharedPreferences.Editor shareEditor;private static PreferenceUtils preferenceUtils = null;public static final String NOTE_TYPE_KEY = "NOTE_TYPE_KEY";public static final String EVERNOTE_ACCOUNT_KEY = "EVERNOTE_ACCOUNT_KEY";public static final String EVERNOTE_NOTEBOOK_GUID_KEY = "EVERNOTE_NOTEBOOK_GUID_KEY";@Inject @Singletonprotected PreferenceUtils(@ContextLifeCycle("App") Context context){sharedPreferences = context.getSharedPreferences(SettingFragment.PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);shareEditor = sharedPreferences.edit();}public static PreferenceUtils getInstance(Context context){if (preferenceUtils == null) {synchronized (PreferenceUtils.class) {if (preferenceUtils == null) {preferenceUtils = new PreferenceUtils(context.getApplicationContext());}}}return preferenceUtils;}public String getStringParam(String key){return getStringParam(key, "");}public String getStringParam(String key, String defaultString){return sharedPreferences.getString(key, defaultString);}public void saveParam(String key, String value){shareEditor.putString(key,value).commit();}......
}

可以看到其思想还是挺简单的,基本上对于限制一二三全都照顾到了。
对于限制一,因为是单例,只要明确这个类已经初始化过一次了,后面就可以这样来获取实例PreferenceUtils.getInstance(null)——必须说明这是一种取巧的手段,而且看上去非常丑陋——所以说不需要依赖Context(另外我们还可以增加对于resId的支持,让这种方式成为可能getStringParam(int resId)只要在这个类中持有Context就能做到——但要注意为防内存泄漏应给这个类传ApplicationContext);关键是限制二的解决并不漂亮,因为不同的设置项的default值多数情况下是不一样的,所以还是提供了一个二参方法getStringParam(String key, String defaultString),本质上并没有解决。

不过不管怎样,我们的Library LitePreferences最起码要包含以上这个工具类的全部功能,然后再谈突破。

极致简约

既然是个单例,那么在使用之前就必须调用getInstance()了,像是这样:

LitePrefs.getInstance(mContext).getInt(R.string.tedious);

在这行代码中,如果LitePrefs已经初始化过一次了,那么中间的getInstance(mContext)纯粹就是毫无意义。我们希望代码简约成这样:

LitePrefs.getInt(R.string.tedious);

要达到这样的效果,只需让getInt()是一个静态方法即可。直接包装一层:

public static int getInt(int resId) {return  getInstance().getIntLite(resId);
}

为什么这里的getInstance()无参?因为LitePrefs构造方法是这样的:

private LitePrefs() {}

无参,什么也不做。对于这个类的初始化全都剥离到一个专门的初始化方法中去了。这意味着要使用这个类之前,必须先初始化。它们看上去像是这样:

private boolean valid = false;public static void init(Context ctx) {getInstance().initLite(ctx);
}public void initLite(Context ctx) {// do something to initialize valid = true;
}private void checkValid() {if (!valid) {throw new IllegalStateException("this should only be called when LitePrefs didn't initialize once");}}

记得用一个标志位来保障工具类已经初始化过。
使用这种方式,所有的操作都可以简化为LitePrefs.静态方法()。

支持文件配置

完成之后,我们的Library会拥有这样的初始化技能:

        try {LitePrefs.initFromXml(context, R.xml.prefs);} catch (IOException | XmlPullParserException e) {e.printStackTrace();}

支持文件配置不仅会让配置变得很方便,同时也绕过了限制二:依常理考虑,一个设置项的默认值应该是惟一的。那么,如果在第一次启动应用时写一次初始值到SharedPreferences中,那么今后取值的时候不就永远有值了吗?那么上面那种单参封装也就可以一直正常使用了。

既然要用文件读写,那就开搞吧,很容易想到使用一个xml文件来放配置项像是这样:

<?xml version="1.0" encoding="utf-8"?>
<prefs name="liteprefs"><pref><key>preference_key</key><def-value>default value</def-value><description>Write some sentences if you want,the LitePrefs parser will not parse the tag "description"</description></pref><pref><key>boolean_key</key><def-value>false</def-value></pref><pref><key>int_key</key><def-value>233</def-value></pref><pref><key>float_key</key><def-value>3.141592</def-value></pref><pref><key>long_key</key><def-value>4294967296</def-value></pref><pref><key>String_key</key><def-value>this is a String</def-value></pref>
</prefs>

由于xml解析器由我们自己来写,所以非常自由。这里attribute"name"中写上了对应的SharedPreferences使用的name。tag也是各种随意。而且多写几个不解析的tag用来在配置文件中添加说明也没有问题,像是上面的"<description>","</description>"。
基本数据类型全都可以很容易写出来,处理也容易,就是Set<String>不是太好处理,但SharedPreferences中这个支持用到的场合还是非常少的,目前我在Android源码中从未见过使用的例子。

考虑一个问题:上面怎么说也有五种类型的数据,我们要怎么读?只有两个tag显然不足以判断这一项的具体类型是int还是String,难道我们要加一个tag专门来区分吗?
虽然可以这样做,但这样写model类又会是老大难的问题——要写一个model类让它持有标志类型的flag,再加上持有五种类型的域?这也太恐怖了吧!

话说回来,写入配置到xml这一步真的是必要的吗?
因为SharedPreferences要写过之后才有值,所以我们想要在第一次运行应用时读配置文件然后把值写进xml,之后运行则不再需要进行这样的操作——这就是原定计划了,但这其实是存在漏洞的,漏洞出在SharedPreferences中的两个方法上:remove(String key)clear()
这两个方法会把值清空,用户来一发恢复默认设置的时候就是它们登场的时候。

既然如此,我们更改计划:应用启动时读取配置文件并持有这些信息,在读Preference项的时候,如该项未设置则返回配置文件中的默认值
这样一来,无须考虑写文件操作的情况下,我们读文件时条件也可放宽了:根本就不需要知道Preference的数据类型,全部用String类型保存就好,编程者为正确使用它们而负责

我们用一个Pref类作为Preference项的模型,这样设计:

 public class Pref {public String key;/*** use String store the default value*/public String defValue;/*** use String store the current value*/public String curValue;/*** flag to show the pref has queried its data from SharedPreferences or not*/public boolean queried = false;public Pref() {}public Pref(String key, String defValue) {this.key = key;this.defValue = defValue;}public Pref(String key, int defValue) {this.key = key;this.defValue = String.valueOf(defValue);}.......public int getDefInt() {return Integer.parseInt(defValue);}public String getDefString() {return defValue;}.......public int getCurInt() {return Integer.parseInt(curValue);}public String getCurString() {return curValue;}.......public void setValue(int value) {curValue = String.valueOf(value);}public void setValue(String value) {curValue = value;}......

以上代码片段展示了对于int及String类型的处理,用一个defValue保存该Pref项的默认值;用queried标志是否该Pref曾经进行过查询,假如有,那么其实际值保存在curValue之中。通过这样的处理,每一个Preference项最多只会查询一次。

所以,解析器可以非常简单地写成像是这样:

public class ParsePrefsXml {private static final String TAG_ROOT = "prefs";private static final String TAG_CHILD = "pref";private static final String ATTR_NAME = "name";private static final String TAG_KEY = "key";private static final String TAG_DEFAULT_VALUE = "def-value";public static ActualUtil parse(XmlResourceParser parser)throws XmlPullParserException, IOException {Map<String, Pref> map = new HashMap<>();int event = parser.getEventType();Pref pref = null;String name = null;Stack<String> tagStack = new Stack<>();while (event != XmlResourceParser.END_DOCUMENT) {if (event == XmlResourceParser.START_TAG) {switch (parser.getName()) {case TAG_ROOT:name = parser.getAttributeValue(null, ATTR_NAME);tagStack.push(TAG_ROOT);if (null == name) {throw new XmlPullParserException("Error in xml: doesn't contain a 'name' at line:"+ parser.getLineNumber());}break;case TAG_CHILD:pref = new Pref();tagStack.push(TAG_CHILD);break;case TAG_KEY:tagStack.push(TAG_KEY);break;case TAG_DEFAULT_VALUE:tagStack.push(TAG_DEFAULT_VALUE);break;
//                    default:
//                        throw new XmlPullParserException(
//                                "Error in xml: tag isn't '"
//                                        + TAG_ROOT
//                                        + "' or '"
//                                        + TAG_CHILD
//                                        + "' or '"
//                                        + TAG_KEY
//                                        + "' or '"
//                                        + TAG_DEFAULT_VALUE
//                                        + "' at line:"
//                                        + parser.getLineNumber());}} else if (event == XmlResourceParser.TEXT) {switch (tagStack.peek()) {case TAG_KEY:pref.key = parser.getText();break;case TAG_DEFAULT_VALUE:pref.defValue = parser.getText();break;}} else if (event == XmlResourceParser.END_TAG) {boolean mismatch = false;switch (parser.getName()) {case TAG_ROOT:if (!TAG_ROOT.equals(tagStack.pop())) {mismatch = true;}break;case TAG_CHILD:if (!TAG_CHILD.equals(tagStack.pop())) {mismatch = true;}map.put(pref.key, pref);break;case TAG_KEY:if (!TAG_KEY.equals(tagStack.pop())) {mismatch = true;}break;case TAG_DEFAULT_VALUE:if (!TAG_DEFAULT_VALUE.equals(tagStack.pop())) {mismatch = true;}break;}if (mismatch) {throw new XmlPullParserException("Error in xml: mismatch end tag at line:"+ parser.getLineNumber());}}event = parser.next();}parser.close();return new ActualUtil(name, map);}
}

这里解析完成最后返回的ActualUtil是一个实际操作SharedPreferences的基础工具类,它的逻辑也很简单,像是这样:

public class ActualUtil {private int editMode = LitePrefs.MODE_COMMIT;private String name;private SharedPreferences mSharedPreferences;private Map<String, Pref> mMap;public ActualUtil(String name, Map<String, Pref> map) {this.name = name;this.mMap = map;}public void init(Context context) {mSharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE);}public void setEditMode(int editMode) {this.editMode = editMode;}public void putToMap(String key, Pref pref) {mMap.put(key, pref);}private void checkExist(Pref pref) {if (null == pref) {throw new NullPointerException("operate a pref that isn't contained in data set,maybe there are some wrong in initialization of LitePrefs");}}private Pref readyOperation(String key) {Pref pref = mMap.get(key);checkExist(pref);return pref;}public int getInt(String key) {Pref pref = readyOperation(key);if (pref.queried) {return pref.getCurInt();} else {pref.queried = true;int ans = mSharedPreferences.getInt(key, pref.getDefInt());pref.setValue(ans);return ans;}}public boolean putInt(String key, int value) {Pref pref = readyOperation(key);pref.queried = true;pref.setValue(value);if (LitePrefs.MODE_APPLY == editMode) {mSharedPreferences.edit().putInt(key, value).apply();return true;}return mSharedPreferences.edit().putInt(key, value).commit();}......
}

可扩展性

无扩展性、泛用性不够的代码只能作为一次性使用。

UML

我们的结构如图中所示,ActualUtil持有SharedPreferences,实际完成读写操作,ParsePerfsXml提供解析方法将xml配置文件解析成相应的ActualUtil,而提供给用户的实际操作类则为LitePrefs。
看上去抽象程度还算不错,当我们需要针对项目特性定制的时候只需要继承LitePrefs就可以……问题就出在这里,LitePrefs是个单例

    private static volatile LitePrefs sMe;private LitePrefs() {}public static LitePrefs getInstance() {if (null == sMe) {synchronized (LitePrefs.class) {if (null == sMe) {sMe = new LitePrefs();}}}return sMe;}

因为是单例,所以LitePrefs的构造方法为private,这保障了它不会在类外部被创建。但这也同时使得其无法派生出子类。这可不是一件好事。出于这个原由,我们特别设计一个不标准的单例BaseLitePrefs用于扩展:

    private static volatile BaseLitePrefs sMe;protected BaseLitePrefs() {}public static BaseLitePrefs getInstance() {if (null == sMe) {synchronized (BaseLitePrefs.class) {if (null == sMe) {sMe = new BaseLitePrefs();}}}return sMe;}

因为将访问权限修改为了protected,所以这个类可以被顺利继承,虽然损失了一点严谨性,但这完全值得。

现在,我们可尝试着写一个子类看看:

public class MyLitePrefs extends BaseLitePrefs {public static final String THEME = "choose_theme_key";public static void initFromXml(Context context) {try {initFromXml(context, R.xml.prefs);} catch (IOException | XmlPullParserException e) {e.printStackTrace();}}public static ThemeUtils.Theme getTheme() {return ThemeUtils.Theme.mapValueToTheme(getInt(THEME));}public static boolean setTheme(int value) {return putInt(THEME, value);}
}

转载于:https://www.cnblogs.com/holyday/p/7424104.html

使用SharedPreferences进行数据存储相关推荐

  1. Android:使用SharedPreferences进行数据存储

    使用SharedPreferences进行数据存储 目录 访问SharedPreferences中的数据 案例: string.xml文件 main.xml布局文件 SpActivity 如何访问其他 ...

  2. 九、使用SharedPreferences进行数据存储

    原文地址为: 九.使用SharedPreferences进行数据存储 很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的QQ,用户可以设置是否允许陌生人添加自己为好友.对于软件配置参 ...

  3. Android数据存储五种方式总结

    1 使用SharedPreferences存储数据     2 文件存储数据       3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解 ...

  4. Android实现数据存储技术

    本文介绍Android中的5种数据存储方式. 数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是: 1 使用SharedPreferences存储数据 2 ...

  5. Android Learning:数据存储方案归纳与总结

    前言 最近在学习<第一行android代码>和<疯狂android讲义>,我的感触是Android应用的本质其实就是数据的处理,包括数据的接收,存储,处理以及显示,我想针对这几 ...

  6. Android基础——数据存储

    2019独角兽企业重金招聘Python工程师标准>>> 第一种: 使用SharedPreferences存储数据 SharedPreferences是Android平台上一个轻量级的 ...

  7. android mysql储存动态数据_Android数据存储五种方式总结

    本文介绍Android平台进行数据存储的五大方式,分别如下: 下面详细讲解这五种方式的特点 第一种: 使用SharedPreferences存储数据 适用范围:保存少量的数据,且这些数据的格式非常简单 ...

  8. Android数据存储几种方式用法总结

    Android数据存储几种方式用法总结 1.概述 Android提供了5种方式来让用户保存持久化应用程序数据.根据自己的需求来做选择,比如数据是否是应用程序私有的,是否能被其他程序访问,需要多少数据存 ...

  9. Android之本地数据存储(一):SharedPreferences

    所有的应用程序都必然涉及数据的输入与输出.在Android系统中,主要有五种数据存储模式: 1 . Sharedferences:Sharedferences是一种轻型的数据存储方式,本质上是基于XM ...

最新文章

  1. LeetCode简单题之检查整数及其两倍数是否存在
  2. .Net转Java自学之路—Hibernate框架篇三(查询方式)
  3. springboot脚本启动bat_SpringBoot系列(1)基础入门
  4. C++union 联合
  5. suList() 和 asList()
  6. flowable画图教程_JeeGit企业级快速开发平台-JeeSite4 Flowable入门教程
  7. redis——redis主从复制
  8. 斗地主AI算法——第三章の数据处理
  9. Linux下 查看网络连接状态的命令是,查看Linux操作系统下的网络连接状态命令
  10. (16)verilog 条件编译(FPGA不积跬步101)
  11. win10 mysql登录密码忘了_64位 windows10,MYSQL8.0.13重置密码(忘记密码或者无法登录)...
  12. 遇到问题你的PIN不可用,请单击以重置和bitlocker恢复密匙
  13. mysql进阶(十九)SQL语句如何精准查找某一时间段的数据
  14. ds数据与mysql_比较CCDS数据库和R包内置数据集的差异
  15. Factory IO仿真工厂与西门子博途软件联动仿真
  16. python中 什么意思_请问python中%代表什么意思?
  17. 聊天机器人:DeepMind的sparrow
  18. 蓝桥杯 算法训练 ALGO-128 Cowboys 递推、动态规划
  19. 《Linux命令行与shell脚本编程大全》笔记一
  20. mysql向表中插中文显示,针对mysql数据库无法在表中插入中文字符的解决方案(彻底解决jav...

热门文章

  1. JavaScript学习笔记——underscore操作对象的方法
  2. 如何使用ASINetWorkQueue下载实现
  3. HttpModule HttpHandle
  4. [Android]为指定的应用创建桌面快捷方式
  5. Xcode 3.2.5免证书开发调试[转]
  6. 汇编--INT 10H功能
  7. Pycharm 导入 Python 包、模块
  8. [My B.S paper draft]我的本科答辩论文草稿
  9. ThinkPHP 的一些知识
  10. Fedora相关(一)