赞
踩
SharedPreference
是轻量级的数据存储方式,能够保存简单的数据类型。比如String、int、boolean值等。XML
结构保存在/data/data/包名/shared_prefs文件夹下,数据以键值对的形式保存。SharedPreferences.Editor editor = getSharedPreferences("data" , MODE_PRIVATE).edit();
editor.putString("name" , "Tom");
editor.putInt("age" , 28);
editor.putBoolean("married" , false);
editor.apply();
SharedPreferences pref = getSharedPreferences("data" , MODE_PRIVATE);
String name = pref.getInt("age" , 0);
Context.getSharedPreferences()
,里面使用了synchronized
关键字来构建SharedPreferences(线程安全的)
SharedPreferencesImpl
的话,会开启一个子线程从磁盘中加载数据。
.bak
的备份文件的内存对象。之后get异步加载数据时,判断磁盘中有没有这个备份文件。
notifyAll()
方法通知唤醒其他等待线程,数据加载完毕。SharedPreferences.Editor
类。在 Android 系统中,Editor是一个接口,它的具体实现类是EditorImpl。Map<string , Object> mModified
中,而不是直接对SharedPreferences中的mMap进行操作。commit/apply
方法中起到同步内存(SharedPreferences.mMap)以及同步磁盘数据的作用。commitToMemory()
方法,将mModified同步到内存的SharedPreferences.mMap中。enqueueDiskWrite
方法将数据写入到磁盘中。(内部会通过wirteToFile进行写入操作)关于commitToMemory()和enqueueDiskWirte内部的操作可以看下面的源码解析
commitToMemory()
方法,将mModified同步到内存的SharedPreferences.mMap中。enqueueDiskWrite
方法调用writeToFile
方法将所有数据用 异步单线程线程池写入到磁盘中。ContextImpl.getSharedPreferences
的源码:getSharedPreference(File file , int mode)
synchronized
关键字,确保SharedPreference对象的创建是线程安全的。SharedPreferenceImpl
对象,并放入到缓存当中。@Override public SharedPreferences getSharedPreferences(String name, int mode) { // 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"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { // 2.创建一个对应路径 /data/data/packageName/name 的 File 对象 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } // 3.这里调用了 getSharedPreferences(File file, int mode) 方法 return getSharedPreferences(file, mode); }
@Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; // 4.这里使用了 synchronized 关键字,确保了 SharedPreferences 对象的构造是线程安全的 synchronized (ContextImpl.class) { // 5.获取SharedPreferences 对象的缓存 final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); // 5.以参数 file 作为 key,获取缓存对象 sp = cache.get(file); if (sp == null) { // 5.如果缓存中不存在 SharedPreferences 对象 checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { if (isCredentialProtectedStorage() && !getSystemService(UserManager.class) .isUserUnlockingOrUnlocked(UserHandle.myUserId())) { throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } } // 5.构造一个 SharedPreferencesImpl 对象 sp = new SharedPreferencesImpl(file, mode); // 5.放入缓存 cache 中,方便下次直接从缓存中获取 cache.put(file, sp); // 5.返回新构造的 SharedPreferencesImpl 对象 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. // 如果由其他进程修改了这个 SharedPreferences 文件,我们将会重新加载它 sp.startReloadIfChangedUnexpectedly(); } // 程序走到这里,说明命中了缓存,SharedPreferences 已经创建,直接返回 return sp; }
SharedPreferenceImpl
类startLoadFromDisk()
来加载数据loadFromDisk()
方法。notifyAll()
方法唤醒其他等待线程。// SharedPreferencesImpl.java // 构造方法 SharedPreferencesImpl(File file, int mode) { mFile = file; // 创建灾备文件,命名为prefsFile.getPath() + ".bak" mBackupFile = makeBackupFile(file); mMode = mode; // mLoaded代表是否已经加载完数据 mLoaded = false; // 解析 xml 文件得到的键值对就存放在mMap中 mMap = null; // 7.顾名思义,这个方法用于加载 mFile 这个磁盘上的 xml 文件 startLoadFromDisk(); } // 创建灾备文件,用于当用户写入失败的时候恢复数据 private static File makeBackupFile(File prefsFile) { return new File(prefsFile.getPath() + ".bak"); }
// SharedPreferencesImpl.java private void startLoadFromDisk() { synchronized (this) { mLoaded = false; } //注意:这里我们可以看出,SharedPreferences 是通过开启一个线程来异步加载数据的 new Thread("SharedPreferencesImpl-load") { public void run() { // 8.这个方法才是真正负责从磁盘上读取 xml 文件数据 loadFromDisk(); } }.start(); } private void loadFromDisk() { synchronized (SharedPreferencesImpl.this) { // 如果正在加载数据,直接返回 if (mLoaded) { return; } // 9.如果备份文件存在,删除原文件,把备份文件重命名为原文件的名字 // 我们称这种行为叫做回滚 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; StructStat stat = null; try { // 获取文件信息,包括文件修改时间,文件大小等 stat = Os.stat(mFile.getPath());//10.磁盘加载数据 if (mFile.canRead()) { BufferedInputStream str = null; try { // 读取数据并且将数据解析为jia str = new BufferedInputStream( new FileInputStream(mFile), *); map = XmlUtils.readMapXml(str); } catch (XmlPullParserException | IOException e) { Log.w(TAG, "getSharedPreferences", e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { /* ignore */ } synchronized (SharedPreferencesImpl.this) { // 加载数据成功,设置 mLoaded 为 true mLoaded = true; if (map != null) { // 11.将解析得到的键值对数据赋值给 mMap mMap = map; // 12.将文件的修改时间戳保存到 mStatTimestamp 中 mStatTimestamp = stat.st_mtime; // 12.将文件的大小保存到 mStatSize 中 mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } // 13.通知唤醒所有等待的线程 notifyAll(); } }
synchronized
关键字保证getString方法是线程安全的。awaitLoadedLocked
来判断数据是否读取完毕了。(里面判断mLoaded是否为true,为true,表示SharedPreferenceImpl中已经加载完数据了)@Nullable public String getString(String key, @Nullable String defValue) { // 1.synchronize 关键字用于保证 getString 方法是线程安全的 synchronized (this) { // 2.方法 awaitLoadedLocked() 用于确保加载完数据并保存到 mMap 中才进行数据读取 awaitLoadedLocked(); // 根据 key 从 mMap中获取 value String v = (String)mMap.get(key); // 如果 value 不为 null,返回 value,如果为 null,返回默认值 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(); } // 前面我们说过,mLoaded 代表数据是否已经加载完毕 while (!mLoaded) { try { // 等待数据加载完成之后才返回继续执行代码 wait(); } catch (InterruptedException unused) { } } }
sharedPreferences.edit()
方法返回的SharedPreferences.Editor
mModified
中。(而不是直接对SharedPreferences.mMap进行操作)commit
/ apply
方法中起到同步内存SharedPreferences.mMap以及磁盘数据的作用。public final class EditorImpl implements Editor { // putXxx/remove/clear等写操作方法都不是直接操作 mMap 的,而是将所有 // 的写操作先记录在 mModified 中,等到 commit/apply 方法被调用,才会将 // 所有写操作同步到 内存中的 mMap 以及磁盘中 private final Map<String, Object> mModified = Maps.newHashMap(); // private boolean mClear = false; public Editor putString(String key, @Nullable String value) { synchronized (this) { mModified.put(key, value); return this; } } public Editor putStringSet(String key, @Nullable Set<String> values) { synchronized (this) { mModified.put(key, (values == null) ? null : new HashSet<String>(values)); return this; } } public Editor putInt(String key, int value) { synchronized (this) { mModified.put(key, value); return this; } } public Editor putLong(String key, long value) { synchronized (this) { mModified.put(key, value); return this; } } public Editor putFloat(String key, float value) { synchronized (this) { mModified.put(key, value); return this; } } public Editor putBoolean(String key, boolean value) { synchronized (this) { mModified.put(key, value); return this; } } public Editor remove(String key) { synchronized (this) { mModified.put(key, this); return this; } } ...... 其他方法 ...... }
commitToMemory()
方法,将mModified同步写入到内存的SharedPreferences.mMap中。public boolean commit() { // 前面我们分析 putXxx 的时候说过,写操作的记录是存放在 mModified 中的 // 1.在这里,commitToMemory() 方法就负责 将 mModified 保存的 写记录同步到内存中的 mMap 中 // 并且返回一个 MemoryCommitResult 对象 MemoryCommitResult mcr = commitToMemory(); // 2.enqueueDiskWrite 方法负责将 数据落地到磁盘 上 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { // 3.同步等待数据落地磁盘工作完成 才返回 mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } // 通知观察者 notifyListeners(mcr); return mcr.writeToDiskResult; }
首先通过commitToMemory()
方法,将mModified同步到内存的SharedPreferences.mMap中。
具体做了什么
sharedPreferences.edit()
.putString("key1", "value1") // key1 不会被 clear 掉,commit 之后依旧会被写入磁盘中
.clear()
.commit();复制代码
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. 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); } // 2.将 mMap 赋值给 mcr.mapToWriteToDisk,mcr.mapToWriteToDisk 指向的就是最终写入磁盘的数据 mcr.mapToWriteToDisk = mMap; // 1.mDiskWritesInFlight 代表的是“此时需要将数据写入磁盘,但还未处理或未处理完成的次数” // 1.将 mDiskWritesInFlight 自增1(这里是唯一会增加 mDiskWritesInFlight 的地方) mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { // 3.只有调用clear()方法,mClear才为 true if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; // 当 mClear 为 true,清空 mMap mMap.clear(); } mClear = false; } // 4.遍历 mModified for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); // 获取 key Object v = e.getValue(); // 获取 value // 当 value 的值是 "this" 或者 null,将对应 key 的键值对数据从 mMap 中移除 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); } mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } // 将 mModified 同步到 mMap 之后,清空 mModified 历史记录 mModified.clear(); } } //5.返回 return mcr; }
enqueueDiskWrite
方法将数据写入到磁盘中。writeToFile
方法写入磁盘private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { // 创建一个 Runnable 对象 负责写磁盘操作 final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { // 顾名思义了,这就是最终通过文件操作将数据写入磁盘的方法了 writeToFile(mcr); } synchronized (SharedPreferencesImpl.this) { // 写入磁盘后,将 mDiskWritesInFlight 自减1,代表写磁盘的需求减少一个 mDiskWritesInFlight--; } if (postWriteRunnable != null) { // 执行 postWriteRunnable(提示,在 apply 中,postWriteRunnable 才不为 null) postWriteRunnable.run(); } } }; // 如果传进的参数 postWriteRunnable 为 null,那么 isFromSyncCommit 为 true // 温馨提示:从上面的 commit() 方法源码中,可以看出调用 commit() 方法传入的 postWriteRunnable 为 null 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) { // 如果此时只有一个 commit 请求(注意,是 commit 请求,而不是 apply )未处理,那么 wasEmpty 为 true wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { // 当只有一个 commit 请求未处理,那么无需开启线程进行处理,直接在本线程执行 writeToDiskRunnable 即可 writeToDiskRunnable.run(); return; } } // 将 writeToDiskRunnable 方法线程池中执行 // 程序执行到这里,有两种可能: // 1. 调用的是 commit() 方法,并且当前不只有一个 commit 请求未处理(多个) // 2. 调用的是 apply() 方法 QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); }
mFile
中一次性写入 所有键值对数据,即mcr.mapToWriteToDisk(这就是commitToMemory所说的保存了 所有键值对数据的字段) 一次性写入到磁盘。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 { 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); try { final StructStat stat = Libcore.os.stat(mFile.getPath()); synchronized (this) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { // Do nothing } // 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); }
commitToMemory()
方法,将mModified同步写入到内存的SharedPreferences.mMap中。enqueueDiskWrite
方法将数据异步写入到磁盘中。(可以查看上面的源码)public void apply() { // 将 mModified 保存的写记录同步到内存中的 mMap 中,并且返回一个 MemoryCommitResult 对象 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); } }; // 将数据落地到磁盘上,注意,传入的 postWriteRunnable 参数不为 null,所以在 // enqueueDiskWrite 方法中会开启子线程异步将数据写入到磁盘中 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); }
// 将数据落地到磁盘上,注意,传入的 postWriteRunnable 参数不为 null,所以在
// enqueueDiskWrite 方法中会开启子线程异步将数据写入到磁盘中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
val key = "DataStore"
val sp = getSharedPreferences("文件名", Context.MODE_PRIVATE)
sp.edit { putInt(key, 0) } // 使用 Int 类型的数据覆盖相同的 key
sp.getString(key, ""); // 使用相同的 key 读取 Sting 类型的数据
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。