Android SharedPreferences 学习总结

简单的使用方法我就不说了,网上和书上都说的很详细。

注意:使用SharedPreferences是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。(参考:Android数据存储(总结篇))

否则会出现如下问题:

在这里我主要说一下共享数据的问题。既然叫shared了也有write属性,A应用就应该能够对B应用生成的SharedPreferences文件进行写操作,但是如果仅仅使用createPackageContext然后getSharedPreferences,修改完数据后commit,就会报错04-05 01:51:21.641: E/SharedPreferencesImpl(546): Couldn't rename file /data/data/com.kle.Demo.SharedPreference/shared_prefs/rw_SaveSettings.xml to backup file /data/data/com.kle.Demo.SharedPreference/shared_prefs/rw_SaveSettings.xml.bak

类似于SharedPreferences修改其它应用下面的xml文件

就是说A应用没有对B应用下的shared_prefs文件目录写权限,导致rename失败。具体流程如下(参考自:Android中的SharedPreferences陷阱)

将保存SharedPreferences的xml文件删除了,能够彻底删除对应的SharedPreferences吗?

一次开发过程中,一个功能是需要将程序缓存清除掉,包括SharedPreferences文件。
第一次做的方式是,把相关的文件删除。但是发现有问题:程序退出后,再次进入程序仍然能够读取到应该被删除掉的SharedPreferences的值,但是DDMS查看要删除的pref文件确实都不在了。
为什么文件都不在了还能够取到值呢?
换了另一种清除SharedPreferences的方式:使用SharedPreferences.Editor的commit方法。结果证明,这样做是能够起作用的,调用后不退出程序都马上生效。
既然这样,第一个反应就是取SharedPreferences是首先内存缓存中取的。那为什么重启程序都还能去得到呢?
就进入源代码中看了一下,有几下几个地方可以了解了解:

取SharedPreferences实际上是在ContextImpl这个类中完成的。
1、context.getSharedPreferences(pref_name, mode)的流程:
A 在sSharedPrefs这个map(同步的)中以pref_name为键取SharedPreferencesImp对象sp。如果sp不为空并且对应的pref文件未被异常修改,就返回这个对象。否则进入B。
B 如果sp为空,重新生成一个SharedPreferenceImp对象并且加入到sSharedPrefs这个map中。
C 同步的:从pref文件中解析出map对象并用之替换SharedPreferenceImp对象中原有的存放pref键值对的mMap成员对象。如果pref文件解析异常导致map为null,就保持原有对象而不替换。 如果备份的pref文件(…pref_name.xml.bak)存在,就使用备份文件。
D 返回SharedPreferenceImp对象sp。
注意:sSharedPrefs在程序中是静态的:private static final HashMap sSharedPrefs = new HashMap(); 如果退出了程序但Context没有被清掉,那么下次进入程序仍然可能取到本应被删除掉的值。

2、从SharedPreference中取值getString(String key, String defValue):
从SharedPreferencesImp对象的mMap成员对象中根据key取出相应的对象v。如果取得的对象v为空,返回默认对象defValue;否则,返回对象v。

3、commit过程:
A 在内存中提交,即用要提交的map去刷新已有的mMap对象。如果map对象中某个键的值指向editer对象自身,就代表要移除这个键值对。
B 将步骤A返回的MemoryCommitResult对象加入到写入本地的队列中,写入本地文件。这一步目前是在同一个线程中做的,因为性能表现得很良好。在写入文件前,如果同名文件已经存在,则会原文件重命名为备份文件名,如果写入成功,才删除bak备份文件。
C 通知SharedPreferences的监听状态改变了。返回提交是否内存成功的状态。

4、EditorImpl内部类:
内部有一个Map成员对象mModified,用来保存将要提交的pref键值。
apply方法与commit方法的区别:前者先提交到内存中,再异步写到文件,并且不需要返回写入成功与否的状态;后者同步写入内存和文件。

5、MemoryCommitResult内部类:
用来存放Editor提交到内存的返回状态,包括是否有键值改变、将要写入文件中的map对象,写入文件成功与否等。

总结一下:要想及时并安全清除SharedPreferences一定要使用Editor去clearcommit,不要直接暴力地删除其xml文件。

