Android SharedPreferences解析
来源:互联网 发布:美国和日本知乎 编辑:程序博客网 时间:2024/05/07 06:19
转载请标明出处:
http://blog.csdn.net/zq2114522/article/details/50282293;
本文出自:【梁大盛的博客】
Android SharedPreferences解析
引:在使用SharedPreferences过程中,觉得Android这种轻量级的存储思想用起来挺方便的.本着知其然知其所以然的想法.尝试阅读SharedPreferences源码
入口函数
public SharedPreferences getPreferences(int mode) { return getSharedPreferences(getLocalClassName(), mode); }
public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (sSharedPrefs) { sp = sSharedPrefs.get(name); if (sp == null) { File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); sSharedPrefs.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; }
很简单的两个入口函数.
public SharedPreferences getPreferences(int mode)
定义在Activity.class类
public SharedPreferences getSharedPreferences(String name, int mode)
定义在ContextImpl.class类
两者之间的关系,getPreferences通过调用getSharedPreferences来实现.
public SharedPreferences getPreferences(int mode) { return getSharedPreferences(getLocalClassName(), mode); }
public String getLocalClassName() { final String pkg = getPackageName(); final String cls = mComponent.getClassName(); int packageLen = pkg.length(); if (!cls.startsWith(pkg) || cls.length() <= packageLen || cls.charAt(packageLen) != '.') { return cls; } return cls.substring(packageLen+1); }
getPreferences(int mode)函数通过调用getLocalClassName()传递的是类名进去.然而他们两区别就是这里了.
getSharedPreferences函数
getSharedPreferences函数返回的是一个SharedPreferencesImpl实例.使用过SharedPreferences的应该都知道,我们操作都是通过SharedPreferences实例进行操作的.
SharedPreferencesImpl继承SharedPreferences
ContextImpl.class有个sSharedPrefs静态变量.sSharedPrefs保存的是键值对.每当调用getSharedPreferences函数首先会用name查询sSharedPrefs里面有没保存对于的SharedPreferencesImpl实例.如果有那么直接返回name对于的SharedPreferencesImpl实例.如果没找到那么创建一个SharedPreferencesImpl实例并且已键值对的形式插入到sSharedPrefs.以便以后在通过name获取SharedPreferencesImpl实例.
- 也就说调用getSharedPreferences函数传递进来的name是一个索引.通过索引返回SharedPreferencesImpl实例.
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs = new HashMap<String, SharedPreferencesImpl>();
public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (sSharedPrefs) { //通过name查找SharedPreferencesImpl实例 sp = sSharedPrefs.get(name); if (sp == null) { //为空,第一次读取.没有根据name产生SharedPreferencesImpl实例 //getSharedPrefsFile()构造Xml文件File File prefsFile = getSharedPrefsFile(name); //创建SharedPreferencesImpl实例,默认在”/data/data/包名/shared_prefs/”目录里面创建XML文件 sp = new SharedPreferencesImpl(prefsFile, mode); //保存SharedPreferencesImpl实例到sSharedPrefs,name作为索引 sSharedPrefs.put(name, sp); return sp; } } //这部分忽略,Context.MODE_MULTI_PROCESS getSharedPreferences是用于跨线程 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;
接下来这三个很好理解,getSharedPrefsFile就是返回Xml文件的File.所以说SharedPreferences的实现是基于Xml文件.从这里可以看出.
- 例如:
- 会生成xml文件
- /data/data/com.dsliang.SharedPreferencesDemo/shared_prefs/ds.xml
public File getSharedPrefsFile(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }
private File getPreferencesDir() { synchronized (mSync) { if (mPreferencesDir == null) { mPreferencesDir = new File(getDataDirFile(), "shared_prefs"); } return mPreferencesDir; } }
private File getDataDirFile() { if (mPackageInfo != null) { return mPackageInfo.getDataDirFile(); } throw new RuntimeException("Not supported in system context"); }
SharedPreferencesImpl类
理解了SharedPreferencesImpl类基本对SharedPreferences就明白一大半了.
构造函数
SharedPreferencesImpl(File file, int mode) { //Xml文件的File mFile = file; //Xml文件的副本(备份) mBackupFile = makeBackupFile(file); //SharedPreferences模式 mMode = mode; //是否加载文件内容到内存(加载到sSharedPrefs) mLoaded = false; //SharedPreferences键值对哈希表,最终获取数据就是在这表里面获取 mMap = null; //异步读取文件并且保存数据到sSharedPrefs startLoadFromDisk(); }
private void startLoadFromDisk() { synchronized (this) { //标识文件未读取到内存 //在getString/getBoolean一系列的读取函数中会调用awaitLoadedLocked()函数会判断mLoaded.false - awaitLoadedLocked()函数阻塞等待mLoaded为true. //还没读取完Xml文件哪里来读取呢?关键就在这变量.没赋值为true之前所有读取操作都是阻塞等待. mLoaded = false; } //子线程执行读取Xml文件操作 new Thread("SharedPreferencesImpl-load") { public void run() { synchronized (SharedPreferencesImpl.this) { loadFromDiskLocked(); } } }.start(); } private void loadFromDiskLocked() { if (mLoaded) { return; } //如果存在Xml备份文件,删除Xml文件.用备份文件替换. if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } // Debugging if (mFile.exists() && !mFile.canRead()) { Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); } Map map = null; FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) { try { BufferedInputStream str = new BufferedInputStream( //通过XmlPull解析Xml文件 new FileInputStream(mFile), 16*1024); //返回的数据是Map.有兴趣可以进去看看.里面很多递归调用,看到头都晕.知道是解析Xml文件并且返回Map就可以了. map = XmlUtils.readMapXml(str); str.close(); } catch (XmlPullParserException e) { Log.w(TAG, "getSharedPreferences", e); } catch (FileNotFoundException e) { Log.w(TAG, "getSharedPreferences", e); } catch (IOException e) { Log.w(TAG, "getSharedPreferences", e); } } //到这里文件已经读取出来并且保持到内存当中. mLoaded = true; if (map != null) { //mMap,SharedPreferencesImpl内部用来存放Map的实例 mMap = map; //文件读取时间 mStatTimestamp = stat.mtime; //文件大小 mStatSize = stat.size; } else { //文件不存在创建一个空的Map mMap = new HashMap<String, Object>(); } //通知等待线程 notifyAll(); }
SharedPreferencesImpl类解析,在构造函数会创建子线程读取Xml文件解析成Map保存到SharedPreferencesImpl类内部的mMap.在读取解析Xml文件这过程当中对该SharedPreferencesImpl实例进行操作都会阻塞.(awaitLoadedLocked()函数导致)
可以知道SharedPreferences是会将Xml文件的全部数据都读取到内存.
读取数据
在SharedPreferencesImpl.class内部,mMap哈希表用来存放SharedPreferences键值对.
private Map<String, Object> mMap;
getInt/getLong/getString等.
这一系列函数本质上没多少区别,就是通过key在mMap查询,查不到责返回默认值.
public String getString(String key, String defValue) { synchronized (this) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; }public int getInt(String key, int defValue) { synchronized (this) { awaitLoadedLocked(); Integer v = (Integer)mMap.get(key); return v != null ? v : defValue; } } public long getLong(String key, long defValue) { synchronized (this) { awaitLoadedLocked(); Long v = (Long)mMap.get(key); return v != null ? v : defValue; } }
private void awaitLoadedLocked() { if (!mLoaded) { // Raise an explicit StrictMode onReadFromDisk for this // thread, since the real read will be in a different // thread and otherwise ignored by StrictMode. BlockGuard.getThreadPolicy().onReadFromDisk(); } while (!mLoaded) { try { wait(); } catch (InterruptedException unused) { } } }
读取数据函数主要是要关注awaitLoadedLocked()函数.如果文件还没加载完成会导致操作阻塞.
修改数据-Editor类
调用SharedPreferencesImpl类的edit()方法返回EditorImpl类.EditorImpl类封装对数据操作的函数.putString/putBoolean等一系列方法.这里需要注意几个问题.
- putString/putBoolean等一系列方法操作EditorImpl类内部的一个Map并不是操作SharedPreferencesImpl类的Map.所以要生效是需要调用EditorImpl类的commit()方法.
- EditorImpl类的clear(0方法是清空EditorImpl类内部的Map实例.
以下操作不会导致SharedPreferences清楚数据.
edit().clear().commit();
- 正确姿势如下:
edit().remove("Save")..remove("PassWord")commit();
public Editor edit() { // TODO: remove the need to call awaitLoadedLocked() when // requesting an editor. will require some work on the // Editor, but then we should be able to do: // // context.getSharedPreferences(..).edit().putString(..).apply() // // ... all without blocking. synchronized (this) { awaitLoadedLocked(); } return new EditorImpl(); }
最后就是commit()方法源代码.
//记录Commit()方法结果的类 private static class MemoryCommitResult { //数据是否需要更新 public boolean changesMade; // any keys different? //Map实例 public List<String> keysModified; // may be null //回调函数,有可能为null public Set<OnSharedPreferenceChangeListener> listeners; // may be null public Map<?, ?> mapToWriteToDisk; public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); //结果 public volatile boolean writeToDiskResult = false; public void setDiskWriteResult(boolean result) { writeToDiskResult = result; writtenToDiskLatch.countDown(); } }// Returns true if any changes were made private MemoryCommitResult commitToMemory() { MemoryCommitResult mcr = new MemoryCommitResult(); synchronized (SharedPreferencesImpl.this) { // We optimistically don't make a deep copy until // a memory commit comes in when we're already // writing to disk. //mDiskWritesInFlight等处理的提交个数 if (mDiskWritesInFlight > 0) { // We can't modify our mMap as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked mMap = new HashMap<String, Object>(mMap); } //SharedPreferences的Map保存到mcr.mapToWriteToDisk mcr.mapToWriteToDisk = mMap; mDiskWritesInFlight++; //回调函数 boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { //是否有清空,调用clear()以后设置mClear=true.就是当调用clear()以后在put数据进去也不会生效. if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; mMap.clear(); } mClear = false; } //处理edit的Map和SharedPreferences的Map for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); //调用romve以后,Value会设置成this.用来标识此key需要删除.很犀利的技巧.如果不设置没法知道哪一个需要删除. if (v == this) { // magic value for a removal mutation if (!mMap.containsKey(k)) { continue; } //从SharedPreferences的Map移除 mMap.remove(k); } else { boolean isSame = false; if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } //键值对插入SharedPreferences的Map mMap.put(k, v); } //设置修改位,标志已经是脏的.需要更新 mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } //edit的Map已经处理完.可以清空.此时SharedPreferences的Map数据已经是最新的 mModified.clear(); } } return mcr; } 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(); } } }; /.通过commit()方法调用,此处isFromSyncCommit为true 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); } 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 (!mcr.changesMade) { // If the file already exists, but no changes were // made to the underlying map, it's wasteful to // re-write the file. Return as if we wrote it // out. mcr.setDiskWriteResult(true); return; } if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); mcr.setDiskWriteResult(false); return; } } else { //如果备份Xml文件存在,把mFile删除就可以了. 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); if (str == null) { mcr.setDiskWriteResult(false); return; } //回写到文件 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); //刷新 FileUtils.sync(str); str.close(); //设置权限ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(mFile.getPath(), stat)) { synchronized (this) { mStatTimestamp = stat.mtime; mStatSize = stat.size; } } // Writing was successful, delete the backup file if there is one. //成功后,把备份文件删除 mBackupFile.delete(); 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); }}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; } private void notifyListeners(final MemoryCommitResult mcr) { if (mcr.listeners == null || mcr.keysModified == null || mcr.keysModified.size() == 0) { return; } if (Looper.myLooper() == Looper.getMainLooper()) { for (int i = mcr.keysModified.size() - 1; i >= 0; i--) { final String key = mcr.keysModified.get(i); for (OnSharedPreferenceChangeListener listener : mcr.listeners) { if (listener != null) { listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); } } } } else { // Run this function on the main thread. ActivityThread.sMainThreadHandler.post(new Runnable() { public void run() { notifyListeners(mcr); } }); } } }
SharedPreferences代码总结
SharedPreferences本质是通过Xml文件实现.使用的时候会把Xml文件解析成Map保存到内存中.要知道它会将Xml文件全部数据都解析到内存当中.免不了会占用内存.其次对SharedPreferences修改都会导致整个Xml文件再次回写.
- Android SharedPreferences解析
- Xamarin.Android SharedPreferences 使用解析
- Android本地存储之SharedPreferences源码解析
- Android SharedPreferences
- Android SharedPreferences
- android SharedPreferences
- SharedPreferences Android
- Android SharedPreferences
- Android SharedPreferences
- Android SharedPreferences
- Android SharedPreferences
- Android-------SharedPreferences
- Android SharedPreferences
- Android SharedPreferences
- Android SharedPreferences
- android SharedPreferences
- Android SharedPreferences
- Android:SharedPreferences
- 稀疏问题的学习
- zzulioj--1832--贪吃的松鼠(位运算好题)
- 位域http://www.nowcoder.com/questionTerminal/853a980f7db14eec87209c6a1e2d99dd
- Nginx代理以及负载均衡配置
- 使用golang的标准库搭建网站--4.关于静态资源处理的问题
- Android SharedPreferences解析
- 我的烦恼
- android插件化-绑定宿主生命周期版本
- 走好职场每一步:关于求职技巧、跳槽迷思、职场困惑
- bootstrap封装成seajs cmd模块
- engine ISO默认域nfs文件配置
- sizeof关键字
- matlab中k-means聚类算法画点
- 剑指offer系列之三十八:判断是否是平衡二叉树