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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM