SharedPreferences 源码分析

我们先看一下SharedPreferences (下文用 SP 简写替代)的用法。

        SharedPreferences preferences = getSharedPreferences("name", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("key", "value");
        editor.commit();  // 或者 editor.apply();

第一步是 获取 sp 对象;

第二步是 获取 Editor 对象;

第三步是 将要存放的内容以 key, value 的型式放进去

第四步是 使用 Editor.commit() 或者 Editor.apply() 方法提交保存的内容,这两个方法的区别我们在后面会讲到;

下面我们看一下详细的内容

第一步 获取 sp 对象

Context 只是一个接口,真正实现的是它的实现类 ContextImpl, 所以

 SharedPreferences preferences = Context.getSharedPreferences("name", Context.MODE_PRIVATE);

方法,最终调用的是 ContextImpl 里面的方法

 SharedPreferences preferences = ContextImpl.getSharedPreferences("name", Context.MODE_PRIVATE);

看看在 ContextImpl 里面的 getSharedPreferences(...) 方法的源码

 @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            if (sSharedPrefs == null) {
                // sSharedPrefs 以 PackageName 为 key 保存 ArrayMap<String, SharedPreferencesImpl>();
                // 而 ArrayMap<String, SharedPreferencesImpl>() 使用文件名 为 key 的 Map.
                sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
            }

            final String packageName = getPackageName();
            ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
            if (packagePrefs == null) {
                packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
                sSharedPrefs.put(packageName, packagePrefs);
            }

            // 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.
            // 可以以 null 作为文件名, null.xml
            if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                if (name == null) {
                    name = "null";
                }
            }

            // 从 ArrayMap<String, SharedPreferencesImpl>() , 中取出 SharedPreferenceImpl.
            sp = packagePrefs.get(name);
            if (sp == null) {  // 第一次的时候 sp 是为空的, 创建一个新的 SharedPreferencesImpl,
                // 放入 Map 中, 并返回 SharedPreferencesImpl 对象
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                packagePrefs.put(name, 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();
        }
        // 非第一次的时候直接返回  SharedPreferencesImpl 对象
        return sp;
    }

说明:

1.存放的文件名可以为空,它会生成一个以 null 为文件名的 null.xml 文件;

2.SharedPreferences 只是接口,它的是实现类是 SharedPreferencsImpl, 所以getSharedPreferences 返回的是

SharedPreferencesImpl.

第一次使用 sp 的时候会生成一个新的 SharedPreferencesImpl 对象

  SharedPreferencesImpl(File file, int mode) {
        mFile = file;  // 正式文件
        mBackupFile = makeBackupFile(file);  // 备份文件
        mMode = mode;   // 类型
        mLoaded = false;    // 加载标记位
        mMap = null;        // 存放内容的 Map
        startLoadFromDisk();
    }

    private void startLoadFromDisk() {
        synchronized (this) {
            mLoaded = false;
        }
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                synchronized (SharedPreferencesImpl.this) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }

    // 从磁盘中读取数据
    private void loadFromDiskLocked() {
        if (mLoaded) {
            return;
        }
        // 如果备份文件存在,则将 mFile 文件删除,然后将备份文件的名称改为 mFile 文件的名称
        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 (XmlPullParserException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (IOException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
        }
        // 加载标志位
        mLoaded = true;
        if (map != null) {
            mMap = map;
            mStatTimestamp = stat.st_mtime;
            mStatSize = stat.st_size;
        } else {
            mMap = new HashMap<String, Object>();
        }
        notifyAll();
    }

    private static File makeBackupFile(File prefsFile) {
        return new File(prefsFile.getPath() + ".bak");
    }

说明:

1.生成 SharedPreferencesImpl 对象时,回到调用 startLoadFromDisk() 方法,在该方法里面,加了同步锁,并且启动新

线程,在新线程中调用 loadFromDiskLocked() 方法;

2. loadFromDiskLocked() 才是真正从磁盘中读取数据的方法,通过 XmlUtils 工具类将数据存放到 Map 中。

第二步是 获取 Editor 对象

Editor 也是一个接口,它的实现类是 EditorImpl;

第三步是 将要存放的内容以 key, value 的型式放进去

Editor.putXXX(...) 方法调用的是 EditorImpl.putXXX(...) 方法;

这里以 EditorImpl.putString(...) 方法分析,其他方法都是一样,只是返回值不一样而已;

  // 放入到 Map 中
        public Editor putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

说明:

mModified 是一个 HashMap , 是 EditorImpl 类的一个成员变量

 private final Map<String, Object> mModified = Maps.newHashMap();

第四步 提交数据

我们先看 EditorImpl.commit() 方法

        public boolean commit() {
            // 将前面 put 的值 写到内存中,其实是拷贝到 mMap 中,同时修改一些 mcr 的值
            MemoryCommitResult mcr = commitToMemory();
            //  写到磁盘中
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);
            try {
                // 等待前面的写进磁盘等工作的完成,使用 CountDownLatch 等待
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            }
            // 回调接口
            notifyListeners(mcr);
            // 返回执行结果,boolean 类型
            return mcr.writeToDiskResult;
        }

