SharedPreferences源码浅析

一、实例获取

        在开发中,我们调用的是Context.getSharedPreferences(String name, int mode),实际的实现,是在ContextImpl中:

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    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);
}

        在这里,会根据传入的name去获取File:

@Override
public File getSharedPreferencesPath(String name) {
    return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
    synchronized (mSync) {
        if (mPreferencesDir == null) {
            mPreferencesDir = new File(getDataDir(), "shared_prefs");
        }
        return ensurePrivateDirExists(mPreferencesDir);
    }
}

        获取到SharedPreferences对应的xml文件后,最终会走到getSharedPreferences(File file, int mode)中去:

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    checkMode(mode);
    //Android O版本的新特性:在用户手机处于锁定状态时,应用不可访问内部存储空间,需要用户手动解锁后才可访问
    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;
}

        这里插一句,可以看到,Android O之后,会去判断是否可以访问内部储存,手机刚启动的时候,用户没有解锁,这时如果app跟随系统启动并且去使用SharedPreferences会抛出异常。

        首先会从缓存中去取,如果缓存中没有,就会new一个SharedPreferences实例,SharedPreferences的实际实现是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();
}

        初始化的时候先是创建了一个备份文件mBackupFile,然后开启一条线程去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;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16*1024);
                //读取数据,保存在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) {
            mMap = map;
            mStatTimestamp = stat.st_mtime;
            mStatSize = stat.st_size;
        } else {
            mMap = new HashMap<>();
        }
        mLock.notifyAll();
    }
}

        loadFromDisk的代码也比较简单,做了一些多线程安全处理,然后从文件中读取数据保存到map中,到此,初始化工作就完成了

二、读取数据

        我们通过SharedPreferences实例的getXxx方法就可以读取对应key的数据,我们以String为例:

@Nullable
public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

        awaitLoadedLocked是等待load的机制,load完成后,会notifyAll来唤醒所有等待的线程。然后就是从mMap中取出相应的value,这个mMap就是之前load过程中,从xml文件解析出来的数据。

三、写入数据

        我们要向SharedPreferences存入数据时,一般会先edit()获取一个Editor,然后通过Editor的putXxx来存入数据,最后再调用apply或者commit来保存数据到文件。
        首先看一下edit()方法:

public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();
    }

    return new EditorImpl();
}

        实际返回的是一个EditorImpl,所以我们来看一下EditorImpl的内容,首先是putXxx,还是以String为例:

public Editor putString(String key, @Nullable String value) {
    synchronized (mLock) {
        mModified.put(key, value);
        return this;
    }
}

        可以看到,这里把需要修改的值暂存到了mModified中,mModified是一个HashMap。那么暂存的数据是在哪里被使用的呢?是在commitToMemory()中:

commitToMemory

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复制一份
                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 (mLock) {
                boolean changesMade = false;

                if (mClear) {
                    if (!mMap.isEmpty()) {
                        changesMade = true;
                        mMap.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.
                    if (v == this || v == null) {
                        //在remove方法里,将需要删除的key对应的value设置成了this,相当于打了一个标记,在这里统一做remove处理
                        if (!mMap.containsKey(k)) {
                            continue;
                        }
                        mMap.remove(k);
                    } else {
                        if (mMap.containsKey(k)) {
                            Object existingValue = mMap.get(k);
                            if (existingValue != null && existingValue.equals(v)) {
                                //value的新值和旧值一样,不作处理
                                continue;
                            }
                        }
                        //更新value
                        mMap.put(k, v);
                    }

                    changesMade = true;
                    if (hasListeners) {
                        keysModified.add(k);
                    }
                }

                mModified.clear();

                if (changesMade) {
                    mCurrentMemoryStateGeneration++;
                }

                memoryStateGeneration = mCurrentMemoryStateGeneration;
            }
        }
        return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                mapToWriteToDisk);
    }

        经过分析,不难看出:commitToMemory()将mModified中存储的修改项一口气同步到了mMap中,修改结果封装成了一个MemoryCommitResult对象,MemoryCommitResult里面有listener列表、mMap里的所有数据,以及发生修改的kay的列表,还有一个内存标识(memoryStateGeneration)。

        那么这个commitToMemory()又是由谁来调用的呢?就是commit和apply,终于到了我们熟悉的方法了~不过别着急,还有几个方法需要先看一下

enqueueDiskWrite

