美文网首页
SharedPreference 的多进程共享数据浅析

SharedPreference 的多进程共享数据浅析

作者: 奋斗小青年Jerome | 来源:发表于2017-10-19 10:45 被阅读73次

    关于多进程共享数据,看了这篇文章,分析的比较透彻,感谢作者!! 以下是摘自文章

    在使用SharedPreference 时,有一些模式:MODE_PRIVATE 私有模式,这是最常见的模式,一般情况下都使用该模式。 MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE,文件开放读写权限,不安全,已经被废弃了,google建议使用FileProvider共享文件。MODE_MULTI_PROCESS,跨进程模式,如果项目有多个进程使用同一个Preference,需要使用该模式,但是也已经废弃了
    Android不保证该模式总是能正确的工作,建议使用ContentProvider替代。结合前面的MODE_WORLD_READABLE标志,可以发现,Google认为多个进程读同一个文件都是不安全的,不建议这么做,推荐使用ContentProivder来处理多进程间的文件共享,FileProvider也继承于ContentProvider
    总结下来就是:

    确保一个文件只有一个进程在进行读写操作

    为什么不建议使用MODE_MULTI_PROCESS

    原因并不复杂,我们可以从android源码看一下,通过方法context.getSharedPreferences 获取到的类实质上是SharedPreferencesImpl 。该类就是一个简单的二级缓存,在启动时会将文件里的数据全部都加载到内存里,

        private void startLoadFromDisk() {
            synchronized (this) {
                mLoaded = false;
            }
            new Thread("SharedPreferencesImpl-load") {
                public void run() {
                    loadFromDisk();
                }
            }.start();
        }
    

    这里也提醒一下,由于SharedPreference内容都会在内存里存一份,所以不要使用SharedPreference保存较大的内容,避免不必要的内存浪费。
    注意有一个锁mLoaded ,在对SharedPreference做其他操作时,都必须等待该锁释放

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

    写操作有两个commit apply 。 commit 是同步的,写入内存的同事会等待写入文件完成,apply是异步的,先写入内存,在异步线程里再写入文件。apply肯定要快一些,优先推荐使用apply

    SharedPreferenceImpl的创建过程

    @Override
        public SharedPreferences getSharedPreferences(File file, int mode) {
            checkMode(mode);
            SharedPreferencesImpl sp;
            synchronized (ContextImpl.class) {
                final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
                sp = cache.get(file);
                if (sp == null) {
                    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.
                sp.startReloadIfChangedUnexpectedly();
            }
            return sp;
        }
    

    这段代码里,我们可以看出,1. SharedPreferencesImpl是保存在全局个map cache里的,只会创建一次。2,MODE_MULTI_PROCESS模式下,每次获取都会尝试去读取文件reload。当然会有一些逻辑尽量减少读取次数,比如当前是否有正在进行的读取操作,文件的修改时间和大小与上次有没有变化等。原来MODE_MULTI_PROCESS是这样保证多进程数据正确的!

    void startReloadIfChangedUnexpectedly() {
            synchronized (this) {
                // TODO: wait for any pending writes to disk?
                if (!hasFileChangedUnexpectedly()) {
                    return;
                }
                startLoadFromDisk();
            }
        }
        // Has the file changed out from under us?  i.e. writes that
        // we didn't instigate.
        private boolean hasFileChangedUnexpectedly() {
            synchronized (this) {
                if (mDiskWritesInFlight > 0) {
                    // If we know we caused it, it's not unexpected.
                    if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
                    return false;
                }
            }
            final StructStat stat;
            try {
                /
                  Metadata operations don't usually count as a block guard
                  violation, but we explicitly want this one.
                 /
                BlockGuard.getThreadPolicy().onReadFromDisk();
                stat = Os.stat(mFile.getPath());
            } catch (ErrnoException e) {
                return true;
            }
            synchronized (this) {
                return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
            }
        }
    

    这里起码有3个坑!

    1. 使用MODE_MULTI_PROCESS时,不要将SharedPreference设置成成员变量,尽量在哪里修改就在哪里直接获取SharedPreference,修改后别忘记commit。如果你图方便使用变量存了下来,那么无法触发reload,有可能两个进程数据不同步。
    2. 前面提到过,load数据是耗时的,并且其他操作会等待该锁。这意味着很多时候获取SharedPreference数据都不得不从文件再读一遍,大大降低了内存缓存的作用。文件读写耗时也影响了性能。
    3. 修改数据时得用commit,保证修改时写入了文件,这样其他进程才能通过文件大小或修改时间感知到。
      综上,无论怎么说,MODE_MULTI_PROCESS都很糟糕,避免使用就对了。

    相关文章

      网友评论

          本文标题:SharedPreference 的多进程共享数据浅析

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