Android SharedPreferences解析

转载请标明出处:
http://blog.csdn.net/zq2114522/article/details/50282293
本文出自:【梁大盛的博客】

Android SharedPreferences解析

引:在使用SharedPreferences过程中,觉得Android这种轻量级的存储思想用起来挺方便的.本着知其然知其所以然的想法.尝试阅读SharedPreferences源码

入口函数

public SharedPreferences getPreferences(int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
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;
    }

很简单的两个入口函数.

public SharedPreferences getPreferences(int mode)

定义在Activity.class类

 public SharedPreferences getSharedPreferences(String name, int mode) 

定义在ContextImpl.class类

两者之间的关系,getPreferences通过调用getSharedPreferences来实现.

public SharedPreferences getPreferences(int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
public String getLocalClassName() {
        final String pkg = getPackageName();
        final String cls = mComponent.getClassName();
        int packageLen = pkg.length();
        if (!cls.startsWith(pkg) || cls.length() <= packageLen
                || cls.charAt(packageLen) != '.') {
            return cls;
        }
        return cls.substring(packageLen+1);
    }

getPreferences(int mode)函数通过调用getLocalClassName()传递的是类名进去.然而他们两区别就是这里了.

getSharedPreferences函数

getSharedPreferences函数返回的是一个SharedPreferencesImpl实例.使用过SharedPreferences的应该都知道,我们操作都是通过SharedPreferences实例进行操作的.

SharedPreferencesImpl继承SharedPreferences

ContextImpl.class有个sSharedPrefs静态变量.sSharedPrefs保存的是键值对.每当调用getSharedPreferences函数首先会用name查询sSharedPrefs里面有没保存对于的SharedPreferencesImpl实例.如果有那么直接返回name对于的SharedPreferencesImpl实例.如果没找到那么创建一个SharedPreferencesImpl实例并且已键值对的形式插入到sSharedPrefs.以便以后在通过name获取SharedPreferencesImpl实例.

  • 也就说调用getSharedPreferences函数传递进来的name是一个索引.通过索引返回SharedPreferencesImpl实例.
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
            new HashMap<String, SharedPreferencesImpl>();
public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (sSharedPrefs) {
            //通过name查找SharedPreferencesImpl实例
            sp = sSharedPrefs.get(name);
            if (sp == null) {
                //为空,第一次读取.没有根据name产生SharedPreferencesImpl实例
                //getSharedPrefsFile()构造Xml文件File
                File prefsFile = getSharedPrefsFile(name);
                //创建SharedPreferencesImpl实例,默认在”/data/data/包名/shared_prefs/”目录里面创建XML文件
                sp = new SharedPreferencesImpl(prefsFile, mode);
                //保存SharedPreferencesImpl实例到sSharedPrefs,name作为索引
                sSharedPrefs.put(name, sp);
                return sp;
            }
        }

        //这部分忽略,Context.MODE_MULTI_PROCESS getSharedPreferences是用于跨线程
        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;

接下来这三个很好理解,getSharedPrefsFile就是返回Xml文件的File.所以说SharedPreferences的实现是基于Xml文件.从这里可以看出.

  • 例如:
  • 会生成xml文件
  • /data/data/com.dsliang.SharedPreferencesDemo/shared_prefs/ds.xml
 public File getSharedPrefsFile(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }
private File getPreferencesDir() {
        synchronized (mSync) {
            if (mPreferencesDir == null) {
                mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
            }
            return mPreferencesDir;
        }
    }
    private File getDataDirFile() {
        if (mPackageInfo != null) {
            return mPackageInfo.getDataDirFile();
        }
        throw new RuntimeException("Not supported in system context");
    }

SharedPreferencesImpl类

理解了SharedPreferencesImpl类基本对SharedPreferences就明白一大半了.

构造函数

SharedPreferencesImpl(File file, int mode) {
        //Xml文件的File
        mFile = file;
        //Xml文件的副本(备份)
        mBackupFile = makeBackupFile(file);
        //SharedPreferences模式
        mMode = mode;
        //是否加载文件内容到内存(加载到sSharedPrefs)
        mLoaded = false;
        //SharedPreferences键值对哈希表,最终获取数据就是在这表里面获取
        mMap = null;
        //异步读取文件并且保存数据到sSharedPrefs
        startLoadFromDisk();
    }
    private void startLoadFromDisk() {
        synchronized (this) {
            //标识文件未读取到内存
            //在getString/getBoolean一系列的读取函数中会调用awaitLoadedLocked()函数会判断mLoaded.false - awaitLoadedLocked()函数阻塞等待mLoaded为true.
            //还没读取完Xml文件哪里来读取呢?关键就在这变量.没赋值为true之前所有读取操作都是阻塞等待.
            mLoaded = false;
        }
        //子线程执行读取Xml文件操作
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                synchronized (SharedPreferencesImpl.this) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }

    private void loadFromDiskLocked() {
        if (mLoaded) {
            return;
        }
        //如果存在Xml备份文件,删除Xml文件.用备份文件替换.
        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;
        FileStatus stat = new FileStatus();
        if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) {
            try {
                BufferedInputStream str = new BufferedInputStream(
                        //通过XmlPull解析Xml文件
                        new FileInputStream(mFile), 16*1024);
                //返回的数据是Map.有兴趣可以进去看看.里面很多递归调用,看到头都晕.知道是解析Xml文件并且返回Map就可以了.
                map = XmlUtils.readMapXml(str);
                str.close();
            } catch (XmlPullParserException e) {
                Log.w(TAG, "getSharedPreferences", e);
            } catch (FileNotFoundException e) {
                Log.w(TAG, "getSharedPreferences", e);
            } catch (IOException e) {
                Log.w(TAG, "getSharedPreferences", e);
            }
        }

        //到这里文件已经读取出来并且保持到内存当中.
        mLoaded = true;
        if (map != null) {
            //mMap,SharedPreferencesImpl内部用来存放Map的实例
            mMap = map;
            //文件读取时间
            mStatTimestamp = stat.mtime;
            //文件大小
            mStatSize = stat.size;
        } else {
            //文件不存在创建一个空的Map
            mMap = new HashMap<String, Object>();
        }

        //通知等待线程
        notifyAll();
    }