private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
    final boolean isFromSyncCommit = (postWriteRunnable == null);
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    //写入文件,区分commit和apply
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    //执行完了当前任务数-1
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    // Typical #commit() path with fewer allocations, doing a write on
    // the current thread.
    //commit操作会走到这里来
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
            //mDiskWritesInFlight表示当前有多少个任务等待提交,可能并发提交多个修改
        }
        if (wasEmpty) {
            //如果只有一个提交需要执行就直接执行,如果之前还有多个提交在排队执行,就放到QueuedWork中排队执行
            writeToDiskRunnable.run();
            return;
        }
    }
    //排队执行writeToDiskRunnable
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

        这个方法主要是创建一个任务 writeToDiskRunnable并且交给线程池去执行。在这个任务中是负责将 mMap 中的数据写入到文件中,并且在写入完成后,调用postWriteRunnable.run();来执行写入任务完毕后的后续操作,例如从QueueWork移除任务等。

writeToFile

 private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    //debug用的数据,用来观测执行时间
    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();

        backupExistsTime = existsTime;
    }

    // 重命名当前文件,以便在下次读取时将其用作备份
    if (fileExists) {
        boolean needsWrite = false;

        // 如果磁盘状态早于此提交,则需要写入
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            if (isFromSyncCommit) {
                //同步的,commit走这里
                needsWrite = true;
            } else {
                //异步的,apply走这里,比commit多了一个判断,校验一下是否是本次提交,防止并发导致错乱
                synchronized (mLock) {
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }

        if (!needsWrite) {
            //不需要写入,直接通知完成
            mcr.setDiskWriteResult(false, true);
            return;
        }

        boolean backupFileExists = mBackupFile.exists();

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

        //判断备份文件是否存在
        if (!backupFileExists) {
            //没有备份文件,将mFile重命名成备份文件
            if (!mFile.renameTo(mBackupFile)) {
                Log.e(TAG, "Couldn't rename file " + mFile
                      + " to backup file " + mBackupFile);
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
            //有备份,删除file
            mFile.delete();
        }
    }

    // 上述步骤完成后,将只剩下备份文件,原文件被删除,后续写入操作将重新创建一个mFile去写入

    // 尝试写入文件,删除备份并尽可能以原子方式返回true。 如果发生任何异常,删除新文件; 下次我们将从备份中恢复。
    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();
        //写入完成,根据mMode,设置文件权限
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        if (DEBUG) {
            setPermTime = System.currentTimeMillis();
        }
        //保存文件状态
        try {
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (mLock) {
                mStatTimestamp = stat.st_mtime;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }

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

        // 写入成功,删除备份文件
        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(Long.valueOf(fsyncDuration).intValue());
        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);
}

        代码比较多,但是并不复杂。可以看到,在写入的时候,这里有一个备份机制,会先创建一个备份,然后删除原文件重新写入,如果写入成功了就删除备份,如果写入失败了,就从备份中恢复。

        做完了这些铺垫,我们就可以来看一下commit和apply了

commit

public boolean commit() {
    long startTime = 0;

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

    // 保存数据到mMap内存中,并返回一个MemoryCommitResult 对象
    MemoryCommitResult mcr = commitToMemory();
    // 创建任务
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        // 阻塞,等待setDiskWriteResult()后继续执行
        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;
}

        commit是阻塞式的,会通过CountDownLatch来挂起当前线程,等写入完成后唤醒并返回一个boolean变量值表示数据是否写入成功。

apply

public void apply() {
    final long startTime = System.currentTimeMillis();

    // 保存数据到mMap内存中,并返回一个MemoryCommitResult 对象
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            public void run() {
                try {
                    // 阻塞,等待setDiskWriteResult()后继续执行
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };

    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            public void run() {
                // 从 QueuedWork 中移除
                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);
}

        apply相对commit而言,是异步的,虽然也有CountDownLatch挂起线程的机制,但是挂起的线程是子线程,并不会阻塞主线程。

小结

写入方面的分析就到这里,相对来说复杂一些,所以小结一下:
1. 首先会通过commitToMemory将数据同步到内存中,也就是mMap,同时会将内存数据封装成一个MemoryCommitResult返回
2. 通过enqueueDiskWrite创建写入任务,如果是apply操作,会将任务放进QueuedWork中
3. 写入任务会执行真正的写入操作:writeToFile,这里有一个备份机制,会先创建一个备份,然后删除原文件重新写入,如果写入成功了就删除备份,如果写入失败了,就从备份中恢复。
4. 写入完成后,通过CountDownLatch唤醒挂起的线程

四、使用SharedPreferences的时候要小心

        经过上面的分析,大家应该都对SharedPreferences的各个流程有了一个大致的了解,那么我们平时在使用的时候,有哪些需要注意的呢?

1、不要存放大的key和value

        存放大的key和value会带来内存问题、界面卡顿以及ANR
        首先说内存问题,在初始化的时候,我们知道SharedPreferences会把xml解析成对象存到内存中,如果你存了很大的key和value的话,那自然会占用很大的内存。
        其次,大量的数据,会导致xml解析缓慢,轻则界面卡顿,重则ANR!有人说了,解析xml不是在子线程中么?为什么会引起卡顿?我们来看一下getString方法

@Nullable
public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

        注意这个awaitLoadedLocked()方法,之前我们也说了awaitLoadedLocked是等待load的机制,我们看一下代码:

private void awaitLoadedLocked() {
    if (!mLoaded) {
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
}

        看到了吗,awaitLoadedLocked()会阻塞当前线程,也就是调用getXxx的线程,直到初始化完成。所以,当我们第一次调用的getXxx的时候,其实是阻塞的,要等SharedPreferences初始化完成,所以,当SharedPreferences存了一堆乱七八糟的东西导致初始化慢的话,那必然会造成界面卡顿甚至ANR!

        那怎么办呢?第一,我们可以提前getSharedPreferences,让它去初始化,比如在开屏期间。第二,不要往里存大的数据,大数据可以存数据库什么的。

2、最好不要存json和html

        JSON或者HTML格式存放在sp里面的时候,需要转义,这样会带来很多&这种特殊符号,sp在解析碰到这个特殊符号的时候会进行特殊的处理,引发额外的字符串拼接以及函数调用开销,一样会增加初始化的时间。要是存了很大的json和html的话,那后果更是不堪设想,初始化的时候能阻塞好几秒,如果遇到了这样的猪队友,建议打死

3、能一次apply就别多次apply

        虽然apply是在子线程的,但是多次apply在一些特定情况下,也会引起主线程阻塞,我们一起来看一下:首先在上面分析apply时,我们知道写入任务最终是通过QueuedWork去排队执行的,我们可以看一下QueuedWork的queue方法:

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);
        }
    }
}

        这里通过handler发了一个消息

