Activity之SharedPreferences探究

先说说SharedPreferences的定义: 

        在frameworks\base\core\java\android\content\SharedPreferences.java 这个文件里定义了SharedPreferences.的Interface有其二段原文需要说说:

第一段:

         Interface for accessing and modifying preference data returned by {@linkContext#getSharedPreferences}.  这说明Preferences其实是实现了对数据的访问和修改的。 

第二段 

         For any particular set of preferences, there is a single instance of this class that all clients share.  这说明一个特定的preferences,其实是共多个客户共享的。

        通过上面两段英文说明,大致就知道SharedPreferences是什么了、是干什么的了。下面我一步一步来解开SharedPreferences秘密。

        刚才说了SharedPreferences.java 只是定义了SharedPreferences的一个接口,其实真正实现它的是在这里:SharedPreferencesImpl.java。我们来看他到底在做什么:

 首先看他的构造函数:

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        startLoadFromDisk();
    }
这里我们关注这个函数的 第一个参数:file. 传递了一个文件。这个file是如何创建的,我们先不说,后面再议。 我们现在只关注SharedPreferencesImpl.的实现。

再看一个函数:

    public Editor edit() {
        // TODO: remove the need to call awaitLoadedLocked() when
        // requesting an editor.  will require some work on the
        // Editor, but then we should be able to do:
        //
        //      context.getSharedPreferences(..).edit().putString(..).apply()
        //
        // ... all without blocking.
        synchronized (this) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }

当获取一个SharedPreferences实例时候,就需要调用这个函数才能获得这个SharedPreferences的操作。返回值是一个 Editor。那么对一个SharedPreferences的写数据、读数据等操作都是对这个SharedPreferences的Editor了,所以我们重点来看看Editor到底做了些什么。

先看看这个Editor的实现类EditorImpl其实是SharedPreferencesImpl的一个内部Final的类(下面给出一部分代码):

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

        public Editor putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }
        public Editor putStringSet(String key, Set<String> values) {
            synchronized (this) {
                mModified.put(key,
                        (values == null) ? null : new HashSet<String>(values));
                return this;
            }
        }
        public Editor putInt(String key, int value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }
... ...
这里我们关注 他里面定义了一个HashMap的私有变量mModified。再看看putInt()z这些函数就知道, 对SharedPreferences写入数据,其实就是对mModified这个HashMap加入数据。你会说,不会吧,数据写入HashMap能够永久保存吗?当然不会。 不慌,我们来看看这个内部类的一个函数:

        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);
        }
重点看到里面有一行代码:SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);那这个函数做了些什么呢?

    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);
    }
首先,这个函数ANDROID给了一段说明我们来看看: Enqueue an already-committed-to-memory result to be written to disk.  好像是说把数据写道DISK里面????

我们注意这个函数定义的一个Runnable的变量:writeToDiskRunnable它里面执行的重要代码是writeToFile(mcr);估计你看到这里就觉得有明白,他要干什么呢!的确就如你想的一样,其实是把刚刚存储在一个HashMap的数据写道一个文件里。 不信的话,我们继续看看writeToFile(mcr);这个函数干了些什么。

writeToFile(mcr);这个函数里面有这样一段重要的代码,你看了就彻底明白了,

            FileOutputStream str = createFileOutputStream(mFile);
            if (str == null) {
                mcr.setDiskWriteResult(false);
                return;
            }
            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
            FileUtils.sync(str);
            str.close();
            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
看看第一行代码 :FileOutputStream str = createFileOutputStream(mFile);  还记得一开始让你注意的那个文件吗?现在是不是终于用的了mFile。OK, 其实就是把数据写在了这个文件里面了。

XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);        mcr.mapToWriteToDisk这个数据是那里来的呢,其实 mapToWriteToDisk这个数据就是我们刚才说的那个存储数据的那个HashMap。看看如下的代码就知道了:

        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;

