阅读 109

SharedPreferences源码解析(sharedpreferences使用方法)

简介

SharedPreferences(简称SP)是Android中很常用的数据存储方式,SP采用key-value(键值对)形式,  但不建议使用SP 来存储大规模的数据, 可能会降低性能.

问题

  1. 为什么不要存放大的key和value,会引起界面卡?

  2. apply和commit的区别是啥?

  3. 写入磁盘是增量还是全量?

使用

private void putValue() {     SharedPreferences sharedPreferences = getSharedPreferences("gityuan", Context.MODE_PRIVATE);     SharedPreferences.Editor editor = sharedPreferences.edit();     editor.putString("blog", "www.gityuan.com");     editor.putInt("years", 3);     editor.commit(); } private String getValue() {     SharedPreferences sharedPreferences = getSharedPreferences("gityuan", Context.MODE_PRIVATE);     return sharedPreferences.getString("blog", "null"); } 复制代码

源码分析

获取SharedPreferences

ContextImpl.java

 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<>();         }         (1)获取当面名字的File         file = mSharedPrefsPaths.get(name);         if (file == null) {             (2)如果没找到就创建一个文件             file = getSharedPreferencesPath(name);             (3)存到Map内存中             mSharedPrefsPaths.put(name, file);         }     }     (4)看下这个方法。     return getSharedPreferences(file, mode); } @Override public File getSharedPreferencesPath(String name) {     return makeFilename(getPreferencesDir(), name + ".xml"); } 复制代码

