SharedPreferences是我们在android开发中使用的比较频繁的轻量级数据存储方案。如果使用不当可能会造成一些困扰。通过源码分析,可以帮助我们更好的了解它内部的运行机制。

使用方式

SharedPreferences sp = getSharedPreferences("test", Context.MODE_PRIVATE);
      //添加数据改变的监听
	  sp.registerOnSharedPreferenceChangeListener(mListener);
      Set<String> set = new HashSet<>();
      set.add("一班");
      set.add("二班");
      //写入
      sp.edit().putString("name", "张三").putStringSet("grade", set).apply();
      //读取
      String name = sp.getString("name","");
      //删除
      sp.edit().remove("name").apply();
      //清空
      sp.edit().clear().apply();

数据会以test.xml文件形式,存储在设备上data/data/程序包名/shared_prefs文件夹下。内容如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
	<map>
	    <string name="name">张三</string>
	    <set name="grade">
	        <string>一班</string>
	        <string>二班</string>
	    </set>
	</map>

源码解析

获取SharedPreferences对象
在ContextImpl调用getSharedPreferences()获取,源码

@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) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }


    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        checkMode(mode);
        if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
            if (isCredentialProtectedStorage()
                    && !getSystemService(StorageManager.class).isUserKeyUnlocked(
                            UserHandle.myUserId())
                    && !isBuggy()) {
                throw new IllegalStateException("SharedPreferences in credential encrypted "
                        + "storage are not available until after user is unlocked");
            }
        }
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                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.
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }
  • SharedPreferences是一个接口,其实现类为SharedPreferencesImpl 。
  • SharedPreferencesImpl 在第一次创建的时候会被缓存。

SharedPreferencesImpl 初始化 源码

SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        //创建备份文件
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = 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 map = null;
        StructStat stat = null;
        try {
        	//获取文件信息结构体
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16*1024);
                    //将xml文件解析为map
                    map = XmlUtils.readMapXml(str);
                } catch (Exception e) {
                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
            /* ignore */
        }

        synchronized (mLock) {
            mLoaded = true;
            if (map != null) {
            	//数据缓存到内存map
                mMap = map;
                //存储上次访问时间
                mStatTimestamp = stat.st_mtime;
                //存储文件长度
                mStatSize = stat.st_size;
            } else {
                mMap = new HashMap<>();
            }
            //唤醒对象的等待池中的所有线程
            mLock.notifyAll();
        }
    }
  • 查询是否有备份文件,如果有就证明上一次的文件写入出现了问题,因此会读取备份的文件。如果没有就读取原始文件。
  • 开启一个线程读取文件内容,并缓存到内存map。
  • 所有操作完成后通过mLock.notifyAll()唤醒对象的等待池中的所有线程。

通过getXXX读取数据
由于get方法大都一样,这里分析getString。

@Nullable
    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
        	//加锁等待异步加载线程把数据加载完毕
            awaitLoadedLocked();
            //直接从内存map读取数据
            String v = (String)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 {
            	//等待mLock.notifyAll()
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
    }
  • 数据直接从内存map读取。
  • 数据读取之前通过mLock.wait()保证数据已经从文件到内存加载完毕。如果xml文件过大,导致解析时间过长,此时进行读取有可能导致界面卡顿或者ANR。
  • 数据读取是线程安全的。

通过putXXX写入数据
数据的写入是通过SharedPreferences的内部类Editor完成的,Editor也是一个接口,其实现类为SharedPreferencesImpl 的内部类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) {
            	//将数据保存到临时map
                mModified.put(key, value);
                return this;
            }
        }
        ...
        public Editor remove(String key) {
            synchronized (mLock) {
            	//删除数据传入this
                mModified.put(key, this);
                return this;
            }
        }

        public Editor clear() {
            synchronized (mLock) {
            	//标记为清空
                mClear = true;
                return this;
            }
        }
        public void apply() {
            final long startTime = System.currentTimeMillis();
			//将修改先提交到内存
            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                    public void run() {
                        try {
                        	//阻塞等待
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }

                        if (DEBUG && mcr.wasWritten) {
                            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                    + " applied after " + (System.currentTimeMillis() - startTime)
                                    + " ms");
                        }
                    }
                };
			//添加一个等待任务到QueuedWork
            QueuedWork.addFinisher(awaitCommit);

            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        //移除等待任务
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
			//将提交到内存后的结果写入磁盘
            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);
        }        
        public boolean commit() {
            long startTime = 0;

            if (DEBUG) {
                startTime = System.currentTimeMillis();
            }
			//将修改先提交到内存
            MemoryCommitResult mcr = commitToMemory();
			//将提交到内存后的结果写入磁盘
            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;
        }
  • 当我们调用putXXX方法的时候,数据会先被缓存到EditorImpl 中的临时map。然后,无论我们调用commit还是apply修改都会先提交到内存 commitToMemory。
  • commitToMemory完成后无论是commit和apply都会调用enqueueDiskWrite将数据写入磁盘。区别是commit会阻塞等待写入磁盘完成,传入的第二个参数为null;apply会传入一个Runnable 。
  • 如果我们设置了监听,最后会回调监听。如果是commit,回调监听一定发生在写入磁盘之后。如果是apply,修改提交到内存后就会回调,并不保证数据已经写入磁盘。

