SharedPreferences详解
来源:互联网 发布:读屏软件安卓版 编辑:程序博客网 时间:2024/05/29 17:23
SharedPreferences
SharedPreferences在文件中以键值对形式保存数据,适合保存少量数据。使用SharedPreferences可以通过下面两种方式:
通过Context的getSharedPreferences()
通过Activity的getSharedPreferences()
通过Context的方式是当前App都可以访问或者有相同user ID的其他App也可以访问,通过Activity是只能当前这个activity对象访问,并且这个SharedPreferences的文件名是activity的类名。
有几种操作模式:
Context.MODE_PRIVATE
Context.MODE_WORLD_READABLE
Context.MODE_WORLD_WRITEABLE
Context.MODE_MULTI_PROCESS
第一种是默认的,后面三个被废弃了,分别表示可以被其他的App读取和写入以及多进程中访问Preferences时设置会在修改时检查避免同时修改文件,有的提到了MODE_APPEND,这个是用在openFileOutput中的,文件存在会在文件后面追加而不是覆盖,Preferences没有这个标志,但实际上Preferences也是对文件的操作,只不过的相关操作中没有用到。
其中MODE_PRIVATE表示是只能被当前App访问或者被有相同user ID的App们访问,相同的user ID表示在manifest中设置android:sharedUserId=”com.my.app”,并且使用相同的签名打包,获取方法方法:
Context ctx = createPackageContext(“com.my.app.first”, CONTEXT_RESTRICTED);
prefs = context.getSharedPreferences(“PreferencesName”, Context.MODE_PRIVATE);
虽然可以用但是可能还是会有问题,B如果打算获取A的Preferences,有可能只能获取到B自己的,获取不到A的,必须在调用B自己的getSharedPreferences()之前先调用获取A的getSharedPreferences(),(具体参见How to read other app’s SharedPreferences (same user ID)?),
下面看一下具体的实现:
Context的实现类是ContextImpl,SharedPreferences的实现类是SharedPreferencesImpl,
ContextImpl中的getSharedPreferences():
@Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { if (sSharedPrefs == null) { sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); } final String packageName = getPackageName(); ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); sSharedPrefs.put(packageName, packagePrefs); } // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } sp = packagePrefs.get(name); if (sp == null) { File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); packagePrefs.put(name, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. sp.startReloadIfChangedUnexpectedly(); } return sp; }
sSharedPrefs 是一个静态变量,是一个Map,保存包名和包名对应的SharedPreferences对象,不存在就创建,保证只有一个实例,其中SharedPreferences的名字可以为null,也就是getSharedPreferences(String name, int mode)中可以name==null。
SharedPreferencesImpl中
SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false; mMap = null; startLoadFromDisk(); }
mFile 是保存Preferences中数据的file,mBackupFile 是mFile的备份,mMode 就是getSharedPreferences中的mode,mMap 是Preferences中的键值对,startLoadFromDisk是读取数据,
private void startLoadFromDisk() { synchronized (this) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { public void run() { synchronized (SharedPreferencesImpl.this) { loadFromDiskLocked(); } } }.start(); }
使用了线程锁,保证同时只有一个线程访问,然后子线程调用loadFromDiskLocked(),
//部分删减 private void loadFromDiskLocked() { //...... if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } //...... try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16*1024); map = XmlUtils.readMapXml(str); } //..... } } mLoaded = true; if (map != null) { mMap = map; mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } //...... }
其中数据从mBackupFile读取出来,赋值给mMap,是一次全部读取,所以Preferences不适合保存大量数据,否则效率很慢,而使用mBackupFile是为了在提交操作写mFile文件时读取的数据没问题,虽然数据可能是旧的,这也符合逻辑,如果采用异步提交,立即读取数据就有可能还是旧的。
继续往下看,看读取和写入操作:
读取
public int getInt(String key, int defValue) { synchronized (this) { awaitLoadedLocked(); Integer v = (Integer)mMap.get(key); return v != null ? v : defValue; } }
以getInt为例,同样加了线程锁,保证同时只有一个线程访问Preferences,直接从mMap读取,为什么不是重新读文件然后获取就必须要讲讲提交数据了,这里猜测可能是在put数据或commit时赋值给了mMap,下面看看是不是。
提交操作都是封装在Editor类中,实现类是EditorImpl,以putInt为例:
public Editor putInt(String key, int value) { synchronized (this) { mModified.put(key, value); return this; } }
同样是加了同步锁,注意这里是保存到了mModified中,这样给mMap赋值可能就是在commit中,提交操作设计的方法有几个分别是apply,commit,commitToMemory,apply和commit都调用了commitToMemory,
//部分删减 private MemoryCommitResult commitToMemory() { synchronized (SharedPreferencesImpl.this) { mcr.mapToWriteToDisk = mMap;//提交写文件时将mcr.mapToWriteToDisk中保存的数据写入文件 mDiskWritesInFlight++; synchronized (this) { for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. if (v == this || v == null) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mMap.put(k, v); } } mModified.clear(); } } return mcr; }
这里面进行了赋值,在分别看看apply和commit,
public void apply() { final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr); } public boolean commit() { MemoryCommitResult mcr = commitToMemory(); SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } notifyListeners(mcr); return mcr.writeToDiskResult; }
可以看出基本上就是SharedPreferencesImpl.this.enqueueDiskWrite的参数不一样,一个传了postWriteRunnable,一个是null,看一下enqueueDiskWrite:
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr); } synchronized (SharedPreferencesImpl.this) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; final boolean isFromSyncCommit = (postWriteRunnable == null); // Typical #commit() path with fewer allocations, doing a write on // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (SharedPreferencesImpl.this) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); }
其中isFromSyncCommit这个变量控制,当postWriteRunnable参数不为空时为false,会执行QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);在子线程中执行写文件操作,否则就在当前线程(主线程)执行写操作,这样commit就是同步提交,apply是异步提交。还有writeToFile写文件:
//部分删减 private void writeToFile(MemoryCommitResult mcr) { // Rename the current file so it may be used as a backup during the next read if (mFile.exists()) { if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) {//提交之前先备份 Log.e(TAG, "Couldn't rename file " + mFile+ " to backup file " + mBackupFile); mcr.setDiskWriteResult(false); return; } } else { mFile.delete(); } } // Attempt to write the file, delete the backup and return true as atomically as // possible. If any exception occurs, delete the new file; next time we will restore // from the backup. try { FileOutputStream str = createFileOutputStream(mFile);//创建一个新文件 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);//将mcr.mapToWriteToDisk中的数据写入 FileUtils.sync(str); str.close(); //这里用到了getSharedPreferences的mode ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); // Writing was successful, delete the backup file if there is one. mBackupFile.delete();//已经提交成功,需要删除备份文件,否则再读取数据可能就是从mBackupFile中读取 mcr.setDiskWriteResult(true); return; } catch (XmlPullParserException e) { Log.w(TAG, "writeToFile: Got exception:", e); } catch (IOException e) { Log.w(TAG, "writeToFile: Got exception:", e); } // Clean up an unsuccessfully written file if (mFile.exists()) { if (!mFile.delete()) { Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } mcr.setDiskWriteResult(false); }
- SharedPreferences 详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- SharedPreferences详解
- Android项目之广播(BroacastReceiver)与服务(Service)
- 函数变换法生成随机变量的原理梳理
- 剑指offer|面试题5:从尾到头打印链表(Java代码)
- -D_REENTRANT编译选项的作用
- matlab之fopen,fread,fclose,fwrite
- SharedPreferences详解
- CentOS 7搭建LAMP环境(二)
- HTML基础点
- Nginx下css的链接问题
- 任意进制的转换
- System.exit(0)和System.exit(1)区别 .
- 分享10款优雅动人的HTML5教程及源码
- Java 错误:java.io.EOFException
- 网站加速方案