Android SharedPreferences解析

来源:互联网 发布:美国和日本知乎 编辑:程序博客网 时间:2024/05/07 06:19

转载请标明出处:
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文件再次回写.

0 0
原创粉丝点击