接下来看看最重要的两个函数commitToMemory和enqueueDiskWrite。
commitToMemory

// Return value from EditorImpl#commitToMemory()
    private static class MemoryCommitResult {
        final long memoryStateGeneration;
        //修改的key集合
        @Nullable final List<String> keysModified;
        //设置的监听
        @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
        //需要写入磁盘的数据
        final Map<String, Object> mapToWriteToDisk;
        //同步锁
        final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

        @GuardedBy("mWritingToDiskLock")
        volatile boolean writeToDiskResult = false;
        boolean wasWritten = false;

        private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
                @Nullable Set<OnSharedPreferenceChangeListener> listeners,
                Map<String, Object> mapToWriteToDisk) {
            //正在发生的内存修改次数
            this.memoryStateGeneration = memoryStateGeneration;
            //修改的key的集合
            this.keysModified = keysModified;
            //监听哪些key被修改
            this.listeners = listeners;
            //需要写入磁盘的数据
            this.mapToWriteToDisk = mapToWriteToDisk;
        }

        void setDiskWriteResult(boolean wasWritten, boolean result) {
            this.wasWritten = wasWritten;
            writeToDiskResult = result;
            //释放锁
            writtenToDiskLatch.countDown();
        }
    }
// Returns true if any changes were made
        private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            List<String> keysModified = null;
            Set<OnSharedPreferenceChangeListener> listeners = null;
            Map<String, Object> 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<String, Object>(mMap);
                }
                //将源数据赋值给临时变量以便后面遍历修改
                mapToWriteToDisk = mMap;
                //正在排队的磁盘写入任务数量
                mDiskWritesInFlight++;

                boolean hasListeners = mListeners.size() > 0;
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }

                synchronized (mEditorLock) {
                    boolean changesMade = false;
					//如果有被标记为清空
                    if (mClear) {
                        if (!mapToWriteToDisk.isEmpty()) {
                            changesMade = true;
                            //将源数据清空
                            mapToWriteToDisk.clear();
                        }
                        mClear = false;
                    }
					//遍历被修改的数据
                    for (Map.Entry<String, Object> 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.
                        //这里别忘了,在remove的时候传入了this,标记删除
                        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);
                        }

                        changesMade = true;
                        if (hasListeners) {
                        	//只有设置了监听者才缓存修改的key
                            keysModified.add(k);
                        }
                    }
					//清空临时数据
                    mModified.clear();

                    if (changesMade) {
                    	//将正在发生的内存修改加1,写入磁盘的时候会根据这个值来确定是否有必要写入
                        mCurrentMemoryStateGeneration++;
                    }

                    memoryStateGeneration = mCurrentMemoryStateGeneration;
                }
            }
            //返回内存修改结果
            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
        }
  • 将源数据与mModified遍历组合生成真正需要写入磁盘的数据,数据会全量写入xml文件。
  • 通过mCurrentMemoryStateGeneration来记录正在发生的内存修改次数,用来保证最后写入磁盘的数据是最后一次修改的,减少IO操作的数量。
  • 用mDiskWritesInFlight来标记正在排队的磁盘写入任务数量。
  • 清空标记清空的是源数据,并没有清空缓存数据。例如: sp.edit().putString(“name”, “张三”).clear().apply();存在临时map里面的“name”对应的数据不会被清空,最终会被写入磁盘。
  • 只有我们设置了监听者,才有必要缓存修改的key。同时需要注意,我们设置的listener是通过弱引用缓存的,如果我们直接通过匿名内部类进行设置很有可能无法达到预期。
@GuardedBy("mLock")
    private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
            new WeakHashMap<OnSharedPreferenceChangeListener, Object>();

enqueueDiskWrite

