current position:Home>In depth analysis of Android SharedPreferences source code
In depth analysis of Android SharedPreferences source code
2022-01-27 00:11:38 【Manon Xiaofeng】
summary
SharedPreferences( abbreviation SP) yes Android Common data storage methods in ,SP use key-value( Key value pair ) form , It is mainly used for lightweight data storage , It is especially suitable for saving the configuration parameters of the application , But not recommended SP To store large-scale data , It can degrade performance .
SP use XML File format to save data , The file is located at /data/data/<packageName>/shared_prefs/
.
Examples of use
// load SP File data ,“my_prefs” For the file name
SharedPreferences sp = getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
// Save the data
Editor editor = sp.edit();
editor.putString("blog", "www.xiaox.com");
// Submit data : Synchronization mode , A return value indicates whether the data is saved successfully
boolean result = editor.commit();
// Submit data : Asynchronous way , no return value //
editor.apply()
// Reading data
String blog = sp.getString("blog", "");
my_prefs.xml The contents of the document :
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="blog">www.xiaox.com</string>
</map>
framework
Class diagram
explain :SharedPreferences And Editor Just two interfaces ,SharedPreferencesImpl and EditorImp The corresponding interfaces are implemented respectively . in addition ,ContextImpl Record SharedPreferences Important data , as follows :
- sSharedPrefsCache: By package name key, second level key yes SP file , With SharedPreferencesImp by value Nesting of map structure ,sSharedPrefsCache Is a static member variable , There is only one copy of each process , And by the ContextImpl.class Lock protection .
- mSharedPrefsPaths: Record all SP file , Named by file name key, The specific document is value Of map structure .
- mPreferencesDir: Is the value SP In the directory , namely
/data/data/<packageName>/shared_prefs/
Workflow
explain :
- putXxx() operation : Write data to EditorImpl.mModified;
- apply() perhaps commit() operation : a. First call
commitToMemory()
, Synchronize data to SharedPreferencesImpl Of mMap, And save to MemoryCommitResult OfmapToWriteToDisk
; b. Call againenqueueDiskWrite()
, Write to disk file ; Before that, save the original data to.bak
Postfix file , Used to recover any abnormal data in the process of writing to the disk . - getXxx() operation : from SharedPreferencesImpl.mMap Reading data .
Source code analysis (API 28)
obtain SharedPreferences
Can pass Activity.getPreferences(mode)
、 PreferenceManager.getDefaultSharedPreferences(context)
perhaps Context.getSharedPreferences(name,mode)
To get SharedPreferences example , The final call is ContextImpl Of getSharedPreferences(name, mode).
ContextImpl#getSharedPreferences(name, mode):
class ContextImpl extends Context {
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
// ...
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// ...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// First from mSharedPrefsPaths Query whether the corresponding file exists
file = mSharedPrefsPaths.get(name);
if (file == null) {
// If the file doesn't exist , Create a new file
file = getSharedPreferencesPath(name);
// Save the newly created file to mSharedPrefsPaths, Named by file name key
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
// Create directory /data/data/<packageName>/shared_prefs/
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
}
ContextImpl#getSharedPreferences(file, mode):
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = 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");
}
}
// establish SharedPreferencesImpl
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
// Specify multi process mode , When the file is changed by another process , It will be reloaded
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();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
SharedPreferencesImpl initialization :
SharedPreferencesImpl.java
SharedPreferencesImpl(File file, int mode) {
mFile = file;
// establish .bak Suffix backup file , Used when an exception occurs , You can restore data by backing up files
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
SharedPreferencesImpl#startLoadFromDisk():
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
// Read the file data to through the worker thread mMap
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}
.start();
}
private void loadFromDisk() {
synchronized (mLock) {
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<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;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} 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();
}
}
}
obtain SharedPreferences summary :
- For the first use, create the corresponding xml file ;
- Load file contents into memory asynchronously , Execute at this time getXxx() and edit() Methods are blocking and waiting , Until all the file data is loaded into memory ;
- Once the data is fully loaded into memory , Follow up getXxx() Direct access to memory .
get data
SharedPreferencesImpl#getString(key, defValue):
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// Check whether the data is loaded
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
private void awaitLoadedLocked() {
if (!mLoaded) {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
// When loading is not complete , Then enter the waiting state
mLock.wait();
}
catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
Edit the data
obtain Editor Editor instance :SharedPreferencesImpl#edit()
public Editor edit() {
synchronized (mLock) {
// Wait for data loading to complete
awaitLoadedLocked();
}
// establish EditorImpl example
return new EditorImpl();
}
EditorImpl#putString(key, value):
public final class EditorImpl implements Editor {
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@GuardedBy("mEditorLock")
private Boolean mClear = false;
// ...
// insert data
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
// insert data , Staging to mModified
mModified.put(key, value);
return this;
}
}
// Remove data
public Editor remove(String key) {
synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
// Clear all the data
public Editor clear() {
synchronized (mEditorLock) {
mClear = true;
return this;
}
}
}
Save the data
Save the data , Mainly called
commit()
andapply()
Method to complete .
EditorImpl#commit()
public Boolean commit() {
// ...
// Update data to memory
MemoryCommitResult mcr = commitToMemory();
// Synchronize memory data to file
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */
);
try {
// Enter the waiting state , Until the operation of writing to the file is completed
mcr.writtenToDiskLatch.await();
}
catch (InterruptedException e) {
return false;
}
// Notification listener , And call back in the main thread onSharedPreferenceChanged() Method
notifyListeners(mcr);
// Returns the result of the file operation
return mcr.writeToDiskResult;
}
EditorImpl#commitToMemory()
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
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;
// When mClear by true, Then empty it directly mMap
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// this It's a special value , When v It's empty , amount to remove The data
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);
}
// Indicates that the data has changed
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
// Empty mModified The data of
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
EditorImpl#enqueueDiskWrite()
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) {
// Perform file write operations
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Use commit Method , Will enter this branch , Execute on current thread
if (isFromSyncCommit) {
Boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// Use apply Method , Will execute the sentence , Put the task into a single threaded thread pool to execute
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
EditorImpl#writeToFile()
private void writeToFile(MemoryCommitResult mcr, Boolean isFromSyncCommit) {
// ...
Boolean fileExists = mFile.exists();
if (fileExists) {
Boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
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;
}
}
}
}
// There is no change , Go straight back to
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
Boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
// When the backup file does not exist , Then put mFile Rename to backup file
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
// otherwise , Delete directly mFile
mFile.delete();
}
}
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
// take mMap All information is written to the file
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
writeTime = System.currentTimeMillis();
FileUtils.sync(str);
fsyncTime = System.currentTimeMillis();
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
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();
}
// Write successfully , Delete backup file
mBackupFile.delete();
mDiskStateGeneration = mcr.memoryStateGeneration;
// Return write success , Wake up waiting thread
mcr.setDiskWriteResult(true, true);
return;
}
catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// If the file write operation fails , Delete the file that was not successfully written
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
// Return write failure , Wake up waiting thread
mcr.setDiskWriteResult(false, false);
}
EditorImpl#apply()
public void apply() {
final long startTime = System.currentTimeMillis();
// Update data to memory
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
// Enter the waiting state
mcr.writtenToDiskLatch.await();
}
catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
// Write data to file asynchronously
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
performance optimization
IO bottleneck
IO The bottleneck is caused by SP The biggest reason for poor performance , solve IO bottleneck ,80% The performance problem is solved .
SP Of IO Bottlenecks include Read data to memory
And Data written to disk
Two parts .
1. There are two scenarios to trigger when reading data into memory :
-
SP When the file is not loaded into memory , call getSharedPreferences Method initializes the file and reads it into memory .
-
Version below Android-H Or use MULTI_PROCESS When the tag , Every time you call getSharedPreference Method will be read in .
Optimize :
What we can optimize is b 了 , Too much data is loaded into memory every time, which will affect the efficiency , but H The following versions are already very low , Basically negligible . about MULTI_PROCESS, May adopt ContentProvider And so on , More efficient , And can avoid SP Data loss .
2. There are also two scenarios for writing data to disk :
-
Editor Of commit Method , Write to disk synchronously every time .
-
Editor Of apply Method , Write to the disk in the single thread pool every time , Asynchronous write .
Optimize :
commit and apply The difference between synchronous writing and asynchronous writing , And whether the return value is required . Without the need to return a value , Use apply This method can greatly improve the performance . meanwhile , Multiple write operations can be combined into one commit/apply, Combining multiple write operations can also improve IO performance .
Poor lock performance
-
SP Of get operation , Will lock SharedPreferences object , Mutually exclusive other operations .
-
SP Of put operation ,edit() And commitToMemory Will lock SharedPreferences object ,put Operation will lock Editor object , Writing to the disk will lock a write lock .
Optimize :
Because of the lock ,SP When the operation is concurrent , Time consuming will increase . Reduce lock time , Is an optimization point . Because the locks of read and write operations are SP Of instance objects , Split the data into different sp In file , This is a direct solution to reduce lock time . Reduce the frequency of single file access , Multi file sharing access , To reduce lock time .
Optimization summary
- It is strongly recommended not to be in sp It stores very large key/value, It helps to reduce the number of jams /anr;
- Please do not use high frequency apply, Submit as many batches as possible ;commit Operate directly on the main thread , Pay more attention to ;
- Do not use MODEMULTIPROCESS;
- High frequency write operation key With high frequency read operation key You can split the file properly , Due to the reduction of synchronous lock competition ;
- Don't do it right away getSharedPreferences().edit(), It should be done in two major steps , Other code can be executed in the middle ;
- Don't repeat it many times in a row edit(), You should get it once edit(), And then do it many times putxxx(), Reduce memory fluctuations ; We often see that people like packaging methods , The result is this ;
- Every time commit A file that will update all the data when , So the whole file should not be too large , Affect overall performance .
copyright notice
author[Manon Xiaofeng],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201270011338539.html
The sidebar is recommended
- Spring IOC container loading process
- [thinking] the difference between singleton mode and static method - object-oriented programming
- Hadoop environment setup (MySQL environment configuration)
- 10 minutes, using node JS creates a real-time early warning system for bad weather!
- Git tool
- Force deduction algorithm - 92 Reverse linked list II
- What is the sub problem of dynamic programming?
- C / C + +: static keyword summary
- Idea does not have the artifacts option when configuring Tomcat
- Anaconda can't open it
guess what you like
-
I don't know how to start this
-
Matlab simulation of transportation optimization algorithm based on PSO
-
MySQL slow log optimization
-
[Vue] as the window is stretched (larger, smaller, wider and higher), the text will not be displayed
-
Popular Linux distributions for embedded computing
-
Suzhou computer research
-
After installing SSL Certificate in Windows + tomcat, the domain name request is not successful. Please answer!!
-
Implementation time output and greetings of jQuery instance
-
The 72 year old uncle became popular. Wu Jing and Guo fan made his story into a film, which made countless dreamers blush
-
How to save computer research
Random recommended
- Springboot implements excel import and export, which is easy to use, and poi can be thrown away
- The final examination subjects of a class are mathematical programming, and the scores are sorted and output from high to low
- Two pronged approach, Tsinghua Professor Pro code JDK and hotspot source code notes, one-time learning to understand
- C + + recursive knapsack problem
- The use of GIT and GitHub and the latest git tutorial are easy to understand -- Video notes of crazy God speaking
- PostgreSQL statement query
- Ignition database test
- Context didn't understand why he got a high salary?, Nginxfair principle
- Bootstrap switch switch control user's guide, springcloud actual combat video
- A list that contains only strings. What other search methods can be used except sequential search
- [matlab path planning] multi ant colony algorithm grid map path planning [including GUI source code 650]
- [matlab path planning] improved genetic algorithm grid map path planning [including source code phase 525]
- Iinternet network path management system
- Appium settings app is not running after 5000ms
- Reactnative foundation - 07 (background image, status bar, statusbar)
- Reactnative foundation - 04 (custom rpx)
- If you want an embedded database (H2, hsql or Derby), please put it on the classpath
- When using stm32g070 Hal library, if you want to write to flash, you must perform an erase. If you don't let it, you can't write continuously.
- Linux checks where the software is installed and what files are installed
- SQL statement fuzzy query and time interval filtering
- 69. Sqrt (x) (c + + problem solving version with vs runnable source program)
- Fresh students are about to graduate. Do you choose Java development or big data?
- Java project: OA management system (java + SSM + bootstrap + MySQL + JSP)
- Titanic passenger survival prediction
- Vectorization of deep learning formula
- Configuration and use of private image warehouse of microservice architect docker
- Relearn JavaScript events
- For someone, delete return 1 and return 0
- How does Java dynamically obtain what type of data is passed? It is used to judge whether the data is the same, dynamic data type
- How does the database cow optimize SQL?
- [data structure] chain structure of binary tree (pre order traversal) (middle order traversal) (post order traversal) (sequence traversal)
- Webpack packaging optimization solution
- 5. Operation element
- Detailed explanation of red and black trees
- redhat7. 9 install database 19C
- Blue Bridge Cup notes: (the given elements are not repeated) complete arrangement (arrangement cannot be repeated, arrangement can be repeated)
- Detailed explanation of springboot default package scanning mechanism and @ componentscan specified scanning path
- How to solve the run-time exception of test times
- Detailed explanation of k8s management tool kubectl
- Android system view memory command