美文网首页
SharedPreferences

SharedPreferences

作者: 拿拿guardian | 来源:发表于2020-03-19 18:01 被阅读0次

SP几个关键方法。

1.getSharedPreferences

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        File file;
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name); //尝试从内存的mSharedPrefsPaths中获取xml文件
            if (file == null) {
                file = getSharedPreferencesPath(name); //如果不存在,则new一个file,保存在mSharedPrefsPaths里。
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

然后就是获取SharedPreferences对象,如果sSharedPrefsCache有缓存,直接返回;没有的话new一个SharedPreferencesImpl对象。

    @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) {
                .....
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        ...
        return sp;
    }

2.SharedPreferencesImpl构造方法

    SharedPreferencesImpl(File file, int mode) {
        ...
        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;
            }
            //如果是走调用链startReloadIfChangedUnexpectedly->startLoadFromDisk->loadFromDisk,会把备份文件还原成mFile,直接使用。
         
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

        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) {
            // An errno exception means the stat failed. Treat as empty/non-existing by
            // ignoring.
        } catch (Throwable t) {
            thrown = t;
        }

        synchronized (mLock) {
            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(); //唤醒其他被阻塞的线程
            }
        }
    }

可以看出,每个SharedPreferencesImpl对象,会去解析sp的xml文件,将k-v保存在内存里

private Map<String, Object> mMap;

3.getString等get方法。

会等待直到loadFromDisk方法执行完,将所有k-v独取出来。

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

4.Editor

Editor是个接口,实现类是内部类EditorImpl。
每次调用edit()方法都会new一个EditorImpl。所以尽量多次put一次提交,避免生成太多的editor对象。

 @Override
    public Editor edit() {
        synchronized (mLock) {
            awaitLoadedLocked();
        }
        return new EditorImpl();
    }

5.putString

EditorImpl内部的mModified集合保存了所有要修改的k-v。

    private final Map<String, Object> mModified = new HashMap<>();
    @Override
    public SharedPreferences.Editor putString(String key, @Nullable String value) {
        synchronized (mEditorLock) {
            mModified.put(key, value);
            return this;
        }
    }

6.commit

    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); //通知各种Listener
        return mcr.writeToDiskResult;
    }

7.apply

    public void apply() {

        final MemoryCommitResult mcr = commitToMemory();
        final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }
            }
        };

        QueuedWork.addFinisher(awaitCommit); //ANR元凶在这里,每个SP写入任务对应一个awaitCommit等待runnable

        Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                QueuedWork.removeFinisher(awaitCommit); //这一步是在writeToFile完成后执行,移除QueuedWork里等待runnable
            }
        };

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

8.commitToMemory

从mModified集合获取修改的k-v,与原有数据集合并。

    private MemoryCommitResult commitToMemory() {
        long memoryStateGeneration;
        List<String> keysModified = null;
        Set<SharedPreferences.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++;

            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.
                    if (v == this || v == null) { //如果value是this或者null,会移除remove掉key
                        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); //存在key,value不相等或者不存在key
                    }

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

                mModified.clear();

                if (changesMade) {
                    mCurrentMemoryStateGeneration++;
                }

                memoryStateGeneration = mCurrentMemoryStateGeneration;
            }
        }
        return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                mapToWriteToDisk);  //构造一个需要commit的MemoryCommitResult对象
    }

9.enqueueDiskWrite

private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
        final boolean isFromSyncCommit = (postWriteRunnable == null); //是否同步,apply和commit的区别

        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run(); //apply会走这一步,移除等待runnable
                    }
                }
            };

        // 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) {
                writeToDiskRunnable.run(); //如果当前只有一个任务,直接执行,不需入队了
                return;
            }
        }

        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); //多个任务,需要入队等待执行
    }

10. writeToFile写文件

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    ...
    boolean fileExists = mFile.exists();

    // 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 (!backupFileExists) { //创建一个备份文件,如果写如失败,下一次startLoadFromDisk时候使用备份文件。
            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 (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);


        try {
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (mLock) {
                mStatTimestamp = stat.st_mtim;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }

        // Writing was successful, delete the backup file if there is one.
        mBackupFile.delete(); //写入成功,删除备份文件

        mDiskStateGeneration = mcr.memoryStateGeneration;

        mcr.setDiskWriteResult(true, true);

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

11.几个注意点

1.apply是将修改提交到内存,再异步提交到磁盘文件,commit是同步的提交到磁盘文件;
2.多并发的提交commit时,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,容易阻塞;
3.apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率;
3.每次edit都会new一个EditImpl对象,应该获取一次获取edit(),然后多次执行putxxx(), 避免内存抖动;
4.sp每次写文件都是全量更新,效率低下;
5.mBackupFile使用场景有二:
5.1 loadFromDisk方法如是是从reloadSharedPreferences调过来的,如果mBackupFile存在,直接改名使用
5.2 writeToFile写文件时保存mBackupFile文件,如果写入成功,删除备份文件;如果写入失败,mBackupFile文件保留。
6.每次有k-v变动,都会保存在内存中,不需要再次从磁盘读取:

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; //在mapToWriteToDisk(mapToWriteToDisk和mMap指向同一个地址)做增删查改

7.跨进程。android targetSdkVersion < 11(3.0以前)或者设置了MODE_MULTI_PROCESS的时候,在getSharedPreferences会reload一遍重新读取k-v,但是如果getSharedPreferences执行结束后,A进程修改了k-v,B进程是获取不到更新的。所以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();
}

12.SP缺点

1.getString等操作在loadFromDisk执行完前是阻塞的,会阻塞主线程;
2.修改k-v是全量更新,性能不好;
3.commit会阻塞主线程,apply不会阻塞当前调用线程,但会引起ANR;
4.读写文件涉及两次IO操作,性能不好;
5.解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿;
6.k-v会永远存在于内存之中,占用大量内存。

相关文章

网友评论

      本文标题:SharedPreferences

      本文链接:https://www.haomeiwen.com/subject/uzljyhtx.html