/**
     * Enqueue an already-committed-to-memory result to be written
     * to disk.
     *
     * They will be written to disk one-at-a-time in the order
     * that they're enqueued.
     *
     * @param postWriteRunnable if non-null, we're being called
     *   from apply() and this is the runnable to run after
     *   the write proceeds.  if null (from a regular commit()),
     *   then we're allowed to do this disk write on the main
     *   thread (which in addition to reducing allocations and
     *   creating a background thread, this has the advantage that
     *   we catch them in userdebug StrictMode reports to convert
     *   them where possible to apply() ...)
     */
    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final boolean isFromSyncCommit = (postWriteRunnable == null);

        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                    	//写入到磁盘文件
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                    	//需要排队写入的任务数量减一
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                    	//只有apply才不为null
                        postWriteRunnable.run();
                    }
                }
            };

        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
        	//如果是通过调用commit写入
            boolean wasEmpty = false;
            synchronized (mLock) {
            	//是否存在多个写入任务
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
            	//当前任务只有一个,那就是自己,直接同步调用run方法写入文件即可
                writeToDiskRunnable.run();
                return;
            }
        }
		//mDiskWritesInFlight大于1,表示自己之前还有需要执行的写入任务,那就把自己加入队列。
		//这里8.0以上和8.0以下实现方式有些不一样。在7.0上是调用的是
		//QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

    private static FileOutputStream createFileOutputStream(File file) {
        FileOutputStream str = null;
        try {
            str = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            File parent = file.getParentFile();
            if (!parent.mkdir()) {
                Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
                return null;
            }
            FileUtils.setPermissions(
                parent.getPath(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                -1, -1);
            try {
                str = new FileOutputStream(file);
            } catch (FileNotFoundException e2) {
                Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
            }
        }
        return str;
    }

    @GuardedBy("mWritingToDiskLock")
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
        long startTime = 0;
        long existsTime = 0;
        long backupExistsTime = 0;
        long outputStreamCreateTime = 0;
        long writeTime = 0;
        long fsyncTime = 0;
        long setPermTime = 0;
        long fstatTime = 0;
        long deleteTime = 0;

        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        boolean fileExists = mFile.exists();

        if (DEBUG) {
            existsTime = System.currentTimeMillis();

            // Might not be set, hence init them to a default value
            backupExistsTime = existsTime;
        }

        // Rename the current file so it may be used as a backup during the next read
        if (fileExists) {
            boolean needsWrite = false;

            // Only need to write if the disk state is older than this commit
            if (mDiskStateGeneration < mcr.memoryStateGeneration) {
                if (isFromSyncCommit) {
                    needsWrite = true;
                } else {
                    synchronized (mLock) {
                        // No need to persist intermediate states. Just wait for the latest state to
                        // be persisted.
                        if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                            needsWrite = true;
                        }
                    }
                }
            }

            if (!needsWrite) {
                mcr.setDiskWriteResult(false, true);
                return;
            }

            boolean backupFileExists = mBackupFile.exists();

            if (DEBUG) {
                backupExistsTime = System.currentTimeMillis();
            }

            if (!backupFileExists) {
                if (!mFile.renameTo(mBackupFile)) {
                    Log.e(TAG, "Couldn't rename file " + mFile
                          + " to backup file " + mBackupFile);
                    mcr.setDiskWriteResult(false, 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 (DEBUG) {
                outputStreamCreateTime = System.currentTimeMillis();
            }

            if (str == null) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
            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);
        }

        // 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, false);
    }
}

虽然代码行数比较多,但是逻辑很简单。这里贴以下QueuedWork的部分源码

/**
     * Lazily create a handler on a separate thread.
     *
     * @return the handler
     */
    private static Handler getHandler() {
        synchronized (sLock) {
            if (sHandler == null) {
                HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                        Process.THREAD_PRIORITY_FOREGROUND);
                handlerThread.start();

                sHandler = new QueuedWorkHandler(handlerThread.getLooper());
            }
            return sHandler;
        }
    }
        /**
     * Queue a work-runnable for processing asynchronously.
     *
     * @param work The new runnable to process
     * @param shouldDelay If the message should be delayed
     */
    public static void queue(Runnable work, boolean shouldDelay) {
        Handler handler = getHandler();

        synchronized (sLock) {
            sWork.add(work);

            if (shouldDelay && sCanDelay) {
                handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
            } else {
                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
            }
        }
    }
  • 当我们调用的是commit提交数据,如果当前没有正在执行的磁盘写入任务,就直接同步调用写文件操作;如果存在,就将任务加入到QueuedWork通过HandlerThread在新线程里执行。commit会通过mcr.writtenToDiskLatch.await()加锁等待,当磁盘写入完成后通过mcr.setDiskWriteResult(true, true)通知commit释放锁,继续往后执行。
  • 当调用apply提交数据,会先提交一个等待任务到QueuedWork.addFinisher(awaitCommit),任务中包含了等待锁mcr.writtenToDiskLatch.await(),只有写入磁盘完成后才会释放该锁,并移除等待任务。然后和commit一样,将任务加入队列,异步执行。
  • 当Activity、service生命周期变化的时候,ActivityThread 的handleSleeping 、handleServiceArgs、handleStopService、handleStopActivity等方法会被调用,最终会调用到QueuedWork.waitToFinish(),如果我们通过QueuedWork.addFinisher(awaitCommit)加入的等待任务超时未释放等待锁,有可能会导致ANR。美团的这篇文章有详细介绍,文章链接

虽然SharedPreferences有它的缺陷和不足。如:

  • 调用getXXX获取数据可能会导致线程阻塞。
  • SharedPreferences为了提高性能会将数据保存在内存里面,加载内存负担。
  • 文件写入是全量写入,如果xml文件过大会影响性能。
  • apply虽然是异步,也有可能导致ARN。

但通过分析源码,我们能够找到它出现这些问题的原因,并给出一定的解决方案:

  • 通过异步或者延迟加载的方式首次加载数据到内存。
  • 尽量将需要频繁修改的数据单独保存为一个文件,减小全量写入带来的性能损耗。
  • 尽量多次调用putXXX后再一次apply或commit,减少IO操作。

如有不足,欢迎指正。