测试用的源代码(GBK编码):http://www.senseforce.com/share/sources/TestSharedPreferences.zip


已找到的解决方法:

1. 直接使用 java.io.File 来操作共享的xml数据文件(参考:SharedPreferences+File操作详解

以前只是在开发中使用,感觉没太注意它比较细的特性,再说在开发中很少用到比较细的特性,今天就把以前在开发中做的测试代码给贴出来吧:

SHAREDPREFERCENCES中文名为共享参数,大家应该耳熟能详了,它在ANDROID应用中主要的角色就是对其参数设置时做"活动",就像在外面WINDOWS中使用后缀为,INI格式保存的参数与在J2SE中使用PROPERTIES为后缀的格式保存参数一样,他在ANDROID系统中也是为了对其某属性或者小内容进行设置时所做的设置,只是它严格了它对读写权限的操作,在ANDROID中使用SHAREPREFERENCE时,你需要记住的是,它有四个权限供你操作,第一个是:MODE_APPEND只能对本应用说具有的权限,只是在写时是可以追加数据,如在已经的XML内容中追加XXX,但是他不提供其他应用对其生成的文件内容进行操作的权限,第二个是:MODE_PRIVATE也是只能针对本应用进行操作,但是他是不可以对其已经文件内容进行追加的,你每次执行它是,都会对其内容进行全部覆盖,这点请要注意,第三个是:MODE_WORLD_READABLE模式,此权限模式对其本应用生成的文件提供其他应用对其访问的操作权限,只是其他应用只能已读的方式对其进行操作,如果你想执行写的操作的话,你就会遭到拒绝,呵呵,如你真相通过其他应用来访问本应用通过SHAREDPREFERENCES生成的文件内容,请使用第四个:MODE_WORLD_WRITEABLE,简明的说他提供了其他应用对其本应用其文件的读写权限,这是你不但可以通过其他应用对其文件的读操作,也可以很方便的执行写操作,这也就使它在文件读取方面表现得很灵活多用,只是,不管怎么样,它最大的缺点就是只能对其很小的内容进行操作,不过这也是它相对来说比较好的优点,使用它更便捷与方面,如我们在做一些恶参数配置:网络端口,简单联系人属性,或者是提供某个参数的属性,我们都可以使用它来进行灵活的操作.

在上面我们谈论SharedPreferences后,下面我们继续来谈在ANDROID是怎么对其文件进行操作的,由于时间问题,我就简单介绍文件的写与读受权限模式的操作过程,我并没有贴上UI显示,只是使用ANDROIDTESTCASE类来做测试即可,在上面我们了解SHAREDPREFERENCES所具有的四个权限操作模式来实现对其参数的读写操作,以至于是否是允许不同应用之间的访问操作,在这里我们也不难知道,ANDROID里使用文件做读写的方式也是一样具有同等权限操作的,只是我们在操作读取时,SHAREDPREFERENCES使用的是getSharedPreferences(读|取文件名,权限模式),而在操作文件时是使用File流来操作,如写入与读取都会使用一个上下文对象获取Context.openInputStream(取文件名,权限模式),Context.openOutputSream(写文件名,权限模式),它同样可以设置MODE_APPEND,MODE_PRIVATE,MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE这是个权限模式来操作,以至于在操作过程中与SHAREDPREFERENCES的权限操作模式基本一样,所以在这里也就不在熬术,只是我在做相应的操作时,请注意,如要填写其路径时,也就是文件路径,我们不应该写绝对路径,因为这样操作会很不方便,推介大家使用相对路径,如:Environment.getExternalStorageDirectory();来获取其当前文件保存的环境路径,为什么要这么做呢,如我们在之前的ANDROID2.1中默认保存的路径为/sdcard,在ANDROID2.2中这把/sdcard路径映射成了/mnt/sdcard,即使在ANDROID3.0中或者更上使用其他的路径的话,只要使用环境适配路径方式就可以很容易的对其匹配了,这些在开发中的细节还是比较重要的,希望大家都能注意一下,在下就是在使用文件读的时候有个小细节我再说一下,就是在使用FileInputStream file = Context.openFileInputSreeam()后,读取到流我们可以先把它按批写入进内存:ByteArrayOutputStream outStream.writer(buffer,0,len);这里使用了缓冲流机制,而且还使用临时内存存储机制,让其数据在读取时会显得更流畅,记住在关闭时请不要忘了关闭缓冲流,还有就是在这里可能会考虑到编码的问题,如果前面我以UTF-8来写的,这里我们只需要以STRING方式取就行了:return new String(data);在我的源码你有详细记载,你可以下载我的源码自己去测试一下,我把每个权限至不同应用之间的访问都做了代码编写与详细测试,希望能给那些初学者们做更好的了解,下面是我部分源码: /**
     * 通过SharedPreferences保存文件
     */
    private OnClickListener save_file = new OnClickListener() {
 
  @Override
  public void onClick(View v) {
   // TODO Auto-generated method stub
   switch (i) {
   case 1:
    sharePrarams = getSharedPreferences(APPEND_PATH_FILE, MODE_APPEND);
    editor = sharePrarams.edit();
    editor.putInt("code", new Integer(code.getText().toString()));
    editor.putString("name", name.getText().toString());
    editor.putInt("age", new Integer(age.getText().toString()));
    editor.putString("education", education.getText().toString());
    editor.putString("address", address.getText().toString());
    editor.putString("selfIntrodcation", selfIntrodcation.getText().toString());
    editor.commit();
    break;
   case 2:
    sharePrarams = getSharedPreferences(APPEND_PATH_FILE, MODE_PRIVATE);
    editor = sharePrarams.edit();
    editor.putInt("code", new Integer(code.getText().toString()));
    editor.putString("name", name.getText().toString());
    editor.putInt("age", new Integer(age.getText().toString()));
    editor.putString("education", education.getText().toString());
    editor.putString("address", address.getText().toString());
    editor.putString("selfIntrodcation", selfIntrodcation.getText().toString());
    editor.commit();
    break;
   case 3:
    sharePrarams = getSharedPreferences(APPEND_PATH_FILE, MODE_READABLE);
    editor = sharePrarams.edit();
    editor.putInt("code", new Integer(code.getText().toString()));
    editor.putString("name", name.getText().toString());
    editor.putInt("age", new Integer(age.getText().toString()));
    editor.putString("education", education.getText().toString());
    editor.putString("address", address.getText().toString());
    editor.putString("selfIntrodcation", selfIntrodcation.getText().toString());
    editor.commit();
    break;
   case 4:
    sharePrarams = getSharedPreferences(APPEND_PATH_FILE, MODE_WRITEABLE);
    editor = sharePrarams.edit();
    editor.putInt("code", new Integer(code.getText().toString()));
    editor.putString("name", name.getText().toString());
    editor.putInt("age", new Integer(age.getText().toString()));
    editor.putString("education", education.getText().toString());
    editor.putString("address", address.getText().toString());
    editor.putString("selfIntrodcation", selfIntrodcation.getText().toString());
    editor.commit();
    break;
   }
  }
 };

/**
  * 1:读取其他应用以APPEND权限写入的数据:java.lang.NullPointerException
  * 2:读取其他应用以PRIVATE权限写入的数据:java.lang.NullPointerException
  * 3:读取其他应用以READABLE权限写入的数据:

  * 4:读取其他应用以WRATEABLE权限写入的数据:
  */
 public void readOtherAppFile() throws Exception{
  switch (i) {
  case 1:
   Log.i(TAG, APPEND_PATH);
   content = file.readOtherFile(APPEND_PATH);
   Log.i(TAG, content);
   break;
  case 2:
   Log.i(TAG, PRIVATE_PATH);
   content = file.readOtherFile(PRIVATE_PATH);
   Log.i(TAG, content);
   break;
  case 3:
   Log.i(TAG, READABLE_PATH);
   content = file.readOtherFile(READABLE_PATH);
   Log.i(TAG,content);
   break;
  case 4:
   Log.i(TAG, WREATABLE_PATH);
   content = file.readOtherFile(WREATABLE_PATH);
   Log.i(TAG, content);
   break;
  }
 }
 /**
  * 1:以APPEND权限写入其他应用以APPEND权限的文件
  * 2:以PRIVATE权限写入其他应用以APPEND权限的文件
  * 3:以READABLE权限写入其他应用以APPEND权限的文件
  * 4:以WRATEABLE权限写入其他应用以APPEND权限的文件
  */
 public void saveOtherAppendFile() throws Exception{
  Log.i(TAG, APPEND_PATH);
  switch (i) {
  case 1:
   content = "two for append data!";
   file.saveOtherAppendFile(APPEND_PATH, content);
   Log.i(TAG, APPEND_PATH+"|"+content);
   break;
  case 2:
   content = "two for private data!";
   file.saveOtherPrivateFile(APPEND_PATH, content);
   Log.i(TAG, APPEND_PATH+"|"+content);
   break;
  case 3:
   content = "two for readable data!";
   file.saveOtherReadableFile(APPEND_PATH, content);
   Log.i(TAG, APPEND_PATH+"|"+content);
   break;
  case 4:
   content = "two for writeable data!";
   file.saveOtherWriteableFile(APPEND_PATH, content);
   Log.i(TAG, APPEND_PATH+"|"+content);
   break;
  }
 }

2. A和B使用相同的sharedUserId(参考:Android权限之sharedUserId和签名)

    最近在做个东西,巧合碰到了sharedUserId的问题,所以收集了一些资料,存存档备份。

    安装在设备中的每一个apk文件,Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户都会被分配到一个属于自己的统一的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它)。用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。

