Activity之SharedPreferences探究

来源:互联网 发布:软件定义存储产品 编辑:程序博客网 时间:2024/05/02 04:57

先说说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 0
原创粉丝点击