美文网首页技术干货儿源码解析相关Android知识
Android本地存储之SharedPreferences源码解

Android本地存储之SharedPreferences源码解

作者: 饱醉豚我去年买了个表 | 来源:发表于2016-12-13 14:53 被阅读519次

    先来看下SharedPreferences的使用方法

    SharedPreferences存数据:

    //获得SharedPreferences的实例 sp_name是文件名
    SharedPreferences sp = getSharedPreferences("sp_name", Context.MODE_PRIVATE);
    //获得Editor 实例
    SharedPreferences.Editor editor = sp.edit();
    //以key-value形式保存数据
    editor.putString("data_key", "data");
    //apply()是异步写入数据
    editor.apply();   
    //commit()是同步写入数据 
    //editor.commit(); 
    

    SharedPreferences取数据:

    //获得SharedPreferences的实例
    SharedPreferences sp = getSharedPreferences("sp_key", Context.MODE_PRIVATE);
    //通过key值获取到相应的data,如果没取到,则返回后面的默认值
    String data = sp.getString("data_key", "defaultValue");
    

    数据以xml形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里,如图:

    xml.png sp_name.png

    以上是SharedPreferences的简单用法,下面从源码角度来看下整个过程:

    先把结论贴出来:

    1. SharedPreferences读取xml文件时,会以DOM方式解析(把整个xml文件直接加载到内存中解析),在调用getXXX()方法时取到的是内存中的数据,方法执行时会有个锁来阻塞,目的是等待文件加载完毕,没加载完成之前会wait()。
    2. SharedPreferences写文件时,如果调用的commit(),会将数据同步写入内存中,内存数据更新,再同步写入磁盘中;如果调用的apply(),会将数据同步写入内存中,内存数据更新,然后异步写人磁盘,也就是说可能写磁盘操作还没有完成就直接返回了。在主线程中建议使用apply(),因为同步写磁盘,当文件较大时,commit()会等到写磁盘完成再返回,可能会有ANR问题。
    3. SP第一次初始化到读取到数据存在一定延迟,因为需要到文件中读取数据,因此可能会对UI线程流畅度造成一定影响。

    我们通过context.getSharedPreferences方法获取SharedPreferences实例

    //name是存储的文件名,mode是创建文件时的模式
    public abstract SharedPreferences getSharedPreferences(String name, int mode);
    

    Context里面的getSharedPreferences方法是抽象方法,接着就找到了Context的实现类是ContextImpl:

    //Map from preference name to generated path. 
    //mSharedPrefsPaths为保存文件地址的Map
    private ArrayMap<String, File> mSharedPrefsPaths;
    
    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {    
    // 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) { 
       //如果参数name为null,则直接创建一个null.xml的文件
           if (name == null) { 
               name = "null"; 
           }    }   
        File file;
        synchronized (ContextImpl.class) {
            //第一次进的时候初始化
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
             //通过getPreferencesDir()来获取shared_prefs目录,然后根据文件名加上xml后缀
                file = getSharedPreferencesPath(name); 
               mSharedPrefsPaths.put(name, file);
            }    }
        return getSharedPreferences(file, mode);}
    
    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
    //Android N以后不支持Context.MODE_WORLD_WRITEABLE和Context.MODE_WORLD_READABLE模式
        checkMode(mode);
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
        //通过getSharedPreferencesCacheLocked()根据包名来获得缓存preferences的Map
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        //如果静态内存缓存中有,直接取出来
            sp = cache.get(file);
            if (sp == null) {
                 //缓存中没有,new一个sp出来(SharedPreferences是一个接口,SharedPreferencesImpl是其实现类)
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, 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. 
            //MODE_MULTI_PROCESS多进程模式或者SDK<11时,会重新从磁盘加载文件,不过多进程模式
            //已经被deprecated了,官方建议使用ContentProvider来处理多进程访问.
           sp.startReloadIfChangedUnexpectedly();
        } 
       return sp;}
    
    //通过getPreferencesDir()来获取shared_prefs目录,然后根据文件名加上xml后缀
    @Override
    public File getSharedPreferencesPath(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }
    
    //Android N以后不支持Context.MODE_WORLD_WRITEABLE和Context.MODE_WORLD_READABLE模式
    private void checkMode(int mode) {
        if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
            if ((mode & MODE_WORLD_READABLE) != 0) {
                throw new SecurityException("MODE_WORLD_READABLE no longer supported");
            }
            if ((mode & MODE_WORLD_WRITEABLE) != 0) {
                throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
            }    }}
    
    /** * Map from package name, to preference name, to cached preferences. */
    //sSharedPrefsCache根据包名来缓存preferences的Map
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
    
    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }
        final String packageName = getPackageName();
       //首先在静态缓存sSharedPrefsCache中查找preferences的Map,如果有,直接取出来返回
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        //如果静态缓存中没有,直接new一个Map并且加到静态缓存sSharedPrefsCache中
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }
        return packagePrefs;}
    

    下面再来分析一下SharedPreferencesImpl实例化过程,也是从磁盘读取文件到内存中的过程:

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        //产生一个.bak结尾的临时File
        mBackupFile = makeBackupFile(file);
        //加载模式
        mMode = mode;
        //标志位,表示从磁盘加载到内存中是否完成
        mLoaded = false;
        //保存在内存中sp对象的Map
        mMap = null;
        //从磁盘中加载文件到内存中
        startLoadFromDisk();
    }
    static File makeBackupFile(File prefsFile) {
        return new File(prefsFile.getPath() + ".bak");}
    
    private void startLoadFromDisk() {
        synchronized (this) {
            mLoaded = false;
        }
       //新起一个Thread开始加载
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                loadFromDisk();
            }
        }.start();}
    
    private void loadFromDisk() {
        synchronized (SharedPreferencesImpl.this) {
            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 {
                    //SharedPreferences文件以流形式读出来
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16*1024);
                    //读取xml中的内容,构造一个Map赋值给下面的mMap
                    map = XmlUtils.readMapXml(str);
                } catch (XmlPullParserException | IOException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } finally {
                    IoUtils.closeQuietly(str);
                } 
           }    } catch (ErrnoException e) {
            /* ignore */    }
        synchronized (SharedPreferencesImpl.this) {
            //标记位,数据已经加载完成
            mLoaded = true;
            if (map != null) {
                //赋值给内存中的mMap 
                mMap = map;
                //设置时间戳和文件的大小
                mStatTimestamp = stat.st_mtime;
                mStatSize = stat.st_size;
            } else {
                mMap = new HashMap<>();
            }
            // 通知所有线程被SharedPreferencesImpl.this对象锁住的数据已经加载完成了,数据可以使用了。
            notifyAll();    }}
    

    最后来看下读文件和写文件,首先是读文件(文件从磁盘加载到内存中),这里看的SharedPreferencesImpl类中getString(String key, String defValue)地源码,其他getXXX()也是一样的。

    @Nullable
    public String getString(String key, @Nullable String defValue) {
      synchronized (this) {
          //同步等待文件从磁盘加载到内存完成为止,否自wait()
          awaitLoadedLocked();
          //从内存中的mMap直接取值
          String v = (String)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) {
          }
      }}
    

    写文件(先把要修改的数据写到内存中,再写入磁盘中):

    public final class EditorImpl implements Editor {
        // 保存putXXX()方法时提供的所有要提交修改的数据
        private final Map<String, Object> mModified = Maps.newHashMap();
        private boolean mClear = false;
        public Editor putString(String key, @Nullable String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }    }
    
    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;}
    }
    
    
    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);
    }
    
    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);
                    }
                });
        }}
    
    // Returns true if any changes were madeprivate MemoryCommitResult 
    //commit()和apply()两个方法都调用了commitToMemory。该方法主要根据mModified和是否被
    //clear修改内存中mMap的值,然后返回写磁盘需要的一些相关值
    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
               // 当有多个写操作时,clone一份
                mMap = new HashMap<String, Object>(mMap);
            }
            mcr.mapToWriteToDisk = mMap;
            //未完成的写操作数+1
            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);
                    }
                    mcr.changesMade = true;
                    if (hasListeners) { 
                       mcr.keysModified.add(k);
                    }
                }
                mModified.clear();
            }    }
        return mcr;}
    
    // Return value from EditorImpl#commitToMemory()
    //写磁盘需要的相关值
    private static class MemoryCommitResult {
        public boolean changesMade;
      // any keys different? public List<String> keysModified;
      // may be 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();
        }}
    
    

    commit()和apply()这两个方法都是首先修改内存中缓存的mMap的值,然后将数据写到磁盘中。它们的主要区别是commit会等待写入磁盘后再返回,而apply则在调用写磁盘操作后就直接返回了,但是这时候可能磁盘中数据还没有被修改。

    作者:@小马快跑
    Q Q :504091986@qq.com
    博 客:http://www.jianshu.com/users/185028550381/latest_articles
    github:https://github.com/crazyqiang

    相关文章

      网友评论

        本文标题:Android本地存储之SharedPreferences源码解

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