通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样.

对于一个APK来说,如果要使用某个共享UID的话,必须做三步:

1、在Manifest节点中增加android:sharedUserId属性。

2、在Android.mk中增加LOCAL_CERTIFICATE的定义。

如果增加了上面的属性但没有定义与之对应的LOCAL_CERTIFICATE的话,APK是安装不上去的。提示错误是:Package com.test.MyTest has no signatures that match those in shared user android.uid.system; ignoring!也就是说,仅有相同签名和相同sharedUserID标签的两个应用程序签名都会被分配相同的用户ID。例如所有和media/download相关的APK都使用android.media作为sharedUserId的话,那么它们必须有相同的签名media。

3、把APK的源码放到packages/apps/目录下,用mm进行编译。

举例说明一下。

系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加android:sharedUserId="android.uid.system",然后在Android.mk中增加LOCAL_CERTIFICATE := platform。可以参见Settings等

系统中所有使用android.uid.shared作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.uid.shared",然后在Android.mk中增加LOCAL_CERTIFICATE := shared。可以参见Launcher等

系统中所有使用android.media作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.media",然后在Android.mk中增加LOCAL_CERTIFICATE := media。可以参见Gallery等。

另外,应用创建的任何文件都会被赋予应用的用户标识,并且正常情况下不能被其他包访问。当通过getSharedPreferences(String,int)、openFileOutput(String、int)或者openOrCreate Database(String、int、SQLiteDatabase.CursorFactory)创建一个新文件时,开发者可以同时或分别使用MODE_WORLD_READABLE和MODE_WORLD_RITEABLE标志允许其他包读/写此文件。当设置了这些标志后,这个文件仍然属于自己的应用程序,但是它的全局读/写和读/写权限已经设置,所以其他任何应用程序可以看到它。

关于签名:

build/target/product/security目录中有四组默认签名供Android.mk在编译APK使用:

1、testkey:普通APK,默认情况下使用。

2、platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。

3、shared:该APK需要和home/contacts进程共享数据。

4、media:该APK是media/download系统中的一环。

应用程序的Android.mk中有一个LOCAL_CERTIFICATE字段,由它指定用哪个key签名,未指定的默认用testkey.

 

对于使用eclipse编译的apk,可以使用signapk.jar来手动进行签名,其源码在build/tools/signapk下,编译后在out/host/linux-x86/framework/signapk.jar,也可以从网上下载。使用方法,以platform为例:java -jar ./signapk platform.x509.pem platform.pk8 input.apk output.apk  (platform.x509.pem platform.pk8在build/target/product/security获取)


另外还看到了这样的问题,没太看懂:http://groups.google.com/group/android-developers/browse_thread/thread/1d426975a7513124

你可能感兴趣的:(Android)