博客
关于我
解析SharedPreferences
阅读量:326 次
发布时间:2019-03-03

本文共 26787 字,大约阅读时间需要 89 分钟。

文章目录

简介

sp作为轻量级存储,数据最终都是以xml的形式进行存储的。

在这里插入图片描述

一、文件保存

在这里插入图片描述

SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);        String value = config.getString("key", "value");

getSharedPreferences会得到SharedPreferences对象,其实调用的还是ContextImpl#getSharedPreferences()方法:

@Override    public SharedPreferences getSharedPreferences(String name, int mode) {       	//mBase的类型就是ContextImpl        return mBase.getSharedPreferences(name, mode);    }

ContextImpl#getSharedPreferences()

@Override    public SharedPreferences getSharedPreferences(String name, int mode) {           // 目标版本<19且name为null,就把文件名直接设置为null:null.xml        if (mPackageInfo.getApplicationInfo().targetSdkVersion <                Build.VERSION_CODES.KITKAT) {               if (name == null) {                   name = "null";            }        }        File file;        //synchronized持有对象锁,        synchronized (ContextImpl.class) {               if (mSharedPrefsPaths == null) {               	//创建维护文件名和文件File的映射关系的ArrayMap
mSharedPrefsPaths = new ArrayMap<>(); } //根据name获取对应的文件File file = mSharedPrefsPaths.get(name); if (file == null) { //取不到就创建文件 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } //根据File创建SharedPreferences return getSharedPreferences(file, mode); }
  • ①这里有一个判断,如果目标版本 < 19 且name传递为null,就直接将文件名设置为null:null.xml。
  • ②synchronized持有对象锁,创建维护文件名和文件File的映射关系的ArrayMap<name,file>—mSharedPrefsPaths;通过文件名获取对应的文件File,如果取不到就创建,并将其put进mSharedPrefsPaths中。
  • ③最后根据File创建SharedPreferences

重点关注一下SharedPreferences的创建文件的过程:

//根据文件名创建File对象	@Override    public File getSharedPreferencesPath(String name) {           return makeFilename(getPreferencesDir(), name + ".xml");    }    private File getPreferencesDir() {           synchronized (mSync) {               if (mPreferencesDir == null) {               	//创建SharedPreferences文件保存目录,得到            	//的是:/data/data/packageName/                mPreferencesDir = new File(getDataDir(), "shared_prefs");            }            //确保应用的私有文件目录已经存在            return ensurePrivateDirExists(mPreferencesDir);        }    }

由此可知SP文件的存储目录是在shared_prefs下的。得到路径、文件名后就调用makeFilename()方法创建File:

private File makeFilename(File base, String name) {   		//检查文件名是否合法,否则抛异常        if (name.indexOf(File.separatorChar) < 0) {               return new File(base, name);        }        throw new IllegalArgumentException(                "File " + name + " contains a path separator");    }

这里会对文件名进行判断,不能是“/config”等路径形式,否则会抛出异常。

总结一下:通过文件名name创建对应的File对象,并将其put到 ContextImpl的ArrayMap<name,file>缓存容器 中。

二、SP创建

在这里插入图片描述

ContextImpl#getSharedPreferences()方法最后会调用重载方法getSharedPreferences(File file,int mode):

@Override    public SharedPreferences getSharedPreferences(File file, int mode) {           SharedPreferencesImpl sp;        synchronized (ContextImpl.class) {           	//得到用于缓存SP的Map容器,在ContextImpl以单例形式声明            final ArrayMap
cache = getSharedPreferencesCacheLocked(); //单例容器 sp = cache.get(file); if (sp == null) { checkMode(mode); //7.0之后文件加密相关 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,SharedPreferences只是定义了基本API的接口,真实实现类是SharedPreferencesImpl sp = new SharedPreferencesImpl(file, mode); //保存到ArrayMap
缓存容器中。 cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // MODE_MULTI_PROCESS的加载策略 sp.startReloadIfChangedUnexpectedly(); } return sp; }

先看一下如何获取到缓存SP的Map容器的,在getSharedPreferencesCacheLocked()方法:

@GuardedBy("ContextImpl.class")    private ArrayMap
getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { // 创建缓存SharedPreferencesImpl的ArrayMap容器 sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); //根据应用包名,获取ArrayMap ArrayMap
packagePrefs = sSharedPrefsCache.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); //根据包名,保存所有SharedPreferencesImpl sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs; }

它会创建用于缓存SharedPreferencesImpl实例的ArrayMap缓存容器,然后根据包名,将所有的SharedPreferencesImpl实例都保存起来。最后返回这个SharedPreferencesImpl集合:ArrayMap<File, SharedPreferencesImpl> packagePrefs

总结一下:

  • SharedPreferences只是定义了几个API的接口,实际实现类还是SharedPreferencesImpl。所有的SP操作都是依赖SharedPreferencesImpl来完成的。
  • 每个SharedPreferencesImpl都会被缓存到ArrayMap容器中,以后再获取它,就都是从缓存容器中取
  • SP提供了加载策略MODE_MULTI_PROCESS,当目标版本 < 11就重新从文件中加载一遍数据到内存中。于是,SP不能跨进程通信

三、SP数据加载

在这里插入图片描述

SharedPreferencesImpl构造方法:

SharedPreferencesImpl(File file, int mode) {       	//SharedPreferences保存文件        mFile = file;        //SP的原始文件的备份文件        mBackupFile = makeBackupFile(file);        //加载模式        mMode = mode;        //标志位,是否正在加载        mLoaded = false;        mMap = null;        mThrowable = null;        //开启线程,加载对应文件数据到Map容器中        startLoadFromDisk();    }

看SP如何开启线程,将对应文件中的数据加载到Map容器中:

private void startLoadFromDisk() {           synchronized (mLock) {           	//加载标志位,每次需要加载时,需将其置为false        	//加载完后在置为true            mLoaded = false;        }        new Thread("SharedPreferencesImpl-load") {               public void run() {               	//开启独立线程加载数据                loadFromDisk();            }        }.start();    }

这个标志位mLoaded在SharedPreferencesImpl的构造方法中声明,每次需要加载时需要先将其置False,加载完成后再置为True。mLoaded在多线程访问等待方面很重要,如果在UI线程操作SP数据,可能会导致UI线程等待。

随后调用startLoadFromDisk()方法,在其内部开启独立线程,通过loadFromDisk()方法异步加载数据

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
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); //SP的文件操作都封装在XmlUtils中,得到map实例 map = (Map
) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { // An errno exception means the stat failed. Treat as empty/non-existing by // ignoring. } catch (Throwable t) { thrown = t; } synchronized (mLock) { //表示SP文件中的数据已经从磁盘加载到内存Map中了 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 = map; mStatTimestamp = stat.st_mtim; 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(); } } }

开启独立线程加载数据的方法中,先对mLoaded进行判断,这样可以有效的避免重复加载的情况发生。然后判断备份文件是否存在,true则删除源文件,将备份文件重命名为源文件。通过BufferedInputStream读取文件内容,并借助XmlUtils将其转化为map实例(XmlUtils封装了对Xml文件的读写操作,并将数据封装进Map容器返回)。随后检查是否加载成功、是否加载异常,若无异常就直接赋值操作(mMap = map—>private Map<String,Object> mMap)。

总结一下:SharedPreferencesImpl初始化,会开启独立线程异步加载对应name的xml文件到内存中,并保存到Map容器中。后续的getxxx()操作都是直接操作的Map容器。

四、getxxx()操作

在这里插入图片描述

此时的操作,都是针对的Map容器—mMap中的数据。即根据传入的Key,到mMap中获取对应的Value。

@Override    @Nullable    public String getString(String key, @Nullable String defValue) {           synchronized (mLock) {           	//重点!!!            awaitLoadedLocked();            //直接从Map容器中获取Value            String v = (String)mMap.get(key);            return v != null ? v : defValue;        }    }

重点关注awaitLoadedLocked()方法,根据mLoaded判断是否加载完成,否则调用方线程等待:

@GuardedBy("mLock")    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 {                   mLock.wait();            } catch (InterruptedException unused) {               }        }        if (mThrowable != null) {               throw new IllegalStateException(mThrowable);        }    }

SP文件加载时,要先将mLoaded置为false,加载成功后在置为true。如果加载过程比较耗时,此时如果在UI线程操作SP数据,那么该线程就进入wait状态了。

总结一下:mLoaded的作用就是判断SP数据是否从Disk完全加载到Memory并保存到Map容器中了。如果未完成,就让调用方线程wait。待数据加载完成后,再让所有的等待线程notifyAll唤醒。由于SP存储的数据都会在内存中保留一份,故所有的getXXX()操作都是直接操作的缓存容器。

五、putXXX()操作

在这里插入图片描述

SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);        SharedPreferences.Editor editor = config.edit().putString("key", "value");        editor.apply();

put操作首先要经过edit()方法获取到Editor对象:

SharedPreferencesImpl#edit()  @Override    public Editor edit() {           //mLoaded在发挥作用        synchronized (mLock) {               awaitLoadedLocked();        }		//实际返回的EditorImpl        return new EditorImpl();    }

返回的EditorImpl实例:

public final class EditorImpl implements Editor {           private final Object mEditorLock = new Object();		//保存修改数据的容器		//一系列添加、修改、删除数据都保存在该临时容器中        @GuardedBy("mEditorLock")        private final Map
mModified = new HashMap<>(); //标志当前是否是清除操作 @GuardedBy("mEditorLock") private boolean mClear = false; //添加String类型数据 @Override public Editor putString(String key, @Nullable String value) { synchronized (mEditorLock) { //添加到临时Map容器中 mModified.put(key, value); return this; } }

和SharedPreferences类似,Editor只是一个接口,定义了一些API方法,真正实现实在EditorImpl中。修改数据后,要commit或apply进行提交,这也是最重要的!

5.1 commit &&apply

在这里插入图片描述

每次.edit()都会生成一个新的EditorImpl对象,故应该尽量批量提交,避免单次提交。

@Override        public boolean commit() {               long startTime = 0;            if (DEBUG) {                   startTime = System.currentTimeMillis();            }			//将临时容器mModified中的数据提交到SharedPreferencesImpl的Map容器中,为了方便写入文件            MemoryCommitResult mcr = commitToMemory();			//将写入文件的结果mcr作为参数,根据apply/commit决定任务run in工作线程/当前线程            SharedPreferencesImpl.this.enqueueDiskWrite(                mcr, null /* sync write on this thread okay */);            try {                   mcr.writtenToDiskLatch.await();            } catch (InterruptedException e) {                   return false;            } finally {                   if (DEBUG) {                       Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration                            + " committed after " + (System.currentTimeMillis() - startTime)                            + " ms");                }            }            //通知外部监听            notifyListeners(mcr);            return mcr.writeToDiskResult;        }

commitToMemory()方法将临时容器mModified中的数据提交到SharedPreferencesImpl的mMap中:

private MemoryCommitResult commitToMemory() {               long memoryStateGeneration;            //保存发生变化的key            List
keysModified = null; //外部监听器 Set
listeners = null; Map
mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { if (mDiskWritesInFlight > 0) { //数据拷贝 mMap = new HashMap
(mMap); } //将mMap赋值给局部变量,后续for循环 mapToWriteToDisk = mMap; mDiskWritesInFlight++; //可以监听SP数据提交完成 boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList
(); //收集回调通知 listeners = new HashSet
(mListeners.keySet()); } synchronized (mEditorLock) { //确保是否真的发生变化,避免无谓的I/O boolean changesMade = false; //如果是clear操作,就直接清空数据 if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } //遍历修改后的临时容器mModified for (Map.Entry
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) { //value==null,mMap不包含该Key,可直接跳过 if (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { //mMap中包含该Key,Value就直接修改为最新提交的 if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); //value没变,就跳过。主要考虑changesMade标志位,确保数据是否真的发生了变化 if (existingValue != null && existingValue.equals(v)) { continue; } } //直接添加新的Key:Value mapToWriteToDisk.put(k, v); } //for循环中,数据若发生变化,changesMade就置为true。表示当前数据发生了变化 changesMade = true; if (hasListeners) { keysModified.add(k); } } //清空临时容器mModified mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }

在getXXX()、putXXX()过程中,这些操作都会添加到临时容器mModified中,mModified保存着当前的改变。遍历mModified,并与数据容器mMap做比较,就将修改提交到mMap中。mMap中保存了最新的修改Value值后,最后清空临时容器mModified。

然后commit方法又回去调用enqueueDiskWrite()方法,负责执行写入文件的任务,就是将最后一次commitToMemory的mMap数据写回到文件:

private void enqueueDiskWrite(final MemoryCommitResult mcr,                                  final Runnable postWriteRunnable) {           final boolean isFromSyncCommit = (postWriteRunnable == null);		//执行写入文件的Runnable任务		//这里也区分commit、apply		//apply会将任务扔到线程池,异步执行        final Runnable writeToDiskRunnable = new Runnable() {                   @Override                public void run() {                       synchronized (mWritingToDiskLock) {                       	//写入到文件                        writeToFile(mcr, isFromSyncCommit);                    }                    synchronized (mLock) {                           mDiskWritesInFlight--;                    }                    if (postWriteRunnable != null) {                           postWriteRunnable.run();                    }                }            };        //commit时,会在当前线程执行run方法        if (isFromSyncCommit) {               boolean wasEmpty = false;            synchronized (mLock) {                   wasEmpty = mDiskWritesInFlight == 1;            }            if (wasEmpty) {               	//commit操作,直接在当前线程中执行                writeToDiskRunnable.run();                return;            }        }		//apply操作,把任务扔到线程池中排队执行        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);    }

如果是commit,就直接在当前线程执行 writeToDiskRunnable.run()。如果是apply,就将任务添加到线程池中排队执行。

不管是commit还是apply,就算只改动一个条目,都会把mMap中的所有数据全部写入到文件。而且就算是多次写入同一个文件,SP也没有合而为一的意思。因此,SP的性能有点不堪入目。

5.2 apply

apply的异步提交,也不一定就是安全的。这里联系到Android四大组件的生命周期管理,ActivityThread负责调度。在它的onPause的回调中:

@Override    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,            int configChanges, PendingTransactionActions pendingActions, String reason) {           // Make sure any pending writes are now committed.            if (r.isPreHoneycomb()) {               	//检查异步提交的SP任务是否已完成,否则就等待直到完成                QueuedWork.waitToFinish();            }    }

apply提交的任务都会被扔到线程池里排队执行(通过HandlerThread串行),如果apply的太多任务,此时判断任务队列里的任务还未执行完,就会一直等待执行完成。

5.3 SP监控

SP提供了OnSharedPreferencesChangeListener监听数据发生改变:

public interface OnSharedPreferenceChangeListener {                   void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);    }

在notifyListeners()方法中调用:

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(() -> notifyListeners(mcr));            }        }

使用commit,监听回调的时机在数据落盘完成后。不一定成功,有可能会发生异常。

使用apply,是在完成数据内存替换之后,即临时容器mModified中的数据提交到容器mMap中之后。
系统采用弱引用在WeakHashMap中保存OnSharedPreferencesChangeListener对象:

@GuardedBy("mLock")    private final WeakHashMap
mListeners = new WeakHashMap
();

六、SP落盘机制

在这里插入图片描述

在SharedPreferencesImpl的构造方法中创建SP文件的备份文件:

//SP的原始文件的备份文件 mBackupFile = makeBackupFile(file);

SP的落盘机制,全都依赖于此。

static File makeBackupFile(File prefsFile) {           return new File(prefsFile.getPath() + ".bak");    }

mBackupFile就是源文件的备份文件:…/sp文件名.xml.bak。不管是commit还是apply最终都会调用writeToFile()方法,即将我们一系列的remove、putXXX()操作后的数据落地磁盘。

@GuardedBy("mWritingToDiskLock")    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {           ......		//判断源文件是否存在        boolean fileExists = mFile.exists();        ......                if (fileExists) {               boolean needsWrite = false;            if (mDiskStateGeneration < mcr.memoryStateGeneration) {               	//needsWrite判断要提交的数据是否真的发生了变化                if (isFromSyncCommit) {                       needsWrite = true;                } else {                       synchronized (mLock) {                                             if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {                               needsWrite = true;                        }                    }                }            }			//如果提交的数据没变化,就return,避免无谓的I/O操作            if (!needsWrite) {                   mcr.setDiskWriteResult(false, true);                return;            }			//判断备份文件是否存在            boolean backupFileExists = mBackupFile.exists();            if (DEBUG) {                   backupExistsTime = System.currentTimeMillis();            }			//备份文件不存在,落地新内容时,就将源文件备份            if (!backupFileExists) {               	//源文件备份错误,直接return                if (!mFile.renameTo(mBackupFile)) {                       Log.e(TAG, "Couldn't rename file " + mFile                          + " to backup file " + mBackupFile);                    mcr.setDiskWriteResult(false, false);                    return;                }            } else {               	//备份文件存在的话,就删除源文件                mFile.delete();            }        }                try {           	//创建mFile的输入流            FileOutputStream str = createFileOutputStream(mFile);            if (DEBUG) {                   outputStreamCreateTime = System.currentTimeMillis();            }            if (str == null) {                   mcr.setDiskWriteResult(false, false);                return;            }            //真正写入磁盘的操作,发生在XmlUtils中            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);            writeTime = System.currentTimeMillis();			//强制落盘            FileUtils.sync(str);            fsyncTime = System.currentTimeMillis();            str.close();            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);            if (DEBUG) {                   setPermTime = System.currentTimeMillis();            }            try {                   final StructStat stat = Os.stat(mFile.getPath());                synchronized (mLock) {                       mStatTimestamp = stat.st_mtim;                    //计算文件大小                    mStatSize = stat.st_size;                }            } catch (ErrnoException e) {                   // Do nothing            }            if (DEBUG) {                   fstatTime = System.currentTimeMillis();            }            // Writing was successful, delete the backup file if there is one.            //写入成功,删除备份文件            mBackupFile.delete();            if (DEBUG) {                   deleteTime = System.currentTimeMillis();            }            mDiskStateGeneration = mcr.memoryStateGeneration;            mcr.setDiskWriteResult(true, true);            if (DEBUG) {                   Log.d(TAG, "write: " + (existsTime - startTime) + "/"                        + (backupExistsTime - startTime) + "/"                        + (outputStreamCreateTime - startTime) + "/"                        + (writeTime - startTime) + "/"                        + (fsyncTime - startTime) + "/"                        + (setPermTime - startTime) + "/"                        + (fstatTime - startTime) + "/"                        + (deleteTime - startTime));            }            long fsyncDuration = fsyncTime - writeTime;            mSyncTimes.add((int) fsyncDuration);            mNumSync++;            if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {                   mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");            }			//写入成功,返回            return;        } catch (XmlPullParserException e) {               Log.w(TAG, "writeToFile: Got exception:", e);        } catch (IOException e) {               Log.w(TAG, "writeToFile: Got exception:", e);        }        // 写入过程发生异常,直接删除源文件        if (mFile.exists()) {               if (!mFile.delete()) {                   Log.e(TAG, "Couldn't clean up partially-written file " + mFile);            }        }        mcr.setDiskWriteResult(false, false);    }

首先判断源文件是否存在,若存在,就通过needsWrite判断要提交的数据是否真的发生了变化。如果提交的数据没变化,就return,避免无谓的I/O操作。接着判断备份文件是否存在:① 备份文件不存在,落地新内容时,就将源文件备份(源文件备份错误,直接return) ② 备份文件存在的话,就删除源文件。

真正的写入Disk的操作发生在XmlUtils中(XmlUtils.writeMapXml),并强制落盘(FileUtils.sync(str))。写入成功,就删除备份文件,并返回。写入异常,就删除源文件。

强调一点,系统把真正写入数据的操作都封装在XmlUtils中,并强制sync落地磁盘(FileUtils.sync(str)),这个强制落盘机制也能保证数据不丢失。

关于对提交的数据是否真的发生了变化的判断,是在commitToMemory()方法中:

private MemoryCommitResult commitToMemory() {               long memoryStateGeneration;            List
keysModified = null; Set
listeners = null; Map
mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { // 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
(mMap); } mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList
(); listeners = new HashSet
(mListeners.keySet()); } synchronized (mEditorLock) { boolean changesMade = false; if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } for (Map.Entry
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 (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mapToWriteToDisk.put(k, v); } //在for循环中,若数据发生变化,就将changesMade置为true changesMade = true; if (hasListeners) { keysModified.add(k); } } mModified.clear(); if (changesMade) { //将当前状态+1,writeToDisk()落地磁盘时,判断新状态是否 > 当前状态 mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }

上面提到了,一系列操作的数据发生在EditorImpl的临时容器mModified(Map)中,要拿着它和SharedPreferencesImpl的容器mMap作比较,来修正最后一次提交的mMap中的数据。如果数据发生了改变,

七、优化

可以在Application中重写getSharedPreferences方法来替换系统默认实现,但这都不能从根本上解决问题。还是选用MMKV吧!

public class MyApplication extends Application {       @Override    public SharedPreferences getSharedPreferences(String name, int mode) {           return SharedPreferencesImpl.getSharedPreferences(name,mode);    }}

总结

SP的写入操作,先把源文件备份:mFile.renameTo(mBackupFile),再写入所有数据。只有写入成功,且通过sync强制落盘后,才会将备份文件 ".bak"删除。如果写入过程中发生异常,就丢弃这份源文件。虽然丢失了这一部分数据,但是它能保证最后一次落盘的成功后的数据。由于这个BackUp机制,所以多线程的情况下,可能会丢失新写入的数据。

转载地址:http://fbbq.baihongyu.com/

你可能感兴趣的文章
mysql主从配置
查看>>
MySQL之2003-Can‘t connect to MySQL server on ‘localhost‘(10038)的解决办法
查看>>
MySQL之CRUD
查看>>
MySQL之DML
查看>>
Mysql之IN 和 Exists 用法
查看>>
MYSQL之REPLACE INTO和INSERT … ON DUPLICATE KEY UPDATE用法
查看>>
MySQL之SQL语句优化步骤
查看>>
MYSQL之union和order by分析([Err] 1221 - Incorrect usage of UNION and ORDER BY)
查看>>
Mysql之主从复制
查看>>
MySQL之函数
查看>>