Android SharedPreferences

这段时间公司的项目真的太忙了,所以博客更新的相对慢了,对自己找借口的行为深表不满,所以昨天极力抽出时间整理了一下SharedPreferences 的相关知识拿出来分享,那下面就说一下 SharedPreferences 的相关知识点。


一、概述



我们都知道在我们 Android 存储方法有很多种,例如 SharedPreferences,File 存储和 SD 卡存储,ContentProvider ,网络存储和 SQLite 存储。今天我们首先会分享一下 SharedPreferences 的存储用法和实现原来,后续会对以上几种方法进行分享。

在我们的项目中我相信大多数都会用到 SharedPreferences 进行存储数据,那 SharedPreferences 主要就是做一些相对简单的数据的存储,因为 SharedPreferences 底层实现和 Map 差不多,所以存储复杂的数据相对困难一些,好了介绍就做到这里,下面说一下SharedPreferences 的相关 API 和使用方法。

二、相关 API 和使用方法


我们首先看一下它的 API,API 相对来说非常少也非常简单:


首先是获取 SharedPreferences 对象:


1.this.getPreferences (int mode)

通过Activity对象获取,获取的是本Activity私有的Preference,保存在系统中的xml形式的文件的名称为这个Activity的名字,因此一个Activity只能有一个,属于这个Activity。

2.this.getSharedPreferences (String name, int mode)

因为Activity继承了ContextWrapper,因此也是通过Activity对象获取,但是属于整个应用程序,这个是我们最常用的获取 SharedPreferences 的方法, 从这个方法的 name 中我们可以看出,一个 APP 的 SharedPreferences 文件可以有多个,以第一参数的name为文件名保存在系统中。

mode可以是以下的值:

Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。


3.PreferenceManager.getDefaultSharedPreferences(this);
这个底层就是通过方法 2 实现的。

getXXX(String key)

通过此方法可以获取 key 对应的 value 值,XXX可以是基本类型


abstract Map< String key,XXX value> getAll() 

获取 SharedPreferences 的全部 key-value 键值对


boolean contains(String key) 

判断 key 对应的键值对是否存在


以上就是 SharedPreferences 的相关 API 了,基本没什么难度,从上面我们可以看出我们只看到了读数据的 API 没看到写数据的 API ,如果说到写数据,那就用到了下面这个类 SharedPreferences.Editor


sharedPreferences.edit() 

通过此方法获取 SharedPreferences.Editor 实例


SharedPreferences.Editor clear() 

清除 sharedPreferences 中的全部数据


SharedPreferences.Editor  putXXX(String key,XXX value)

向 sharedPreferences 插入数据,XXX 为基本类型的数据


SharedPreferences.Editor  remover(String key)

删除 key 对应的键值对


Boolean  commit ()

当我们对 sharedPreferences 进行操作之后我们要进行提交,那就会用到这个方法,如果我们不调用这个方法,那么我们的操作就不会生效


void apply()

这个方法和 commit 方法差不多,只不过这个方法没有返回值,而且我认为最大的区别就是通过这个方法提交的话,写入是在线程中进行的



三、使用中遇到的坑


虽然我们的 sharedPreferences 很简单,但是在使用过程中还是发现了很多问题首先就是存储复杂数据的时候有时候不成功,最主要的还是跨进程读取数据的时候不能实时更新,我先把结论写出来,然后我们再从源码的层面去分析问题


当一个进程启动之后对 sharedPreferences 的值进行了修改,那这个修改不会对其他【已经启动】的进程产生效果。只有当进程重启之后才会或者 app 重启之后其他进程才会感知这个 sharedPreferences 的修改

说了这么多,下面我们看一下 sharedPreferences 的实现原理




四、源码分析


首先我们先分析一下我们在获取 sharedPreferences 所做的操作,下面看代码:


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


从这段代码中我们可以看出,首先先去获取了 name 对应的 file 文件,如果没有对应的文件就去创建一个新的文件,创建之后去把这个文件存入 mSharedPrefsPath 中,那我们接着往下面看 getsharedPreferences 方法做了什么


@Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        checkMode(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;
            }
        }
        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 如果没有就去创建一个新的,那现在重点就是这个 new 方法是怎么去创建的,下面看代码



 private void loadFromDisk() {
        synchronized (SharedPreferencesImpl.this) {
            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 = XmlUtils.readMapXml(str);
                } catch (XmlPullParserException | IOException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
            /* ignore */
        }

        synchronized (SharedPreferencesImpl.this) {
            mLoaded = true;
            if (map != null) {
                mMap = map;
                mStatTimestamp = stat.st_mtime;
                mStatSize = stat.st_size;
            } else {
                mMap = new HashMap<>();
            }
            notifyAll();
        }
    }


当我们创建 SharedPreference 最后会走到这一步,那这一步比较关键的地方就是系统把 sharedPreference 对应的 file 转换成了 map,并且这个 map 还是 SharedPreference 成员变量,那我们是不是可以有一个猜想,我们在读写数据的时候是不是直接从这个 map 中进行读写的呢,那么我们还是从源码找一下答案



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


可以看到我们的读取数据是直接从 map 中读取的,那接下来看一下写的操作



public final class EditorImpl implements Editor {
        private final Map<String, Object> mModified = Maps.newHashMap();
        private boolean mClear = false;

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


可以看到我们的写的操作也是一样的,所以这样我们可以得出结论,在我们读写数据的时候,不会每次都加载 file,而是通过内存中的 map 进行读写的,所以对我们来说速度是非常快的,那么最后我们需要看的就是 apply 的源码了


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

            QueuedWork.add(awaitCommit);

            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.remove(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);
        }


上面的代码中我们看到,提交数据总共分两步,第一步是 commitToMemory ,第二步是 enqueueDiskWrite 那我们先看一下第一步的代码:


private MemoryCommitResult commitToMemory() {
            MemoryCommitResult mcr = new MemoryCommitResult();
            synchronized (SharedPreferencesImpl.this) {
                // 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);
                }
                mcr.mapToWriteToDisk = mMap;
                mDiskWritesInFlight++;

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

                synchronized (this) {
                    if (mClear) {
                        if (!mMap.isEmpty()) {
                            mcr.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) {
                            if (!mMap.containsKey(k)) {
                                continue;
                            }
                            mMap.remove(k);
                        } else {
                            if (mMap.containsKey(k)) {
                                Object existingValue = mMap.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            mMap.put(k, v);
                        }

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

                    mModified.clear();
                }
            }
            return mcr;
        }


这段代码主要就是把我们的 map 更新一下,那下一段代码其实也很简单其实就是把数据写入我们的 file 中,


private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final Runnable writeToDiskRunnable = new Runnable() {
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr);
                    }
                    synchronized (SharedPreferencesImpl.this) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };

        final boolean isFromSyncCommit = (postWriteRunnable == null);

        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (SharedPreferencesImpl.this) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }

        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    }


好了源码基本上就这些主要内容,最后如果想知道为什么不能多进程读取数据的话可以在 apply 方法的最后一句找到答案





总结


好了 SharedPreference 的基本知识就介绍到这里了,后面的话会介绍关于 SQLite 的相关知识,本篇文章如有错误欢迎指出,大家共同进步谢谢



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值