在这个函数commitToMemory里紧接着就有如下代码
   for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                        if (v == this) {  // magic value for a removal mutation
                            if (!mMap.containsKey(k)) {
                                continue;
                            }
                            mMap.remove(k);
                        } else {
                            boolean isSame = false;
                            if (mMap.containsKey(k)) {
                                Object existingValue = mMap.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            mMap.put(k, v);
                        }

看到了吗,这样一来原本暂时存储数据的mModified就把数据传出去了给了mapToWriteToDisk 

这下明白了吧。实际上SharedPreferences的原理就是先把数据存储在hashmap,然后再写入一个文件里面。那么写入的这个文件是什么文件呢?文件名是什么呢?和一个APP有什么关系呢;还有上面说的“这说明一个特定的preferences,其实是共多个客户共享的”这里的多个客户共享是指什么呢?欲知详情,请看下面分解:

     一般情况下,我们是通过如下的代码来获取一个SharedPreferences:

                                         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

    (1)这里的this当然就是指当前Activity或是Service的Context,我们知道其实Activity和Service其实本质都是一样的,他们都是继承Context而来的。

   (2)这里我们重点看这个函数getDefaultSharedPreferences(Context context)在PreferenceManager这个CLASS里面是这样定义的:

    public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
                getDefaultSharedPreferencesMode());
    }
      看了这个函数,你一定豁然开朗了,getDefaultSharedPreferences是一个static 函数,实际上就是通过这个context来获取的SharedPreferences。首先我们来看看getDefaultSharedPreferencesName(context)这个函数传递的是什么参数:

    private static String getDefaultSharedPreferencesName(Context context) {
        return context.getPackageName() + "_preferences";
    }
     原来是传递了一个字符串,而且这个字符串是由当前APP的PackageName+_preferences组成。现在是否看出点什么呢? 我们知道一个APP可以有大量的activity和Service,他们的PackageName是一样的,难道一个APP所有的activity和Service都传递相同的一个字符串去获取一个SharedPreferences吗? 

    难道说一个一个APP所有的activity和Service都共享一个SharedPreferences? 是这样吗?我们继续往下看:


    所以我们最终来关注context的getSharedPreferences函数的实现了。

     我们知道Context.java里面只是Context定义了接口,真正实现这个接口的其实是在这里ContextImpl.java,所以我们很快找到了这个函数的定义:

    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (sSharedPrefs) {
            sp = sSharedPrefs.get(name);
            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                sSharedPrefs.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();
        }
        return sp;
    }
getSharedPreferences这个函数很简单:

       (1)首先是通过传递进来的name来获取一个SharedPreferencesImpl实例,SharedPreferencesImpl已经在上面说过了,他其实就是一个SharedPreferences。这里是通过sSharedPrefs.get(name);这段代码来获取的,看一下sSharedPrefs这个量的定义你就会吓一跳:

    private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
            new HashMap<String, SharedPreferencesImpl>();
      sSharedPrefs是一个私有的、最重要的是:还是一个静态的、并且还是一个final型的HashMap<String, SharedPreferencesImpl>的变量。这里最重要的要理解static:这表示sSharedPrefs是所有Context实例共享的,也就是说,原则上所有APP如此当前的存在可用的他们的SharedPreferences都是暂时存在这里的。

    那么如果当前需要用的SharedPreferences在sSharedPrefs不存在,那怎么办?  当然是创建一个新的SharedPreferences,然后再加入到sSharedPrefs看看上面的这个函数: getSharedPreferences的实现就知道:

            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                sSharedPrefs.put(name, sp);
                return sp;
            }
     (1)上面我贴出来了创建一个新的SharedPreferences的代码。可以看出:先是通过name创建一个File 。我们来来看看到底是创建了什么文件:

    public File getSharedPrefsFile(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }

   看到了吧,这下就明白了,其实就是创建了一个.xml。而文件名其实就是这个name。回顾一下name其实就是这个APP的PackageName+_preferences组成。

   (2)XML文件创建好了,接着sp = new SharedPreferencesImpl(prefsFile, mode);就创建了一个SharedPreferencesImpl实例。 我们回顾一下:在开始介绍SharedPreferencesImpl的实现的时候就说到了他的构造函数,这个构成函数的一个参数就是file,一个文件。 这下就知道了 ,这个文件其实就是一个XML文件。

    所以:对于一个APP的SharedPreferences   “all clients share”的意思就是:一个APP的所有clients share这个SharedPreferences  。而这里的 clients显然是指这个APP的所有activity和Service。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值