说明:

1.将数据提交到内存中;

2.将数据写进磁盘中;

3.使用 CountDownLatch 等待写磁盘的工作;

4.回调接口;

5.返回执行结果。

commit() 是等数据写进磁盘的工作完成之后,才返回一个执行结果

EditorImpl.apply() 方法

 public void apply() {
            // 提交到内存
            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                public void run() {
                    try {
                        // 等待线程执行完成
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
                }
            };

            // 只是放到链表队列中 ConcurrentLinkedQueue<Runnable>(),,并没有执行
            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);
        }

说明:

1.将数据提交到内存;

2.将等待任务放入到链表队列中;

3.将数据写进磁盘中,同时启动等待任务;

4.回调

apply() 方法是没有返回值的

那我们该怎样选择这两个方法呢,官方的文档(链接)上面有提到

Unlike commit(),
which writes its preferences out to persistent storage synchronously, apply() commits
its changes to the in-memorySharedPreferences immediately
but starts an asynchronous commit to disk and you won‘t be notified of any failures. If another editor on thisSharedPreferences does
a regular commit() while
apply() is
still outstanding, the commit() will
block until all async commits are completed as well as the commit itself.

If you don‘t care about the return value and you‘re using this from your
application‘s main thread, consider using apply() instead.

apply() 方法提交数据是异步的,它只管提交,但是不知道提交是否成功;commit() 方法是同步提交数据,它会等待数据写进磁盘后才返回结果,这个结果可能是提交成功也或者是失败的。commit()
方法是在 UI 线程中写数据的,可能会对 UI 造成卡顿现象,而 apply() 中写数据进磁盘是在线程池中执行。

如果不在意返回结果,在 UI 线程时,尽量用 apply() 方法。

commit() 和 apply() 方法里面都调用了 commitToMemory() 方法。这个方法只是修改了
mcr 的一些值,同时将 mModified 的值拷贝到 mMap 中,并清空 mModified.

 // Returns true if any changes were made
        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;
                    }

                    // 将 mModified 中的值拷贝到 mMap 中
                    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
                    mModified.clear();
                }
            }
            return mcr;
        }

写进磁盘的工作

 *     commit 直接在当前线程中写磁盘,而 apply  是在线程池中执行写磁盘动作
     *      如果是在 UI 中执行 commit ,则可能会造成 UI 的卡顿。所以,如果不需要返回
     *      结果用 apply 方法
     */
    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) {  // commit 时候为空, apply 时执行
                    postWriteRunnable.run();
                }
            }
        };

        final boolean isFromSyncCommit = (postWriteRunnable == null);

        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {  //  commit 时执行
            boolean wasEmpty = false;
            synchronized (SharedPreferencesImpl.this) {
                // 在 commitToMemory 中已经 +1 ,所以,wasEmpty = true;
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                // 执行写进磁盘任务
                writeToDiskRunnable.run();
                return;
            }
        }

        // apply 时用线程池执行
        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    }

