青岛品牌网站制作,网站建设费1万多入什么科目,中企动力是做什么的?,免费外贸网站制作基础用法获取Sp:getput监听器原理分析获取SharedPreferences构造SharedPreferencesgetX原理分析putX原理分析创建editorputStringapplyapply总结commitSharedPreferences最佳实践勿存储过大value勿存储复杂数据不要乱edit和apply#xff0c;尽量批量修改一次提交建议apply尽量批量修改一次提交建议apply少用commitregisterOnSharedPreferenceChangeListener弱引用问题apply和commit对registerOnSharedPreferenceChangeListener的影响不要有任何用SP进行多进程存储的幻想基础用法 获取Sp: Activity中getPreferences(int mode)context.getSharedPreferences(String name, int mode)PreferenceManager.getDefaultSharedPreferences(Context context)1和3的获取SP的方法最终都会调用2只是1和3默认选取了特定的name1中通过getLocalClassName()获取通过包名和类名拼装的name3通过context.getPackageName() _preferences获取name注意第二个参数的含义现在均指定为MODE_PRIVATE其余的都被废弃。含义如下File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID). 所存储的数据保存在/data/data/package name/shared_prefs下的指定name.xml文件中get sp.getX(String key, X value);1X为简单的基本类型floatintlongStringBooleanSetput SharedPreferences sharedPreferences getPreferences(0); SharedPreferences.Editor editor sharedPreferences.edit(); editor.putFloat(float, 1f); editor.putBoolean(boolean, true); editor.apply();12345首先获取Editor对象操作完需要进行事务提交操作可以采用commit或者apply进行。commit同步写磁盘返回是否成功的标识码。apply异步写磁盘无返回值。(二者均是同步写内存先同步写内存之后同步/异步写磁盘)监听器 sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } });123456如果使用匿名内部类的形式进行监听。注意因为OnSharedPreferenceChangeListener的引用被保存在一个WeakHashMap中导致程序的行为不确定性。为了避免这种情况推荐以下方式:private OnSharedPreferenceChangeListener mListener new OnSharedPreferenceChangeListener() { Override public void onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key) { Log.i(LOGTAG, instance variable key key); }}; Overrideprotected void onResume() { PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(mListener); super.onResume();} Overrideprotected void onPause() { PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).unregisterOnSharedPreferenceChangeListener(mListener); super.onPause();原理分析 获取SharedPreferences public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { if (sSharedPrefs null) { sSharedPrefs new ArrayMapString, ArrayMapString, SharedPreferencesImpl(); } final String packageName getPackageName(); ArrayMapString, SharedPreferencesImpl packagePrefs sSharedPrefs.get(packageName); if (packagePrefs null) { packagePrefs new ArrayMapString, 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; 可见 sdk 是先取了缓存(sSharedPrefs静态变量), 如果缓存未命中, 才构造对象. 也就是说, 多次 getSharedPreferences 几乎是没有代价的. 同时, 实例的构造被 synchronized 关键字包裹, 因此构造过程是多线程安全的构造SharedPreferences 第一次构建SharedPreferences对象// SharedPreferencesImpl.javaSharedPreferencesImpl(File file, int www.huarenyl.cn mode) { mFile file; mBackupFile makeBackupFile(file); mMode mode; mLoaded false; mMap null; startLoadFromDisk(www.mcyllpt.com);几个关键类成员信息解释如下 1. mFile 代表我们磁盘上的配置文件 2. mBackupFile 是一个灾备文件, 用户写入失败时进行恢复, 后面会再说. 其路径是 mFile 加后缀 ‘.bak’ 3. mMap 用于在内存中缓存我们的配置数据, 也就是 getXxx 数据的来源 重点关注startLoadFromDisk()方法 private void startLoadFromDisk() { synchronized (this) { mLoaded false; } new Thread(SharedPreferencesImpl-load) { public void run(www.thd178.com ) { loadFromDisk(www.taohuayuan178.com );开启了一个从Disk读取的线程 // SharedPreferencesImpl.javaprivate void loadFromDisk(www.douniu157.com) { synchronized (SharedPreferencesImpl.this) { if (mLoaded) { return; } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } ... 略去无关代码 ... str new BufferedInputStream( new FileInputStream(mFile), 16*1024); map XmlUtils.readMapXml(str); synchronized (SharedPreferencesImpl.this) { mLoaded true; if (map ! null) { mMap map; mStatTimestamp stat.st_mtime; mStatSize stat.st_size; } else { mMap new HashMap();; } notifyAll();loadFromDisk()非常关键他总共做了以下几件事 1. 如果有 ‘灾备’ 文件, 则直接使用灾备文件回滚. 2. 把配置从磁盘读取到内存的并保存在 mMap 字段中(看代码最后 mMap map) 3. 标记读取完成, 这个字段后面 awaitLoadedLocked 会用到. 记录读取文件的时间, 后面 MODE_MULTI_PROCESS 中会用到 4. 发一个 notifyAll 通知已经读取完毕, 激活所有等待加载的其他线程 这里写图片描述 getX原理分析 public float getFloat(String key, float defValue) { synchronized (this) { awaitLoadedLocked(); Float v (Float)mMap.get(key); return v ! null ? v : defValue;关键信息如下 1. synchronized保证了线程安全 2. get操作一定是从mMap中读取既从内存中读取无过多性能损耗。 3. awaitLoadedLocked()保证了读取操作一定在loadFromDisk()执行之完同步等待。因此第一次调用get操作可能会阻塞万分注意这也是sp被定义为轻量级存储系统的重要原因 putX原理分析 put操作较为复杂一步一步分析 创建editor // SharedPreferencesImpl.java 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(); } EditorImpl()无构造函数仅仅去初始化两个成员变量 // SharedPreferencesImpl.javapublic final class EditorImpl implements Editor { private final MapString, Object mModified Maps.newHashMap(); private boolean mClear false; ... 略去方法定义 ... public Editor putString(String key, Nullable String value) { ... } public boolean commit() { ... } 关键信息如下 1. ·mModified 是我们每次 putXxx 后所改变的配置项 2. mClear 标识要清空配置项 putString public Editor putString(String key, Nullable String value) { synchronized (this) { mModified.put(key, value); return this;很简单, 仅仅是把我们设置的配置项放到了 mModified 属性里保存. 等到 apply 或者 commit 的时候回写到内存和磁盘. 咱们分别来看看 apply // SharedPreferencesImpl.javapublic void apply() { final MemoryCommitResult mcr commitToMemory(); ... 略无关 ... SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);apply核心在于两点 1. commitToMemory()完成了内存的同步回写 2. enqueueDiskWrite() 完成了硬盘的异步回写, 我们接下来具体看看 // SharedPreferencesImpl.javaprivate MemoryCommitResult commitToMemory() { MemoryCommitResult mcr new MemoryCommitResult(); synchronized (SharedPreferencesImpl.this) { ... 略去无关 ... mcr.mapToWriteToDisk mMap; mDiskWritesInFlight; synchronized (this) { for (Map.Entrylt;String, Objectgt; e : mModified.entrySet()) { String k e.getKey(); Object v e.getValue(); // quot;thisquot; is the magic value for a removal mutation. In addition, // setting a value to quot;nullquot; for a given key is specified to be // equivalent to calling remove on that key. if (v this || v null) { mMap.remove(k); } else { mMap.put(k, v); } } mModified.clear(); } } return mcr;两个关键信息 1. 把 Editor.mModified 中的配置项回写到 SharedPreferences.mMap 中, 完成了内存的同步 2. 把 SharedPreferences.mMap 保存在了 mcr.mapToWriteToDisk 中. 而后者就是即将要回写到磁盘的数据源 // SharedPreferencesImpl.javaprivate void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final Runnable writeToDiskRunnable new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr); } ... } }; ... QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);关键信息 使用singleThreadExecutor单一线程池去依次执行写入磁盘的runnable序列 之后是真正执行把数据写入磁盘的方法// SharedPreferencesImpl.javaprivate 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)) { 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); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat Os.stat(mFile.getPath()); mStatTimestamp stat.st_mtime; mStatSize stat.st_size; } // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); return; } // Clean up an unsuccessfully written file mFile.delete();主要分为三个过程 1. 先把已存在的老的配置文件重命名(加 ‘.bak’ 后缀), 然后删除老的配置文件. 这相当于做了灾备 2. 向 mFile 中一次性写入所有配置项. 即 mcr.mapToWriteToDisk(这就是 commitToMemory 所说的保存了所有配置项的字段) 一次性写入到磁盘. 如果写入成功则删除灾备文件, 同时记录了这次同步的时间 3. 如果上述过程 [2] 失败, 则删除这个半成品的配置文件 apply总结 由于apply比较复杂稍作总结 1. 通过 commitToMemory 将修改的配置项同步回写到内存 SharedPreferences.mMap 中. 此时, 任何的 getXxx 都可以获取到最新数据了 2. 通过 enqueueDiskWrite 调用 writeToFile 将所有配置项一次性异步回写到磁盘. 这是一个单线程的线程池 这里写图片描述 commit commit比较简单直接看代码和时序图即可大致和apply相同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;这里写图片描述 注意commit最后会等待异步任务返回说明会阻塞当前调用线程因此说commit是同步写入apply是异步写入。 以上涵盖了大部分SharedPreferences重要源码分析下面总结SharedPreferences最佳实践提出日后要注意的问题只说结论不解释原因。(如果你不明白为什么证明你前面的分析没有深刻理解)SharedPreferences最佳实践 勿存储过大value 永远记住SharedPreferences是一个轻量级的存储系统不要存过多且复杂的数据这会带来以下的问题 第一次从sp中获取值的时候有可能阻塞主线程使界面卡顿、掉帧。这些key和value会永远存在于内存之中占用大量内存。勿存储复杂数据 SharedPreferences通过xml存储解析JSON或者HTML格式存放在sp里面的时候需要转义这样会带来很多这种特殊符号sp在解析碰到这个特殊符号的时候会进行特殊的处理引发额外的字符串拼接以及函数调用开销。如果数据量大且复杂严重时可能导频繁GC。不要乱edit和apply尽量批量修改一次提交 edit会创建editor对象每进行一次apply就会创建线程进行内存和磁盘的同步千万写类似下面的代码SharedPreferences sp getSharedPreferences(test, MODE_PRIVATE);sp.edit().putString(test1, sss).apply();sp.edit().putString(test2, sss).apply();sp.edit().putString(test3, sss).apply();sp.edit().putString(test4, sss).apply();建议apply少用commit commit同步写内存同步写磁盘。有是否成功的返回值apply同步写内存异步写磁盘。无返回值registerOnSharedPreferenceChangeListener弱引用问题 见本文初apply和commit对registerOnSharedPreferenceChangeListener的影响 对于 apply, listener 回调时内存已经完成同步, 但是异步磁盘任务不保证是否完成对于 commit, listener 回调时内存和磁盘都已经同步完毕不要有任何用SP进行多进程存储的幻想 这个话题不需要过多讨论只记住一点多进程别用SPAndroid没有对SP在多进程上的表现做任何约束和保证。附上Google官方注释deprecated MODE_MULTI_PROCESS does not work reliably insome versions of Android, and furthermore does not provide any mechanism for reconciling 转载于:https://www.cnblogs.com/qwangxiao/p/8667831.html