zoukankan      html  css  js  c++  java
  • SharedPreferences解析

    一、概述

      SharedPreferences(简称SP)是Android中很常用的数据存储方式,SP采用key-value(键值对)形式,主要用于轻量级的数据存储,尤其适合保存应用的配置参数,但不建议使用SP来存储大规模的数据,可能会降低性能。
      SP采用xml文件格式来保存数据,改文件所在目录位于/data/data/shared_prefs/。

    二、使用

    1.得到SharedPreferences对象

    private SharedPreferences mSharedPreferences;
    private final static String PREFRENCE_FILE_KEY = "com.zhangmiao.shared_preferences";
    mSharedPreferences = getSharedPreferences(PREFRENCE_FILE_KEY, MODE_PRIVATE);

    2.添加数据

            final SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putInt("id",1);
            editor.putString("name","小熊");
            editor.putInt("age",24);
            editor.commit();

    3.获取数据

            TextView textView = (TextView)findViewById(R.id.text);
            String message = "id = " + mSharedPreferences.getInt("id",-1)
                    + ",name = " + mSharedPreferences.getString("name","无")
                    + ",age = " + mSharedPreferences.getInt("age",-1)+"。";
            textView.setText(message);

    4.查看生成的sharedpreferences.xml文件

    我使用的是adb命令查看的文件,命令如下:(系统是window10)
    adb shell
    run-as com.zhangmiao.myapplication(应用包名)
    ls(查看xml文件的名称)
    cat com.zhangmiao.shared_preferences.xml(查看xml文件)

    三、解析SharedPreferences

    1.获取方式

    1.1.getSharedPreferences(String name, int mode)

        @Override
        public SharedPreferences getSharedPreferences(String name, int mode) {
            return mBase.getSharedPreferences(name, mode);
        }

    这里的mBase就是Context类,Context的具体实现类是ContextImpl类,所以直接查看ContextImpl类中的getSharedPreferences方法。

    @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) {
                if (name == null) {
                    name = "null";
                }
            }
    
            File file;
            synchronized (ContextImpl.class) {
                if (mSharedPrefsPaths == null) {
                    mSharedPrefsPaths = new ArrayMap<>();
                }
                file = mSharedPrefsPaths.get(name);
                if (file == null) {
                    file = getSharedPreferencesPath(name);【1.3】
                    mSharedPrefsPaths.put(name, file);
                }
            }
            return getSharedPreferences(file, mode);【1.2】
        }

      第一步:判断版本号是否小于17,如果是,判断name是否为null,如果是,则设置name为“null”。
      第二步:同步ContextImpl.class,如果mSharedPrefsPaths为null,则初始化mSharedPrefsPath,判断name对应的file是否为null,则调用getSharedPreferencesPath(name)初始化file,并加入mSharedPrefsPaths中。
      第三步:返回getSharedPreferences(file, mode)方法。

    1.2.public SharedPreferences getSharedPreferences(File file, int mode)

    @Override
        public SharedPreferences getSharedPreferences(File file, int mode) {
            checkMode(mode);【1.4】
            SharedPreferencesImpl sp;
            synchronized (ContextImpl.class) {
                final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();【1.5】
                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 < 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;
        }

      第一步:检查mode的类型,如果没有MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE,则抛出异常。
      第二步:使用getSharedPreferencesCacheLocked()方法,得到cache数组映射,得到file对应的sp,如果为空,初始化并家务cache中。
      第三步:调用sp的startReloadIfChangedUnexpectedly()方法。

    1.3.public File getSharedPreferencesPath(String name)

        @Override
        public File getSharedPreferencesPath(String name) {
            return makeFilename(getPreferencesDir(), name + ".xml");
        }

    继续查看makeFileName()方法

        private File makeFilename(File base, String name) {
            if (name.indexOf(File.separatorChar) < 0) {
                return new File(base, name);
            }
            throw new IllegalArgumentException(
                    "File " + name + " contains a path separator");
        }

    如果name包含文件分隔符则生成xml文件,否则抛出异常。

    1.4.private void checkMode(int mode)

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

      如果sdk的版本大于等于24,则mode包含MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE则抛出异常。意思是从版本24开始,创建的SP文件模式,不允许MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模块。
      当设置MODE_MULTI_PROCESS模式,则每次getSharedPreferences过程,会检查SP文件上次修改时间和文件大小,一旦所有修改则会重新从磁盘加载文件。

    1.5.private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked()

        private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
            if (sSharedPrefsCache == null) {
                sSharedPrefsCache = new ArrayMap<>();
            }
    
            final String packageName = getPackageName();
            ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
            if (packagePrefs == null) {
                packagePrefs = new ArrayMap<>();
                sSharedPrefsCache.put(packageName, packagePrefs);
            }
    
            return packagePrefs;
        }

      第一步:如果sSharedPrefsCache为null,则初始化它。
      第二步:调用getPackageName()方法得到packageName。
      第三步:得到sSharedPrefsCache中packageName对应的ArrayMap对象packagePrefs。
      第四步:如果packagePrefs为null,则初始化它,并且添加到sSharedPrefsCache中。
      总结获取SharedPreference只要实现在ContextImpl中,mSharedPrefsPaths包含name对应的File,sSharedPrefsCache中包含packageName对应的packagePrefs,packagePrefs中包含File对应的SharedPreferencesImpl。

      而获取过程主要时生成File文件,初始化上面的三个ArrayMap,将本应用相关的数据添加进去,以及检测版本与mode,并抛出异常。

    2.添加数据

      添加数据使用到SharedPreferences.Editor类,SharedPreferences的实现是在ShredPreferencesImpl中,SharedPreferences.Editor的具体实现在ShredPreferencesImpl.EditorImpl中。

    2.1.public Editor edit()

        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();【2.2】
            }
    
            return new EditorImpl();
        }

      同步this,调用awaitLoadedLocked()方法,返回EditorImpl对象。

    2.2.private void awaitLoadedLocked()

        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) {
                }
            }
        }

      第一步:如果mLoaded为false,则调用BlockGuard.getThreadPolicy().onReadFromDisk()方法,忽略无用的线程。
      第二步:如果mLoaded为false,没有加载完成,则等待。

    2.3.put***(String key, *** value)系列方法

      put***()方法基本相同,这里以putInt(String key, int value)为例介绍:

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

      同步this,将数据添加到mModified中,this是Editor对象。

    2.4.public Editor remove(String key)

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

      同步this,添加到mModified的数据为key与this。

    2.5.public Editor clear()

            public Editor clear() {
                synchronized (this) {
                    mClear = true;
                    return this;
                }
            }

      设置mClear为true。

    2.6.public boolean commit()

            public boolean commit() {
                MemoryCommitResult mcr = commitToMemory();【2.7】
                SharedPreferencesImpl.this.enqueueDiskWrite(【2.9】
                    mcr, null /* sync write on this thread okay */);
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException e) {
                    return false;
                }
                notifyListeners(mcr);【2.8】
                return mcr.writeToDiskResult;
            }

      第一步:调用commitToMemory()方法,得到mcr对象。
      第二步:调用SharedPreferencesImpl的enqueueDiskWrite()方法。
      第三步:调用mcr,writtenToDiskLatch.await()方法。
      第四步:调用notifyListeners(mcr)方法。

    2.7.private MemoryCommitResult commitToMemory()

            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.
                    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;
                    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;
            }

      第一步:同步SharedPreferencesImpl.this。
      第二步:如果mDiskWritesInFlinght大于0,则初始化mMap。
      第三步:设置mcr.mapToWriteToDisk为mMap,mDiskWritesInFlight加1。
      第四步:如果存在listener,初始化mcr的keysModified和listeners。
      第五步:同步this,如果mClear为true,调用mMap.clear();遍历mModified,如果value为this,则调用mMap.remove(k)方法,移除关键字;如果不是this,先判断mMap是否包含k与value,不包含,则调用mMap.put(k,v),在mcr.keysModified中添加k。
      第六步:调用mModified.clear()方法。

    2.8.private void notifyListeners(final MemoryCommitResult 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);
                            }
                        });
                }
            }

      第一步:如果mcr.listeners为null或者mcr.keysModified为null或者mcr.keysModified.size()等于0,则直接返回。
      第二步:如果当前looper是mainLooper,遍历mcr.keysModified,得到mcr.keysModified.get(i)为key,遍历scr.Listeners,调用listener.onSharedPreferenceChanged()方法。
      第三步:如果不是mainLooper,则在主线程中调用notifyListeners(mcr)方法。

    2.9.private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable)

        private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                      final Runnable postWriteRunnable) {
            final Runnable writeToDiskRunnable = new Runnable() {
                    public void run() {
                        synchronized (mWritingToDiskLock) {
                            writeToFile(mcr);【2.10】
                        }
                        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();
                    return;
                }
            }
    
            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
        }

      第一步:创建writeToDiskRunnable线程,同步mWritingToDiskLock,调用writeFile(mcr)执行文件写入操作,同步SharedPreferencesImpl.this,将mDiskWritesInFlight减一,如果postWriteRunnable不为null,则运行postWriteRunnable。

      第二步:如果isFromSyncCommit为true(commit方法会进入),同步ShreadPreferencesImpl.this,设置wasEmpty的值(因为commitToMemory过程会加1,则wasEmpty为true);如果wasEmpty为true,运行wirteToDiskRunnable线程,然后返回。
      第三步:调用QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable)方法。

    2.10.private void writeToFile(MemoryCommitResult mcr)

        // Note: must hold mWritingToDiskLock
        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);
                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);
        }

      第一步:如果mFile存在,如果mcr.changesMade为false(没有key发生改变,则直接饭后),设置mcr的DiskWriteResult为true,返回。如果mBackupFile不存在,则将mFile重命名为mBackupFile,设置mcr的DiskWriteResult为false,如果mBackupFile存在,则直接删除mFile。
      第二步:将mMap全部信息写去文件中,调用Context.setFilePermissionsFromMode()方法。
      第三步:得到Os.stat(mFile.getPath())的stat,设置mStatTimestamp与mStatSize。
      第四步:写入成功后,删除mBackupFile备份文件。
      第五步:设置mcr的DiskWriteResult为true,返回写入成功,唤醒等待线程。
      第六步:如果文件mFile存在,则删除mFile,如果写入文件的操作失败,则删除未成功写入的文件。
      第七步:设置mcr的DiskWriteResult为true,返回写入失败,唤醒等待线程。

    2.11.public void apply()

            public void apply() {
                final MemoryCommitResult mcr = commitToMemory();【2.7】
                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);【2.8】
            }

      第一步:调用commitToMemory()方法。
      第二步:创建线程awaitCommit调用mcr.writtenToDiskLatch.await()方法。
      第三步:在QueuedWork添加awaitCommit线程。
      第四步:创建线程postWriteRunnable调用awaitCommit.run()方法,从QueuedWork中移除awaitCommit。
      第五步:调用SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable)方法。
      第六步:调用notifyListeners(mcr)方法。
      总结:所有的put、clear、remove都加了同步锁,如果有多次调用putString,建议使用putStringSet方法,减少锁的消耗。
      edit()每次都是创建新的EditorImpl对象,getSharedPreferences()是从ContextImpl.sSharedPrefsCache唯一的SPI对象。
      commit与apply的区别:
        (1)apply没有返回值;commit有返回值能知道修改是否提交成功。
        (2)apply是将修改提交到内存中,再异步提交到磁盘文件;commit是同步地提交到磁盘文件。
        (3)多并发的提交commit是,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率;而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率。

    3.获取数据

    3.1.public Map<String, ?> getAll()

        public Map<String, ?> getAll() {
            synchronized (this) {
                awaitLoadedLocked();
                //noinspection unchecked
                return new HashMap<String, Object>(mMap);
            }
        }

      第一步:调用awaitLoadedLocked()方法。
      第二步:返回mMap的HashMap。

    3.2.public String get***(String key, @Nullable *** defValue)

      get***方法的实现基本相同,这里以getString()方法为例介绍。

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

      第一步:同步this。
      第二步:调用awaitLoadedLocked()方法。
      第三步,从mMap中获取key对应的value值,如果为空,则返回defValue,不为空。则返回从mMap中得到的值。
      总结:所有的get都加了同步锁,

    四、优化建议

    1.强烈建议不要在sp里面存储特别大的key/value,有助于减少卡顿。
    2.请不要高频地使用apply,尽可能地批量提交,commit直接在主线程操作,也需要主要不要高频的使用。
    3.不要使用MODE_MULTI_PROCESS。
    4.高频写操作的key与高频读操作的key可以适当地拆分文件,用于减少同步锁竞争。
    5.不要一上来就执行getSharedPreferences().edit(),应当分成量大步骤来做,中间可以执行其他代码。
    6.不要连续多次edit(),应该一次获取edit(),然后多次执行putXXX(),减少内存波动。
    7.每次commit时会把所有的数据更新到文件,所以整个文件是不应该过大的,影响整体性能。


    参考文章:http://gityuan.com/2017/06/18/SharedPreferences/

  • 相关阅读:
    php+mysql 实现无限极分类
    php 开启微信公众号开发者模式
    PHP对象继承
    phpexcel导出数字带E的解决方法
    jquery layui的巨坑
    jquery jssdk分享报错解决方法
    javascript腾讯地图放到网页中的方法
    jquery手机端横屏判断方法
    javascript 字符串转化成函数执行
    PHP创建文件命名中文乱码解决的方法
  • 原文地址:https://www.cnblogs.com/zhangmiao14/p/7209302.html
Copyright © 2011-2022 走看看