@Override public SharedPreferences getSharedPreferences(File file, int mode) {     SharedPreferencesImpl sp;     synchronized (ContextImpl.class) {         final ArrayMap<File, SharedPreferencesImpl> cache =          (1)看下这个方法 getSharedPreferencesCacheLocked();         sp = cache.get(file);         if (sp == null) {             checkMode(mode);             if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {                 if (isCredentialProtectedStorage()                         && !getSystemService(UserManager.class)                                 .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {                     throw new IllegalStateException("SharedPreferences in credential encrypted "                             + "storage are not available until after user is unlocked");                 }             }             (2)创建SharedPreferencesImpl。看下构造方法里面做了啥。             sp = new SharedPreferencesImpl(file, mode);             (3)放到Map里面             cache.put(file, sp);             return sp;         }     }     (4)指定多进程模式, 则当文件被其他进程改变时,则会重新加载,多进程不做重点。     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; } @GuardedBy("ContextImpl.class") private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {     if (sSharedPrefsCache == null) {         sSharedPrefsCache = new ArrayMap<>();     }     final String packageName = getPackageName();     (1)获取当前包名的Map<File, SharedPreferencesImpl>     ArrayMap<File, SharedPreferencesImpl> packagePrefs =  sSharedPrefsCache.get(packageName);     if (packagePrefs == null) {         packagePrefs = new ArrayMap<>();         sSharedPrefsCache.put(packageName, packagePrefs);     }     return packagePrefs; } 复制代码

每一个App对应多个(name -> File -> SharedPreferencesImpl)。

SharedPreferencesImpl初始化

@UnsupportedAppUsage SharedPreferencesImpl(File file, int mode) {     mFile = file;     (1)创建备份文件     mBackupFile = makeBackupFile(file);     mMode = mode;     mLoaded = false;     mMap = null;     mThrowable = null;     (2)加载磁盘     startLoadFromDisk(); } 复制代码

private void startLoadFromDisk() {     synchronized (mLock) {         (1)标记位是否加载到内存         mLoaded = false;     }     new Thread("SharedPreferencesImpl-load") {         public void run() {             loadFromDisk();         }     }.start(); } 复制代码

private void loadFromDisk() {     synchronized (mLock) {         if (mLoaded) {             return;         }         (1)备份文件存在就把备份文件的内存写到File中         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<String, Object> map = null;     StructStat stat = null;     Throwable thrown = null;     try {         stat = Os.stat(mFile.getPath());         if (mFile.canRead()) {             BufferedInputStream str = null;             try {                 str = new BufferedInputStream(                         new FileInputStream(mFile), 16 * 1024);                 map = (Map<String, Object>) XmlUtils.readMapXml(str);             } catch (Exception e) {                 Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);             } finally {                 IoUtils.closeQuietly(str);             }         }     } catch (ErrnoException e) {         // An errno exception means the stat failed. Treat as empty/non-existing by         // ignoring.     } catch (Throwable t) {         thrown = t;     }     synchronized (mLock) {         mLoaded = true;         mThrowable = thrown;         // It's important that we always signal waiters, even if we'll make         // them fail with an exception. The try-finally is pretty wide, but         // better safe than sorry.         try {             if (thrown == null) {                 if (map != null) {                     mMap = map;(1)读取到内容放到内存中                     mStatTimestamp = stat.st_mtim;(2)跟新修改时间                     mStatSize = stat.st_size;(3)更新文件大小                 } else {                     mMap = new HashMap<>();                 }             }             // In case of a thrown exception, we retain the old map. That allows             // any open editors to commit and store updates.         } catch (Throwable t) {             mThrowable = t;         } finally {             mLock.notifyAll();(4)唤醒处于等待状态的线程         }     } } 复制代码

获取数据

@Nullable public String getString(String key, @Nullable String defValue) {     synchronized (mLock) {         (1)等待磁盘加载到内存成功         awaitLoadedLocked();         String v = (String)mMap.get(key);         return v != null ? v : defValue;     } } @GuardedBy("mLock") 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 {             mLock.wait();         } catch (InterruptedException unused) {         }     }     if (mThrowable != null) {         throw new IllegalStateException(mThrowable);     } } 复制代码

获取数据会一直等主线程完成磁盘数据加载到内存中。

Editor

SharedPreferences.Editor editor = sharedPreferences.edit();

@Override public Editor edit() {     (1)等待读取到内存     synchronized (mLock) {         awaitLoadedLocked();     }     return new EditorImpl(); } 复制代码

public final class EditorImpl implements Editor {     private final Map<String, Object> mModified = Maps.newHashMap();     private boolean mClear = false;     //插入数据     public Editor putString(String key, @Nullable String value) {         synchronized (this) {             //插入数据, 先暂存到mModified对象             mModified.put(key, value);             return this;         }     }     //移除数据     public Editor remove(String key) {         synchronized (this) {             mModified.put(key, this);             return this;         }     }     //清空全部数据     public Editor clear() {         synchronized (this) {             mClear = true;             return this;         }     } } 复制代码

去了Commit和Apply都只是把数据先存入内存中。

数据提交

@Override public boolean commit() {     long startTime = 0;     if (DEBUG) {         startTime = System.currentTimeMillis();     }     (1)将数据更新到内存     MemoryCommitResult mcr = commitToMemory();     (2)将内存数据同步到文件     SharedPreferencesImpl.this.enqueueDiskWrite(         mcr, null /* sync write on this thread okay */);     try {         (3)进入等待状态,直到写入文件的完成         mcr.writtenToDiskLatch.await();     } catch (InterruptedException e) {         return false;     } finally {         if (DEBUG) {             Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration                     + " committed after " + (System.currentTimeMillis() - startTime)                     + " ms");         }     }     (4)通知监听者,并在主线程回调onSharedPreferenceChanged()方法。     notifyListeners(mcr);     (5)返回写入磁盘的结果     return mcr.writeToDiskResult; } 复制代码

// Returns true if any changes were made private MemoryCommitResult commitToMemory() {     long memoryStateGeneration;     boolean keysCleared = false;     List<String> keysModified = null;     Set<OnSharedPreferenceChangeListener> listeners = null;     Map<String, Object> mapToWriteToDisk;     synchronized (SharedPreferencesImpl.this.mLock) {         // 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);         }         mapToWriteToDisk = mMap;         mDiskWritesInFlight++;         boolean hasListeners = mListeners.size() > 0;         if (hasListeners) {             keysModified = new ArrayList<String>();             listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());         }         synchronized (mEditorLock) {             boolean changesMade = false;             (1)当mClear为true, 则直接清空mMap             if (mClear) {                 if (!mapToWriteToDisk.isEmpty()) {                     changesMade = true;                     mapToWriteToDisk.clear();                 }                 keysCleared = true;                 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.                 (2)注意此处的this是个特殊值, 用于移除相应的key操作.                     如果value为null就移除相应的Key。                 if (v == this || v == null) {                     if (!mapToWriteToDisk.containsKey(k)) {                         continue;                     }                     mapToWriteToDisk.remove(k);                 } else {                     if (mapToWriteToDisk.containsKey(k)) {                         Object existingValue = mapToWriteToDisk.get(k);                         if (existingValue != null && existingValue.equals(v)) {                             continue;                         }                     }                     mapToWriteToDisk.put(k, v);                 }                 (3)数据有改变的标志位                 changesMade = true;                 if (hasListeners) {                     (4)记录发生改变的Key。                     keysModified.add(k);                 }             }            (5)清空EditorImpl中的mModified数据             mModified.clear();             if (changesMade) {                 mCurrentMemoryStateGeneration++;             }             memoryStateGeneration = mCurrentMemoryStateGeneration;         }     }     return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,             listeners, mapToWriteToDisk); } 复制代码

private void enqueueDiskWrite(final MemoryCommitResult mcr,                               final Runnable postWriteRunnable) {     (1)同步写入还是异步写入                               final boolean isFromSyncCommit = (postWriteRunnable == null);     final Runnable writeToDiskRunnable = new Runnable() {             @Override             public void run() {                 synchronized (mWritingToDiskLock) {                     (2)写入文件                     writeToFile(mcr, isFromSyncCommit);                 }                 synchronized (mLock) {                     mDiskWritesInFlight--;                 }                 if (postWriteRunnable != null) {                     postWriteRunnable.run();                 }             }         };     // Typical #commit() path with fewer allocations, doing a write on     // the current thread.     if (isFromSyncCommit) {         boolean wasEmpty = false;         synchronized (mLock) {             (3)写入内存加1,写入磁盘减1             wasEmpty = mDiskWritesInFlight == 1;         }         if (wasEmpty) {             writeToDiskRunnable.run();             return;         }     }     QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } 复制代码

@GuardedBy("mWritingToDiskLock") private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {     long startTime = 0;     long existsTime = 0;     long backupExistsTime = 0;     long outputStreamCreateTime = 0;     long writeTime = 0;     long fsyncTime = 0;     long setPermTime = 0;     long fstatTime = 0;     long deleteTime = 0;     if (DEBUG) {         startTime = System.currentTimeMillis();     }     boolean fileExists = mFile.exists();     if (DEBUG) {         existsTime = System.currentTimeMillis();         // Might not be set, hence init them to a default value         backupExistsTime = existsTime;     }     // Rename the current file so it may be used as a backup during the next read     if (fileExists) {         boolean needsWrite = false;         // Only need to write if the disk state is older than this commit         if (mDiskStateGeneration < mcr.memoryStateGeneration) {             (1)commit会需要写入             if (isFromSyncCommit) {                 needsWrite = true;             } else {                 synchronized (mLock) {                     // No need to persist intermediate states. Just wait for the latest state to                     // be persisted.                     if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {                         needsWrite = true;                     }                 }             }         }         (2)不需要写入会立刻返回写入成功         if (!needsWrite) {             mcr.setDiskWriteResult(false, true);             return;         }         boolean backupFileExists = mBackupFile.exists();         if (DEBUG) {             backupExistsTime = System.currentTimeMillis();         }         if (!backupFileExists) {             if (!mFile.renameTo(mBackupFile)) {                 Log.e(TAG, "Couldn't rename file " + mFile                       + " to backup file " + mBackupFile);                 mcr.setDiskWriteResult(false, 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 (DEBUG) {             outputStreamCreateTime = System.currentTimeMillis();         }         if (str == null) {             mcr.setDiskWriteResult(false, false);             return;         }         (5)写入磁盘         XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);         writeTime = System.currentTimeMillis();         FileUtils.sync(str);         fsyncTime = System.currentTimeMillis();         str.close();         ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);         if (DEBUG) {             setPermTime = System.currentTimeMillis();         }         try {             final StructStat stat = Os.stat(mFile.getPath());             synchronized (mLock) {                 mStatTimestamp = stat.st_mtim;                 mStatSize = stat.st_size;             }         } catch (ErrnoException e) {             // Do nothing         }         if (DEBUG) {             fstatTime = System.currentTimeMillis();         }         // Writing was successful, delete the backup file if there is one.         mBackupFile.delete();         if (DEBUG) {             deleteTime = System.currentTimeMillis();         }         mDiskStateGeneration = mcr.memoryStateGeneration;         (3)写入成功         mcr.setDiskWriteResult(true, true);         if (DEBUG) {             Log.d(TAG, "write: " + (existsTime - startTime) + "/"                     + (backupExistsTime - startTime) + "/"                     + (outputStreamCreateTime - startTime) + "/"                     + (writeTime - startTime) + "/"                     + (fsyncTime - startTime) + "/"                     + (setPermTime - startTime) + "/"                     + (fstatTime - startTime) + "/"                     + (deleteTime - startTime));         }         long fsyncDuration = fsyncTime - writeTime;         mSyncTimes.add((int) fsyncDuration);         mNumSync++;         if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {             mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");         }         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);         }     }     (4)写入失败     mcr.setDiskWriteResult(false, false); } 复制代码

数据有改变才会写入磁盘。

写入磁盘。

public static final void writeMapXml(Map val, XmlSerializer out,         WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {     if (val == null) {         return;     }     Set s = val.entrySet();     Iterator i = s.iterator();     while (i.hasNext()) {         Map.Entry e = (Map.Entry)i.next();         writeValueXml(e.getValue(), (String)e.getKey(), out, callback);     } } 复制代码

apply

@Override public void apply() {     final long startTime = System.currentTimeMillis();     final MemoryCommitResult mcr = commitToMemory();     final Runnable awaitCommit = new Runnable() {             @Override             public void run() {                 try {                     mcr.writtenToDiskLatch.await();                 } catch (InterruptedException ignored) {                 }                 if (DEBUG && mcr.wasWritten) {                     Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration                             + " applied after " + (System.currentTimeMillis() - startTime)                             + " ms");                 }             }         };     (1)将awaitCommit添加到QueuedWork     QueuedWork.addFinisher(awaitCommit);     Runnable postWriteRunnable = new Runnable() {             @Override             public void run() {                 awaitCommit.run();                 QueuedWork.removeFinisher(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 enqueueDiskWrite(final MemoryCommitResult mcr,                               final Runnable postWriteRunnable) {     final boolean isFromSyncCommit = (postWriteRunnable == null);     final Runnable writeToDiskRunnable = new Runnable() {             @Override             public void run() {                 synchronized (mWritingToDiskLock) {                     writeToFile(mcr, isFromSyncCommit);                 }                 synchronized (mLock) {                     mDiskWritesInFlight--;                 }                 if (postWriteRunnable != null) {                     postWriteRunnable.run();                 }             }         };     // Typical #commit() path with fewer allocations, doing a write on     // the current thread.     if (isFromSyncCommit) {         boolean wasEmpty = false;         synchronized (mLock) {             wasEmpty = mDiskWritesInFlight == 1;         }         if (wasEmpty) {             writeToDiskRunnable.run();             return;         }     }     (1)apply会走到这里     QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } 复制代码

public static void queue(Runnable work, boolean shouldDelay) {     (1)看下Handler     Handler handler = getHandler();     synchronized (sLock) {         sWork.add(work);         (2)apply正常情况下发送了一个延迟消息。(去除Activity关闭)         if (shouldDelay && sCanDelay) {             handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);         } else {             handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);         }     } } @UnsupportedAppUsage private static Handler getHandler() {     synchronized (sLock) {         if (sHandler == null) {             (1)起了一个子线程去处理消息             HandlerThread handlerThread = new HandlerThread("queued-work-looper",                     Process.THREAD_PRIORITY_FOREGROUND);             handlerThread.start();             sHandler = new QueuedWorkHandler(handlerThread.getLooper());         }         return sHandler;     } } 复制代码

总结:

  1. 为什么不要存放大的key和value,会引起界面卡?

  2. apply和commit的区别是啥?

  3. 写入磁盘是增量还是全量?

第一个问题:

第一次getSharedPreferences的时候会在主线程读取磁盘文件。文件太大,会导致主线程卡顿。

第二个问题:

  1. apply()以异步的方式写入磁盘,而commit()是以同步的方式写入磁盘。但是当组件(Activity Service BroadCastReceiver)这些系统组件特定状态转换的时候,会把QueuedWork中未完成的那些磁盘写入操作放在主线程执行,apply()这个时候就是同步写入了。

  2. apply没有返回值, commit有返回值能知道修改是否提交成功。

第三个问题:

是全量写入的。可以看下源码分析的写入磁盘部分代码。

总结:

  1. 里面用了大量的锁,效率肯定是低的。


作者:中玉
链接:https://juejin.cn/post/7062720610991865893


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