Android中的SharedPreference源码整理总结

 

SharedPreference是Android中的轻量级的存储方式,将键值对写入xml文件中,并保存在/data/data/package_name/shared_prefs路径下。

1、SharedPreferences.java

它是一个interface,如下:

public interface SharedPreferences {

    public interface OnSharedPreferenceChangeListener {

        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
    }

    public interface Editor {
        /**
         * 写入键值对
         */
        Editor putString(String key, String value);

        Editor putStringSet(String key, Set<String> values);

        Editor putInt(String key, int value);

        Editor putBoolean(String key, boolean value);

        /**
         * 清除键值为key的preference
         */
        Editor remove(String key);

        /**
         * 清除preference所有内容
         */
        Editor clear();

        /**
         *commit将其preference同步写入持久存储;
         *写入成功后返回true
         *如果不关心返回值,并在应用主线程中使用,请考虑使用apply()以替换commit()
         */
        boolean commit();

        /**
         *apply启动异步将更改提交到磁盘;没有返回值,如果失败,将不会收到任何通知。
         */
        void apply();
    }
    /**
     * Retrieve all values from the preferences.
     */
    Map<String, ?> getAll();
    /**
     * 获取数据
     */
    String getString(String key, String defValue);

    Set<String> getStringSet(String key, Set<String> defValues);

    int getInt(String key, int defValue);

    boolean getBoolean(String key, boolean defValue);

    void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);

    void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
}

2、SharedPreference的获取

通过 Context#getSharedPreferences 方式获取。

在Context.java中,getSharedPreferences()是一个抽象方法,

public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);

具体实现在ContextImpl.java中。

这里先说一下这个方法中的两个参数:

name:preference文件名;

mode:有MODE_PRIVATE,MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE,MODE_MULTI_PROCESS。其中MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE在AndroidN以后版本不能使用,若使用会抛SecurityException异常;MODE_MULTI_PROCESS在某些Android版本上支持不好,未来可能会Deprecated,设置了此mode,每次getSharedPreferences时都会重新检查并加载磁盘文件。(如需精确的跨进程数据管理,应使用ContentProvider.)

ContextImpl.getSharedPreferences():

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        // 判空处理 "null.xml".
        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) {
                //创建name.xml文件
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }
    @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) {
                //检查mode
                checkMode(mode);
                if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
                    if (isCredentialProtectedStorage()
                            && !getSystemService(UserManager.class)
                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                        //AndroidO及以后版本,sp是ce加密,在用户第一次解锁之前无法使用
                        throw new IllegalStateException("SharedPreferences in credential encrypted "
                                + "storage are not available until after user is unlocked");
                    }
                }
                //创建sp,保存到缓存中并返回
                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) {
            //多进程模式需要重新读取文件
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }

Sharedpreferences的实现类是SharedPreferencesImpl类,构造方法如下:

    SharedPreferencesImpl(File file, int mode) {
        mFile = file; //磁盘上的xml文件
        mBackupFile = makeBackupFile(file); //xml.bak备份文件,用于写入失败时的恢复
        mMode = mode;
        mLoaded = false;
        mMap = null; //用于内存中缓存数据,getXxx从这里获取数据
        mThrowable = null;
        startLoadFromDisk();
    }

构造方法中涉及到startLoadFromDisk(),用于从磁盘读取文件:

    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        //启动了一个名为"SharedPreferencesImpl-load"从磁盘加载数据
        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);
            }
        }

        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);
                    //将xml文件转换为map
                    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) {

        } catch (Throwable t) {
            thrown = t;
        }
        synchronized (mLock) {
            //设置mLoaded为true,表示已经读取过,下次getSharedPreference时不再从磁盘读取
            mLoaded = true;
            mThrowable = thrown;

            try {
                if (thrown == null) {
                    if (map != null) {
                        //将map赋给全局变量mMap
                        mMap = map;
                        mStatTimestamp = stat.st_mtim;
                        mStatSize = stat.st_size;
                    } else {
                        mMap = new HashMap<>();
                    }
                }
            } catch (Throwable t) {
                mThrowable = t;
            } finally {
                //已经加载完毕,释放mLock锁,通知其他等待线程。
                mLock.notifyAll();
            }
        }
    }

3、SharedPreference获取数据方法

