🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 使用 ### 获取SharedPreferences的两种方式 1. 调用Context对象的getSharedPreferences()方法 2. 调用Activity对象的getPreferences()方法 两种方式的区别://待考证 1. 调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享. 2. 调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用. ### SharedPreferences的四种操作模式: * Context.MODE\_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容 * Context.MODE\_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件. * Context.MODE\_WORLD\_READABLE和Context.MODE\_WORLD\_WRITEABLE用来控制其他应用是否有权限读写该文件. * MODE\_WORLD\_READABLE:表示当前文件可以被其他应用读取. Android N后不支持 * MODE\_WORLD\_WRITEABLE:表示当前文件可以被其他应用写入.Android N后不支持 ## 源码解析 ### ContextImpl ~~~ private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache; private ArrayMap<String, File> mSharedPrefsPaths; @Override public SharedPreferences getSharedPreferences(String name, int mode) { ... File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { //获取文件路径=当前app的data目录下的shared_prefs目录+"/"+SharePreferences的name+“.xml” file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); } @Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //读取缓存 final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { // 如果缓存中不存在 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"); } } // 构造一个 SharedPreferencesImpl 对象 sp = new SharedPreferencesImpl(file, mode); cache.put(file, 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. // 如果由其他进程修改了这个 SharedPreferences 文件,我们将会重新加载它 sp.startReloadIfChangedUnexpectedly(); } retu ~~~ * 缓存未命中, 才构造 SharedPreferences 对象,也就是说,多次调用 getSharedPreferences 方法并不会对性能造成多大影响,因为又缓存机制  * SharedPreferences 对象的创建过程是线程安全的,因为使用了 synchronize 关键字  * 如果命中了缓存,并且参数 mode 使用了 Context.MODE\_MULTI\_PROCESS ,那么将会调用 sp.startReloadIfChangedUnexpectedly() 方法,在 startReloadIfChangedUnexpectedly 方法中,会判断是否由其他进程修改过这个文件,如果有,会重新从磁盘中读取文件加载数据 ### SharedPreferencesImpl #### 初始化 ~~~ SharedPreferencesImpl(File file, int mode) { //文件 mFile = file; //回滚文件 mBackupFile = makeBackupFile(file); //模式 mMode = mode; //是否加载 mLoaded = false; //key/value 缓存 mMap = null; mThrowable = null; //开始加载 startLoadFromDisk(); } private void startLoadFromDisk() { synchronized (mLock) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { public void run() { loadFromDisk(); } }.start(); } private void loadFromDisk() { synchronized (mLock) { if (mLoaded) { return; } //如果回滚文件在 就吧原文件删除 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<String, Object> map = null; //文件详情 如编辑时间等 StructStat stat = null; Throwable thrown = null; try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); //将文件读取到 map = (Map<String, Object>) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { } catch (Throwable t) { thrown = t; } synchronized (mLock) { // 加载数据成功,设置 mLoaded 为 true mLoaded = true; mThrowable = thrown; // It's important that we always signal waiters, even if we'll make // them fail with an exception. The try-finally is pretty wide, but // better safe than sorry. try { if (thrown == null) { if (map != null) { // 将解析得到的键值对数据赋值给 mMap mMap = map; // 将文件的修改时间戳保存到 mStatTimestamp 中 mStatTimestamp = stat.st_mtim;= map; // 将文件的大小保存到 mStatSize mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } } // In case of a thrown exception, we retain the old map. That allows // any open editors to commit and store updates. } catch (Throwable t) { mThrowable = t; } finally { //通知等待 mLock.notifyAll(); ~~~ * 如果有备份文件,直接使用备份文件进行回滚 * 第一次调用 getSharedPreferences 方法的时候,会从磁盘中加载数据,而数据的加载时通过开启一个子线程调用 loadFromDisk 方法进行异步读取的  * 将解析得到的键值对数据保存在 mMap 中  * 将文件的修改时间戳以及大小分别保存在 mStatTimestamp 以及 mStatSize 中(保存这两个值有什么用呢?我们在分析 getSharedPreferences 方法时说过,如果有其他进程修改了文件,并且 mode 为 MODE\_MULTI\_PROCESS ,将会判断重新加载文件。如何判断文件是否被其他进程修改过,没错,根据文件修改时间以及文件大小即可知道)  * 调用 notifyAll() 方法通知唤醒其他等待线程,数据已经加载完毕 #### get ~~~ @Override @Nullable public String getString(String key, @Nullable String defValue) { synchronized (mLock) { //等待加载完毕 awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } } @Override @Nullable public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { synchronized (mLock) { awaitLoadedLocked(); Set<String> v = (Set<String>) mMap.get(key); return v != null ? v : defValues; } } private void awaitLoadedLocked() { if (!mLoaded) { // 严苛模式 BlockGuard.getThreadPolicy().onReadFromDisk(); } while (!mLoaded) { try { mLock.wait(); } catch (InterruptedException unused) { } } } ~~~ getString 方法代码很简单,其他的例如 getInt , getFloat 方法也是一样的原理,我们直接对这个疑问进行总结:  * getXxx 方法是线程安全的,因为使用了 synchronize 关键字  * getXxx 方法是直接操作内存的,直接从内存中的 mMap 中根据传入的 key 读取 value * getXxx 方法有可能会卡在 awaitLoadedLocked 方法,从而导致线程阻塞等待( **什么时候会出现这种阻塞现象呢?前面我们分析过,第一次调用 ****getSharedPreferences**** 方法时,会创建一个线程去异步加载数据,那么假如在调用完 ****getSharedPreferences**** 方法之后立即调用 ****getXxx**** 方法,此时的 ****mLoaded**** 很有可能为 ****false**** ,这就会导致 ****awaiteLoadedLocked**** 方法阻塞等待,直到 ****loadFromDisk**** 方法加载完数据并且调用 ****notifyAll**** 来唤醒所有等待线程 **) #### SharedPreferencesImpl.edit() ~~~ public Editor edit() { synchronized (this) {//同步方法,保证每次只有一个线程执行加载操作 awaitLoadedLocked();//等待SharedPreferencesImpl加载到内存中,见2.7 } return new EditorImpl();//创建一个新的Editor实现。 } ~~~ #### EditorImpl ~~~ public final class EditorImpl implements Editor { private final Object mLock = new Object(); @GuardedBy("mLock") private final Map<String, Object> mModified = Maps.newHashMap(); @GuardedBy("mLock") private boolean mClear = false; public Editor putString(String key, @Nullable String value) { synchronized (mLock) { mModified.put(key, value); return this; } ~~~ 维护一个Map #### EditorImpl.commit() todo