private static class QueuedWorkHandler extends Handler {
    static final int MSG_RUN = 1;

    QueuedWorkHandler(Looper looper) {
        super(looper);
    }

    public void handleMessage(Message msg) {
        if (msg.what == MSG_RUN) {
            processPendingWork();
        }
    }
}
private static void processPendingWork() {
    long startTime = 0;

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

    synchronized (sProcessingWork) {
        LinkedList<Runnable> work;

        synchronized (sLock) {
            work = (LinkedList<Runnable>) sWork.clone();
            sWork.clear();

            // Remove all msg-s as all work will be processed now
            getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
        }

        if (work.size() > 0) {
            // 轮询执行
            for (Runnable w : work) {
                w.run();
            }

            if (DEBUG) {
                Log.d(LOG_TAG, "processing " + work.size() + " items took " +
                        +(System.currentTimeMillis() - startTime) + " ms");
            }
        }
    }
}

        接到消息后,会轮询执行各个任务。我们来看一下这个handler的线程:

/**
 * 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;
    }
}

        可以看到,是创建了一个HandlerThread,用的不是主线程的Looper,所以QueuedWork里的任务都是异步的,不会阻塞主线程

        回归正题,我们现在知道在QueuedWork中执行任务,是单线程的,并没有线程池并发执行(毕竟是队列嘛,要有个顺序),所以说,当你多次apply之后,QueuedWork中会插入多个任务,然后依次写入,正常来讲,这并没有什么毛病,毕竟是异步的不会卡UI线程。但是!有一个地方会卡,我们看一下ActivityThread类的handleStopActivity

private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
    //省略无关代码

    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }

    //省略无关代码
}

        看到了吗,又要wait,这可是在主线程啊,我们往里看

public static void waitToFinish() {
    //省略无关代码
    try {
        while (true) {
            Runnable finisher;

            synchronized (sLock) {
                finisher = sFinishers.poll();
            }

            if (finisher == null) {
                break;
            }

            finisher.run();
        }
    } finally {
        sCanDelay = true;
    }
     //省略无关代码
}

        在这里,还是要等所有的finisher完成,finisher是哪里添加的呢?是在apply中,我们添加的一个叫awaitCommit的Runnable,在awaitCommit中,会通过CountDownLatch机制挂起线程,等待写入完成。

        看到这里,相信大家心里已经明白了,在Activity Stop的时候如果QueuedWork里还有任务,那么主线程会阻塞,等待所有任务完成。所以,就算apply是异步的,使用的时候也要注意,能一次就别多次,否则在Activity Stop的时候就有可能引发主线程阻塞

4、它不是进程安全的

        虽然它有一个MODE_MULTI_PROCESS,但是它在Android 3.0以后已经被废除了,而且还有一些其他方面的跨进程问题,总之,进程是不安全的,在多进程情况下,可能出现数据丢失,数据不同步的问题