说明:

1.commit() 方方法是会在当前线程中直接执行 writeToDiskRunnable 任务,而
apply() 则是放进线程池中执行。

从这里我们可以了解到,在UI线程中使用 commit() 方法可能会造成 UI 线程的卡顿;

2. applay() 方法在线程池中执行了写磁盘任务 writeToDiskRunnable,和等待任务 postWriteRunnable.

回调 notifiyListeners 是在主线程中回调的

        // 确保在 UI 线程中进行回调
        private void notifyListeners(final MemoryCommitResult mcr) {
            if (mcr.listeners == null || mcr.keysModified == null ||
                    mcr.keysModified.size() == 0) {
                return;
            }
            if (Looper.myLooper() == Looper.getMainLooper()) {
                for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
                    final String key = mcr.keysModified.get(i);
                    for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                        if (listener != null) {
                            listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                        }
                    }
                }
            } else {
                // Run this function on the main thread.
                ActivityThread.sMainThreadHandler.post(new Runnable() {
                    public void run() {
                        notifyListeners(mcr);
                    }
                });
            }
        

这样,整个存数据的工程就已经介绍了,我们看看存数据的过程。取数据的过程,这要看
EditorImpl.getXXX(...)

    public String getString(String key, String defValue) {
        synchronized (this) {
            // 等待从文件中读取数据完成,并将数据放置到 mMap 中
            awaitLoadedLocked();
            // 等待完成,则可以从 mMap 中读取数据
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

awaitLoadedLoacked() 方法就是等待,等待前面的数据准备好

 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();
        }
        // 会等待 loadFromDiskLocked() 方法中 mLoaded 置为 true 就不会等待
        while (!mLoaded) {
            try {
                wait();
            } catch (InterruptedException unused) {
            }
        }
    }

SharedPreferences 源码的分析过程完成了。

时间: 2024-08-03 06:47:47

SharedPreferences 源码分析的相关文章

Android SharedPreferences源码分析.md

我们经常使用SharedPreferences保存一些简单的数据,比如Settings的数据.如果我们只是简单的使用,可能没什么问题,但是如果要用好它还是得明白它的实现方式,下面来从源码上来分析下SharedPreferences的缓存,异步读写实现,多线程,多进程访问. SharedPreferences简介 SharedPreferences是Android提供的一种使用XML文件保存内容的机制.其内部就是通过xml写入文件的. SharedPreferences是一个接口类,这是使用它的一

Android源码分析之SharedPreferences

在Android的日常开发中,相信大家都用过SharedPreferences来保存用户的某些settings值.Shared Preferences 以键值对的形式存储私有的原生类型数据,这里的私有的是指只对你自己的app可见的,也就是说别的app是无法访问到的. 客户端代码为了使用它有2种方式,一种是通过Context#getSharedPreferences(String prefName, int mode)方法, 另一种是Activity自己的getPreferences(int mo

AsyncHttpClient 源码分析

上一篇文章从功能和用法上对AsyncHttpClient做了个大致介绍,今天我们和以往一样,从内部实现.原理的角度带领大家看看 其工作机制,以便加深理解.写程序越多,我发现每接触一个新东西,都会有强烈的想知道它内部怎么工作的冲动.可能只有知道了 内部原理能更容易写出高质量的代码吧. 我大概浏览了下其代码,关键部分可以分为这4个模块: 1. AsyncHttpClient自己一个模块: 2. AsyncHttpRequest和RequestHandler一个模块: 3. AsyncHttpResp

Android分包MultiDex源码分析

概述 Android开发者应该都遇到了64K最大方法数限制的问题,针对这个问题,google也推出了multidex分包机制,在生成apk的时候,把整个应用拆成n个dex包(classes.dex.classes2.dex.classes3.dex),每个dex不超过64k个方法.使用multidex,在5.0以前的系统,应用安装时只安装main dex(包含了应用启动需要的必要class),在应用启动之后,需在Application的attachBaseContext中调用MultiDex.i

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =