Preference翻译为偏好,但实际上它可以算是Setting(设置)的别名。两种叫法给人的差异在于针对的对象不同:设置更倾向于设备的属性,修改设置便是改变设备的功能;偏好则倾向于用户的性格,用户基于其个人的偏好来来形成设备的差异化。但是总体而言,它们是一致的,都是通过用户的需求改变设备的体验。
在Android的开发过程中,会在使用的API中见到很多名字中带有Preference的类和接口,此篇文章就来介绍一下这些“*Prefere*
”的功能和用途。
在Android提供API中,带有Preference的其实主要分为两类:一类是android.content
包下的SharedPreferences
,另一类则是android.preference
包下的Preference
。它们分别实现不同功能,却又相互联系合作完成对Android程序的控制。
SharedPreferences简介
SharedPreferences
是以复数形式存在,因为在Android中它是用来存储键值对(Key-Value Pair)数据的集合。API中包含了多个方法来方面读取相应类型的数据:
1 2 3 4 5 6 |
String getString(String key, String defValue); Set<String> getStringSet(String key, Set<String> defValues); int getInt(String key, int defValue); long getLong(String key, long defValue); float getFloat(String key, float defValue); boolean getBoolean(String key, boolean defValue); |
这也表明SharedPreferences
所能存储的类型被限定在了String
、int
、long
、float
、boolean
这些基础数据类中,唯一的集合类型也只是Set
,而它看起来更像是用来作为不能有重复数据的数组。
还可以单纯检查是否包换指定的主键,或者干脆将所有的键值对的Map
获取出来:
1 2 |
boolean contains(String key); Map<String, ?> getAll(); |
Android系统的工程师在设计SharedPreferences
的时候,把读取的功能放在了SharedPreferences
上,而把写回的功能实现在了其内嵌的Editor
类上,通过调用edit()
方法来获得一个写入器。这样就很容易实现一个只读的对象,只要返回一个空指针或非可用的Editor对象就可以了。
1 2 3 4 5 6 7 |
Editor putString(String key, String value); Editor putStringSet(String key, Set<String> values); Editor putInt(String key, int value); Editor putLong(String key, long value); Editor putFloat(String key, float value); Editor putBoolean(String key, boolean value); Editor remove(String key); |
SharedPreferences
还有一个内嵌接口OnSharedPreferenceChangeListener
,实现它唯一的方法onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
并通过以下方法添加在SharedPreferences
对象上就可以监听其上键值对的增加、删除和修改:
1 2 |
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); |
SharedPreferences的在Android系统中的实现
SharedPreferences
和内嵌的Editor
其实都只是接口定义而已,并没有实现任何方法。它只是用来制定了一个存储键值对的协议,具体的实现方式和存储形式可以是任意的。在Android系统中,它默认以XML格式的文件来存储这些数据,实现的类则是SharedPreferencesImpl
。
下边就是所保存的XML文件的基本格式,它以数据类型作为XML元素的标签,主键(key)是标签name属性的值,而主键对应的值则作为value属性的值。但如果是String类型则作为标签下的content,这样就不用转义引号也能更好的处理换行。另外对于null
值存储的结构也比较特殊,它以null
为标签,只有一个name
属性,没有其他内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="Name">Ider</string> <boolean name="Android" value="true" /> <set name="Subsites"> <string>code.iderzheng.com</string> <string>blog.iderzheng.com</string> <string>manual.iderzheng.com</string> </set> <int name="VersionCode" value="21" /> <long name="VersionNumber" value="1355" /> <float name="Version" value="5.0" /> <null name="Null" /> </map> |
Android系统会把该XML文件存储在/data/data/(packagename)/shared_prefs/
下,每一个XML文件就对应一个SharedPreferences
对象(实际是SharedPreferencesImpl
对象)。但是SharedPreferences
是接口不能用来实例化对象,而SharedPreferencesImpl
是系统隐藏类,不能被直接访问使用,其构造函数也只是包可见。所以不能通过new来构建一个SharedPreferences
,必须通过Context
提供的getSharedPreferences(String, int)
来获得实例。
该方法的第一个参数是指定XML文件名(不包含“.xml”后缀)的字符串,方法会去读取出对应的文件,创建一个SharedPreferences
对象。第二个参数指定文件的访问权限,共有4中可选模式,从API 17开始基于安全的考虑,MODE_WORLD_READABLE
和MODE_WORLD_WRITEABLE
已经被废弃使用,只有MODE_PRIVATE
和MODE_MULTI_PROCESS
可使用,一般情况下指定MODE_PRIVATE
即可。
对于从SharedPreferences
中读取指定主键的值是十分快的,因为所有存在XML的键值对信息全都被读取被存储在了SharedPreferences
对象中的Map
成员变量里,所以读取都是基于内存访问。使用Editor
写回到文件是避不开IO操作的,所以使用commit()
提交修改还是会花费一些时间。考虑到这点,Android在API 9里引进了apply()
方法来异步地将修改后的内容写回到文件,当然在写回前也会先更新内存中的键值对信息保证读取到的时最新的内容。
既然写回可以是异步的,那么多次调用getSharedPreferences(String, int)
获得多个SharedPreferences
赋值给不同的变量,假如一个变量做了修改,其他的对象不是会出现内容不一致的情况。其实这种情况并不会出现,因为所有创建出来的SharedPreferences
都被存储在ContextImp
的一个静态成员变量中:
1 2 3 4 |
/** * Map from package name, to preference name, to cached preferences. */ private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs; |
这是一个从程序的Package名字到XML文件名再到SharedPreferences
对象的二级Map
。所以每次调用getSharedPreferences(String, int)
得到的对象其实都是同一个实例,修改操作也都就作用在同一段内存中保证了所有访问的一致性。apply()
方法也会自动将所有修改排入队列一一写回文件从而不会因为顺序的错误而造成意料之外的错误覆盖。所以因为这个缓存机制的存在,多次调用getSharedPreferences(String, int)
是非常效率的。而写回时则推荐使用apply()
实现异步操作,而不要用commit()
阻碍主线程。
SharedPreferences的使用和示例
一般而言SharedPreferences
的名字和主键名都是固定的,所以可以定义一些final
的字符串变量来保存这些名字,在读取和写回时都使用这些常熟变量。对于之前展示的XML对应的代码就如下边所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
private static final String IDER_PREFERENCE = "ider-preference"; private static final String IDER_PREFERENCE_KEY_NAME = "Name"; private static final String IDER_PREFERENCE_KEY_SUBSITES = "Subsites"; private static final String IDER_PREFERENCE_KEY_IS_ANDROID = "Android"; private static final String IDER_PREFERENCE_KEY_VERSION = "Version"; private static final String IDER_PREFERENCE_KEY_VERSION_CODE = "VersionCode"; private static final String IDER_PREFERENCE_KEY_VERSION_NUMBER = "VersionNumber"; private static final String IDER_PREFERENCE_KEY_NULL = "Null"; public void write(Context context) { final SharedPreferences spref = context.getSharedPreferences(IDER_PREFERENCE, MODE_PRIVATE); HashSet<String> strs = new HashSet<String>(); strs.add("blog.iderzheng.com"); strs.add("code.iderzheng.com"); strs.add("manual.iderzheng.com"); SharedPreferences.Editor editor = spref.edit(); editor.putString(IDER_PREFERENCE_KEY_NAME, "Ider"); editor.putStringSet(IDER_PREFERENCE_KEY_SUBSITES, strs); editor.putBoolean(IDER_PREFERENCE_KEY_IS_ANDROID, true); editor.putFloat(IDER_PREFERENCE_KEY_VERSION, 5.0f); editor.putInt(IDER_PREFERENCE_KEY_VERSION_CODE, 21); editor.putLong(IDER_PREFERENCE_KEY_VERSION_NUMBER, 1355); editor.putString(IDER_PREFERENCE_KEY_NULL, null); editor.apply(); } public void read(Context context) { final SharedPreferences spref = context.getSharedPreferences(IDER_PREFERENCE, MODE_PRIVATE); String name = spref.getString(IDER_PREFERENCE_KEY_NAME, ""); Set<String> strs = spref.getStringSet(IDER_PREFERENCE_KEY_SUBSITES, null); boolean isAndroid = spref.getBoolean(IDER_PREFERENCE_KEY_IS_ANDROID, false); float version = spref.getFloat(IDER_PREFERENCE_KEY_VERSION, 0); int versionCode = spref.getInt(IDER_PREFERENCE_KEY_VERSION_CODE, 0); long versionNumber = spref.getLong(IDER_PREFERENCE_KEY_VERSION_NUMBER, 0); boolean hasKey = spref.contains(IDER_PREFERENCE_KEY_NULL); } |
既然SharedPreferences
的名字是可以任意给定的,所以对于SharedPreferences
的使用就可以有非常的针对性创建不同的文件来存储不同的内容。比如可以以不同用户为名存放他们的偏好信息,可以根据不同界面保存布局信息、已访问的页码。Act