SharedPreferencesImpl类解析,在构造函数会创建子线程读取Xml文件解析成Map保存到SharedPreferencesImpl类内部的mMap.在读取解析Xml文件这过程当中对该SharedPreferencesImpl实例进行操作都会阻塞.(awaitLoadedLocked()函数导致)
可以知道SharedPreferences是会将Xml文件的全部数据都读取到内存.

读取数据

在SharedPreferencesImpl.class内部,mMap哈希表用来存放SharedPreferences键值对.

 private Map<String, Object> mMap; 

getInt/getLong/getString等.
这一系列函数本质上没多少区别,就是通过key在mMap查询,查不到责返回默认值.

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

public int getInt(String key, int defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            Integer v = (Integer)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

    public long getLong(String key, long defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            Long v = (Long)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
 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();
        }
        while (!mLoaded) {
            try {
                wait();
            } catch (InterruptedException unused) {
            }
        }
    }

读取数据函数主要是要关注awaitLoadedLocked()函数.如果文件还没加载完成会导致操作阻塞.

修改数据-Editor类

调用SharedPreferencesImpl类的edit()方法返回EditorImpl类.EditorImpl类封装对数据操作的函数.putString/putBoolean等一系列方法.这里需要注意几个问题.

  • putString/putBoolean等一系列方法操作EditorImpl类内部的一个Map并不是操作SharedPreferencesImpl类的Map.所以要生效是需要调用EditorImpl类的commit()方法.
  • EditorImpl类的clear(0方法是清空EditorImpl类内部的Map实例.
    以下操作不会导致SharedPreferences清楚数据.
edit().clear().commit();
  • 正确姿势如下:
edit().remove("Save")..remove("PassWord")commit();
    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();
    }

最后就是commit()方法源代码.

    //记录Commit()方法结果的类
    private static class MemoryCommitResult {
        //数据是否需要更新
        public boolean changesMade;  // any keys different?
        //Map实例
        public List<String> keysModified;  // may be null
        //回调函数,有可能为null
        public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
        public Map<?, ?> mapToWriteToDisk;
        public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
        //结果
        public volatile boolean writeToDiskResult = false;

        public void setDiskWriteResult(boolean result) {
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }
    }


// 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.
                //mDiskWritesInFlight等处理的提交个数
                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);
                }
                //SharedPreferences的Map保存到mcr.mapToWriteToDisk
                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) {
                    //是否有清空,调用clear()以后设置mClear=true.就是当调用clear()以后在put数据进去也不会生效.
                    if (mClear) {
                        if (!mMap.isEmpty()) {
                            mcr.changesMade = true;
                            mMap.clear();
                        }
                        mClear = false;
                    }

                    //处理edit的Map和SharedPreferences的Map
                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                        //调用romve以后,Value会设置成this.用来标识此key需要删除.很犀利的技巧.如果不设置没法知道哪一个需要删除.
                        if (v == this) {  // magic value for a removal mutation
                            if (!mMap.containsKey(k)) {
                                continue;
                            }
                            //从SharedPreferences的Map移除
                            mMap.remove(k);
                        } else {
                            boolean isSame = false;
                            if (mMap.containsKey(k)) {
                                Object existingValue = mMap.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }

                            //键值对插入SharedPreferences的Map
                            mMap.put(k, v);
                        }

                        //设置修改位,标志已经是脏的.需要更新
                        mcr.changesMade = true;
                        if (hasListeners) {
                            mcr.keysModified.add(k);
                        }
                    }

                    //edit的Map已经处理完.可以清空.此时SharedPreferences的Map数据已经是最新的
                    mModified.clear();
                }
            }
            return mcr;
        }

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

        /.通过commit()方法调用,此处isFromSyncCommit为true
        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);
    }

    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 {
                //如果备份Xml文件存在,把mFile删除就可以了.
                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);
            //刷新
            FileUtils.sync(str);
            str.close();

               //设置权限ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
            FileStatus stat = new FileStatus();
            if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
                synchronized (this) {
                    mStatTimestamp = stat.mtime;
                    mStatSize = stat.size;
                }
            }
            // 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);
    }
}


public boolean commit() {
            //提交修改
            MemoryCommitResult mcr = commitToMemory();
            //这里就是回写函数
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            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);
                        }
                    });
            }
        }
    }

SharedPreferences代码总结

SharedPreferences本质是通过Xml文件实现.使用的时候会把Xml文件解析成Map保存到内存中.要知道它会将Xml文件全部数据都解析到内存当中.免不了会占用内存.其次对SharedPreferences修改都会导致整个Xml文件再次回写.

    原文作者:dsliang12
    原文地址: https://blog.csdn.net/zq2114522/article/details/50282293
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