以getString(String, String)为例,代码如下:

    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            //阻塞等待sp从磁盘读取xml到内存之后再get数据
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            //v为null,则返回默认值
            return v != null ? v : defValue;
        }
    }


    private void awaitLoadedLocked() {
        ...;
        while (!mLoaded) {
            try {
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
        if (mThrowable != null) {
            throw new IllegalStateException(mThrowable);
        }
    }

awaitLoadedLocked是等待xml文件加载完毕;如果首次执行getSharedPreferences后立即调用getXxx ,若加载还未完成(mLoaded为false), getXxx 会卡在awaitLoadedLocked,一旦加载完毕,则加载线程会通过notifyAll通知所有在 awaitLoadedLocked中等待的线程,getXxx 就能够返回了。sp最好不要加载过大的文件,否则阻塞时间过长。

4、SharedPreference写入及移除数据

4.1、写入数据需要Editor对象,先来看一下SharedPreferencesImpl.java中Editor对象的获取:

    public Editor edit() {
        synchronized (mLock) {
            awaitLoadedLocked();
        }
        //每次调用edit()都会创建一个EditorImpl对象
        return new EditorImpl();
    }

EditorImpl类中有两个成员变量mModified和mClear,其中mModified表示每次putXxx后的改变的配置项,mClear表示清空配置项,但是只清了SharedPreferenceImpl的mMap。

现在来看EditorImpl中的put,remove方法:

        public Editor putString(String key, @Nullable String value) {
            synchronized (mEditorLock) {
                //将键值对存入mModified
                mModified.put(key, value);
                return this;
            }
        }

        public Editor remove(String key) {
            synchronized (mEditorLock) {
                //remove中存入value是this
                mModified.put(key, this);
                return this;
            }
        }

可见,修改值只会将其暂存到mModified中,在Editor中所做的所有更改直到调用了commit()apply()才会设置到mMap(内存)和xml文件(磁盘)中去。

4.2、commit及apply实现

commit()方法:

        public boolean commit() {
            //完成内存写入,并返回一个MemoryCommitResult对象
            MemoryCommitResult mcr = commitToMemory();
            //放入磁盘写入队列
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                //等待其他线程执行完毕 writtenToDiskLatch.countdown减为0后从await方法返回
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            } finally {
            }
            //通知监听者
            notifyListeners(mcr);
            //返回提交结果
            return mcr.writeToDiskResult;
        }

内存写入:

        private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            List<String> keysModified = null;
            Set<OnSharedPreferenceChangeListener> listeners = null;
            Map<String, Object> mapToWriteToDisk;
            synchronized (SharedPreferencesImpl.this.mLock) {
                if (mDiskWritesInFlight > 0) {
                    //此时正在写入,无法修改
                    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;
                    //如果调用了clear方法,会先执行clear操作
                    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();
                        //remove方法里面存放的value是this,如果是this就去执行remove操作
                        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;
                                }
                            }
                            //发生了修改,则存入map中
                            mapToWriteToDisk.put(k, v);
                        }
                        //标记发生了修改
                        changesMade = true;
                        if (hasListeners) {
                            keysModified.add(k);
                        }
                    }
                    //将修改的map:mModified置为null
                    mModified.clear();
                    if (changesMade) {
                        //提交到内存的次数
                        mCurrentMemoryStateGeneration++;
                    }
                    memoryStateGeneration = mCurrentMemoryStateGeneration;
                }
            }
            //将提交到内存次数,发生修改的集合,监听者及map封装成MemoryCommitResult并返回
            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
        }

内存修改完成后,接着加入磁盘写入队列:

    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        //commit为true,apply为false
        final boolean isFromSyncCommit = (postWriteRunnable == null);
        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        //写入文件
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    //apply操作时此runnable不为null,apply操作时该runnable执行
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };
        //commit操作时才执行
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                //writeToDiskRunnable执行,执行写入文件
                writeToDiskRunnable.run();
                return;
            }
        }
        //apply操作时才会执行
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

由上述代码段看出commit()是同步的,通过enqueueDiskWrite()调用 writeToFile()方法将数据写到磁盘。

apply()方法:

apply方法利用QueuedWork实现异步操作.

        public void apply() {
            final long startTime = System.currentTimeMillis();
            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //线程等待
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };
            //将awaitCommit加入QueuedWork
            QueuedWork.addFinisher(awaitCommit);
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
            //放入写队列
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
            //通知监听者
            notifyListeners(mcr);
        }

通过阅读源码,整理总结如下:

1、sharedpreference是一个使用二级缓存(磁盘用xml文件,内存使用hashmap)的key-value存储方式

2、sp写入文件不要太大,否则会导致卡顿等

3、xml解析速度较慢,空间占用大,因此对于高频变化的存储场景不适用

4、不要多次调用edit(),每次调用都会创建一个editor对象

5、不要多次调用apply()或commit(),多次写入的话应该批量修改,最后一次执行调用

6、尽可能早地调用getSharedPreference,这样可以确保在调用getXxx或putXxx时数据已经完成写入内存

7、sharedpreference的跨进程模式只是在获取sharedpreference时重新从磁盘读取文件,使用此模式时每次都要通过getSharedPreferences获取sharedpreference变量,重新load数据,这样会引起其他线程等待耗时,降低内存读写性能。跨进程使用它是不可靠的。

 

    原文作者:xj_hsym
    原文地址: https://blog.csdn.net/hsym321/article/details/89164617
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