SharedPreference源码分析

作者: gatsby_dhn | 来源:发表于2016-10-08 18:02 被阅读628次

    SharePreference属于轻量级的键值存储方式,以XML文件方式保存数据。花2两个小时看下源码还是很有收获的。

    支持原创,转载请注明出处。

    老规矩,先上图:


    SharedPreference.png

    获取SharedPreferences

    我们一般有两种方式获取SharedPreference:

    Activity的public SharedPreferences getPreferences(int mode)方法
    或者
    ContextImpl的public SharedPreferences getSharedPreferences(String name, int mode)方法
    

    Activity的public SharedPreferences getPreferences(int mode)方法实际上调用了ContextImpl的public SharedPreferences getSharedPreferences(String name, int mode)方法:

        public SharedPreferences getPreferences(int mode) {
            return getSharedPreferences(getLocalClassName(), mode);
        }
    

    public abstract SharedPreferences getSharedPreferences(String name, int mode)是Context抽象类的抽象方法,它的实现类是哪个呢?这个涉及到Activity的启动,感兴趣的可以看下ActivityThread的performLaunchActivity方法。这个Context的实现类是ContextImpl类。
    看下它的getSharedPreferences方法。

        @Override
        public SharedPreferences getSharedPreferences(String name, int mode) {
            SharedPreferencesImpl sp;
            synchronized (ContextImpl.class) {
                if (sSharedPrefs == null) {
                    sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();   //创建一个map,键是包名,值还是一个map(j键是传入的name,值是SharedPreferencesImpl对象)
                }
    
                final String packageName = getPackageName();   
                ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);  //获取当前包名对应的map
                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.
                if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                        Build.VERSION_CODES.KITKAT) {
                    if (name == null) {
                        name = "null";
                    }
                }
    
                sp = packagePrefs.get(name); //根据传入的name获取SharedPreferencesImpl对象
                if (sp == null) {    //若为空,则新建一个SharedPreferencesImpl对象
                    File prefsFile = getSharedPrefsFile(name);  //使用name创建一个文件
                    sp = new SharedPreferencesImpl(prefsFile, mode); //新建一个SharedPreferencesImpl对象
                    packagePrefs.put(name, sp);  //放入当前包名对应的map中
                    return sp;               //方法SharedPreferencesImpl对象
                }
            }
            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;
        }
    

    这里建议看下类图,弄明白sSharedPrefs这个map的含义。这个方法从sSharedPrefs中根据包名获取一个和这个包名对应的map,然后从这个map中获取和传入的name关联的SharedPreferencesImpl对象,若不存在则创建这个对象。我们看下这个类。

       final class SharedPreferencesImpl implements SharedPreferences
    
    ------------------------------成员变量-----------------------------------
        private final File mFile;    //保存数据的文件
        private final File mBackupFile;
        private final int mMode;
    
        private Map<String, Object> mMap;     //保存和该name关联的键值对
    
    ------------------------------构造方法--------------------------------------
       SharedPreferencesImpl(File file, int mode) {
            mFile = file;
            mBackupFile = makeBackupFile(file);
            mMode = mode;
            mLoaded = false;
            mMap = null;
            startLoadFromDisk();    //从文件获取map数据
        }
    
        private void startLoadFromDisk() {
            synchronized (this) {
                mLoaded = false;
            }
            new Thread("SharedPreferencesImpl-load") {
                public void run() {
                    synchronized (SharedPreferencesImpl.this) {
                        loadFromDiskLocked();//子线程中加载文件中的数据到成员变量map中
                    }
                }
            }.start();
        }
    
        private void loadFromDiskLocked() {
            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);   //解析xml数据,保存到map中
                    } 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();
        }
    

    创建SharedPreferencesImpl对象的同时,会从对应的xml文件中解析数据,保存到成员变量mMap中。
    到这里,我就就创建好了SharedPreferences,所以我们调用getSharedPreferences后返回的SharedPreferences就是SharedPreferencesImpl对象。

    读写数据

    首先我们要获取一个Editor对象

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

    这个Editor的实现是EditorImpl,我们看下这个类:

        public final class EditorImpl implements Editor
    
    -----------------------------成员变量-------------------------------
    private final Map<String, Object> mModified = Maps.newHashMap();
    
    ----------------------------核心方法---------------------------
    public Editor putBoolean(String key, boolean value)
    等
    

    比如要写入一个boolean值我们调用Editor的如下方法:

    public Editor putBoolean(String key, boolean value) {
                synchronized (this) {
                    mModified.put(key, value);
                    return this;
                }
            }
    

    很简单,将本次增加的键值对保存到mModified中,暂时缓存在内存中。

    同步数据到文件

    想要把数据同步到文件要调用commit()方法:

            public boolean commit() {
                MemoryCommitResult mcr = commitToMemory();  //mModified和mMap中的数据一并保存到MemoryCommitResult对象中
                SharedPreferencesImpl.this.enqueueDiskWrite( //写入到文件
                    mcr, null);
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException e) {
                    return false;
                }
                notifyListeners(mcr);
                return mcr.writeToDiskResult;
            }
    
            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);
                            }
                        });
                }
            }
        }
    

    这里注意commitToMemory方法,该方法将本次修改的数据mModified和以前的数据mMap一并保存到MemoryCommitResult对象中,然后调用enqueueDiskWrite方法将内存中的数据写入到文件。
    看下commitToMemory方法:

            private MemoryCommitResult commitToMemory() {
                MemoryCommitResult mcr = new MemoryCommitResult(); //新建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; //将mMap保存到mcr的mapToWriteToDisk这个map中
                    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);    //将本次修改增加的数据也加入到mMap中
                            }
    
                            mcr.changesMade = true;
                            if (hasListeners) {
                                mcr.keysModified.add(k);
                            }
                        }
    
                        mModified.clear();
                    }
                }
                return mcr;
            }
    

    接着看下enqueueDiskWrite是如何将数据写入文件的:

        private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                      final Runnable postWriteRunnable) {
            final Runnable writeToDiskRunnable = new Runnable() {
                    public void run() {
                        synchronized (mWritingToDiskLock) {
                            writeToFile(mcr);    //将mcr写到文件,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();  //commit走这里,在当前线程执行
                    return;
                }
            }
    
            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
        }
    

    commit方法会直接调用 writeToDiskRunnable.run(),也就是说在当前线程进行写文件操作,我们看下writeToFile方法:

        private void writeToFile(MemoryCommitResult mcr) {
            // Rename the current file so it may be used as a backup during the next read
            if (mFile.exists()) {
                if (!mcr.changesMade) {
                    // If the file already exists, but no changes were
                    // made to the underlying map, it's wasteful to
                    // re-write the file.  Return as if we wrote it
                    // out.
                    mcr.setDiskWriteResult(true);
                    return;
                }
                if (!mBackupFile.exists()) {
                    if (!mFile.renameTo(mBackupFile)) {
                        Log.e(TAG, "Couldn't rename file " + mFile
                              + " to backup file " + mBackupFile);
                        mcr.setDiskWriteResult(false);
                        return;
                    }
                } else {
                    mFile.delete();
                }
            }
    
            // Attempt to write the file, delete the backup and return true as atomically as
            // possible.  If any exception occurs, delete the new file; next time we will restore
            // from the backup.
            try {
                FileOutputStream str = createFileOutputStream(mFile);  //获取文件流
                if (str == null) {
                    mcr.setDiskWriteResult(false);
                    return;
                }
                XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);  //使用XmlUtils.writeMapXml方法将内存中的键值对写入文件
                FileUtils.sync(str);
                str.close();
                ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
                try {
                    final StructStat stat = Os.stat(mFile.getPath());
                    synchronized (this) {
                        mStatTimestamp = stat.st_mtime;
                        mStatSize = stat.st_size;
                    }
                } catch (ErrnoException e) {
                    // Do nothing
                }
                // Writing was successful, delete the backup file if there is one.
                mBackupFile.delete();
                mcr.setDiskWriteResult(true);
                return;
            } catch (XmlPullParserException e) {
                Log.w(TAG, "writeToFile: Got exception:", e);
            } catch (IOException e) {
                Log.w(TAG, "writeToFile: Got exception:", e);
            }
            // Clean up an unsuccessfully written file
            if (mFile.exists()) {
                if (!mFile.delete()) {
                    Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
                }
            }
            mcr.setDiskWriteResult(false);
        }
    

    这里主要关注两步,首先获取文件流,然后使用XmlUtils.writeMapXml将map写入文件。到这里,内存中的数据就写入到了文件。

    总结

    SharedPreference保存数据的形式是xml文件,并且创建时不同的name对应不同的xml文件。只有执行commit()操作才会将数据同步到文件,并且commit是同步的,会阻塞当前线程。想异步写入可以考虑apply方法。

    如果觉得写得还不错可以关注我哦,后面会将更多笔记的内容整理成博客。支持原创,转载请注明出处。

    相关文章

      网友评论

        本文标题:SharedPreference源码分析

        本文链接:https://www.haomeiwen.com/subject/wwbuyttx.html