text
In daily development, usedSharedPreference
Students must have seen it on the surveillance platformSharedPreference
The relevant ANR should be quite large. If you use more or use sp often to store some big data, such as json, the relevant ANRs can often rank in the top 10. Let’s take a look at the source code from the perspective of whySharedPreference
It is easy to generate ANR.
SharedPreference
I believe that students who have done Android development can use it, so I will only briefly introduce it here, not in detail.
// Initialize a spSharedPreferences sharedPreferences = ("name_sp", MODE_PRIVATE); // There are two ways to modify the value of the key: commit and apply().putBoolean("key_test", true).commit(); ().putBoolean("key_test", true).apply(); // Read a key("key_test", false);
SharedPreference Issues
SharedPreference
In addition tocommit
In addition, most development students will call directly on the main thread, thinking that this is not time-consuming. But in fact,SharedPreference
Many methods are time-consuming, and directing directly on the main thread may cause ANR problems. In addition, althoughapply
The method is not time-consuming, but it will cause life cycle-related ANR problems.
Let’s take a look at the problem that may cause ANR from the source code perspective.
getSharedPreference(String name, int mode)
@Override public SharedPreferences getSharedPreferences(String name, int mode) { File file; // All operations related to sp use ContextImpl's class lock synchronized () { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } // mSharedPrefsPaths is the file path of the memory cache file = (name); if (file == null) { // Get the file path of SharedPreferences here, it may be time-consuming file = getSharedPreferencesPath(name); (name, file); } } return getSharedPreferences(file, mode); }
Let’s take a look at the method to get the file path:getSharedPreferencesPath()
, this method may be time-consuming.
public File getSharedPreferencesPath(String name) { // Create a S storage file return makeFilename(getPreferencesDir(), name + ".xml"); }
CallgetPreferencesDir()
GetsharedPrefs
The root path
private File getPreferencesDir() { // All files-related operations will use mSync locks, which may cause time-consuming to grab locks with other threads synchronized (mSync) { if (mPreferencesDir == null) { mPreferencesDir = new File(getDataDir(), "shared_prefs"); } // This method, if the directory does not exist, it will create a directory, which may be time-consuming return ensurePrivateDirExists(mPreferencesDir); } }
ensurePrivateDirExists()
: Make sure the file directory exists
private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) { if (!()) { final String path = (); try { // Creating folders will take time (path, mode); (path, mode); } catch (ErrnoException e) { } return file; }
Let's take a look againgetSharedPreferences
generateSharedPreferenceImpl
The process of the object.
public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized () { // Get the cache, first get the SharedPreferenceImpl from the cache final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = (file); if (sp == null) { // If there is no cache, create a SharedPreferencesImpl, which may be time-consuming here sp = new SharedPreferencesImpl(file, mode); (file, sp); return sp; } } return sp; }
Let's take a look at the principle of cache
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { // sSharedPrefsCache is a static variable that is globally valid if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); } // key: package name, value: ArrayMap<File, SharedPreferencesImpl> final String packageName = getPackageName(); ArrayMap<File, SharedPreferencesImpl> packagePrefs = (packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); (packageName, packagePrefs); } return packagePrefs; }
Let's take a look againSharedPreferenceImpl
The construction method, take a look atSharedPreference
How is it initialized.
SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); // Set the flag to load to memory to false mLoaded = false; startLoadFromDisk(); }
startLoadFromDisk()
: Open a child thread and read the contents in sp into memory
private void startLoadFromDisk() { // When changing the mLoaded flag, you need to obtain the mLock lock synchronized (mLock) { // Set the mLoaded flag to false before loading mLoaded = false; } // Open a thread to read the contents in sp from the file into memory new Thread("SharedPreferencesImpl-load") { public void run() { // load on child thread loadFromDisk(); } }.start(); }
loadFromDisk
: Where to actually read the file
private void loadFromDisk() { synchronized (mLock) { // If you have loaded it, just return it without loading it again if (mLoaded) { return; } stat = (()); if (()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); // Read the content of the xml into the map map = (Map<String, Object>) (str); } catch (Exception e) { (TAG, "Cannot read " + (), e); } finally { (str); } } synchronized (mLock) { // Set the mLoaded flag to true, which means that the load has been completed and notifies all threads waiting mLoaded = true; (); } }
Summary: After the above analysis,getSharedPreferences
The main point of lag is to obtainPreferencesDir
At the time of , the directory may not be created yet. If the method to create a directory is called at this time, it will be very time-consuming.
getBoolean(String key, boolean defValue)
This method is the same as all methods of obtaining keys, and can be time-consuming.
fromSharedPreferencesImpl
We know that a new thread will be opened to read the content from the file into the cached map. This step is called load.
public boolean getBoolean(String key, boolean defValue) { synchronized (mLock) { // You need to wait until the load is successful awaitLoadedLocked(); // Get value from cache Boolean v = (Boolean)(key); return v != null ? v : defValue; } }
The main time-consuming method is in awaitLoadedLocked.
private void awaitLoadedLocked() { // Only when mLoaded is true can the dead loop be broken while (!mLoaded) { try { // After calling wait, the mLock lock will be released and enter the waiting pool, waiting for the wakeup after loading (); } catch (InterruptedException unused) { } } }
This method is called()
, releasedmLock
The object locks and enters the waiting pool until it is awakened.
Summary: So,getBoolean
Wait for obtainingkey
The method will wait until the content of the sp is copied from the file to the cache map. It is likely that there is time-consuming.
commit()
commit()
The method will be written synchronously, which must be time-consuming and cannot be called directly on the main thread.
public boolean commit() { // Start queuing to write ( mcr, null /* sync write on this thread okay */); try { // Wait for the result of synchronous writing (); } catch (InterruptedException e) { return false; } finally { } notifyListeners(mcr); return ; }
apply()
Everyone knowsapply
The method is asynchronous writing, but it may also cause ANR problems. Let's take a look belowapply
Source code of the method.
public void apply() { // Write updates to the memory cache first final MemoryCommitResult mcr = commitToMemory(); // Create a runnable of awaitCommit and add it to QueuedWork final Runnable awaitCommit = new Runnable() { @Override public void run() { try { // Wait for the write to complete (); } catch (InterruptedException ignored) { } } }; // Add awaitCommit to QueuedWork (awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { (); (awaitCommit); } }; // Really execute sp persistence operations and asynchronously (mcr, postWriteRunnable); // Although the file has not been written, the memory cache has been updated, and listeners usually hold the same sharedPreference object, so the data in the memory cache can be used notifyListeners(mcr); }
You can see that this is indeed a write operation performed on the child thread, but whyapply
Will it also cause ANR?
BecauseActivity
andService
Some life cycle methods will be called()
Method, this method will wait for all child threads to complete before continuing. Children such as main thread are prone to ANR problems.
public static void waitToFinish() { Runnable toFinish; //Waiting for all tasks to be completed while ((toFinish = ()) != null) { (); } }
Android 8.0 has made some optimizations here, but it still needs to wait for the write to be completed, and it cannot complete the solution to the ANR problem.
Summarize
In summary,SharedPreference
Stuttering may occur in the following situations, causing ANR:
- create
SharedPreference
When calledgetPreferenceDir
, there may be behavior of creating directories -
getBoolean
Wait untilSharedPreference
Read all key-value pairs in the file into the cache before returning -
commit
The method is written synchronously, and if it is accidentally called in the main thread, it will cause lag -
apply
Although the method is written in an asynchronous thread, it is becauseActivity
andService
The life cycle will wait for allSharedPreference
The write is completed, so it may cause lag and ANR problems
SharedPreference
From the beginning of design, it was to store a small amountkey-value
Yes, and exists. Its own design has many flaws. When storing a particularly small amount of data, the performance bottleneck is not significant. But now many developers will store some large-scale products in it when using it.JSON
Strings, etc., have caused its shortcomings to be clearly exposed. It is recommended to use itSharedPreference
When it is used to store a small amount of data, do not store large strings.
Of course, we also have some methods to optimize uniformlySharedPreference
, reduce the occurrence of ANR, we will continue to talk about it in the next article.
The above is a detailed explanation of the principle of SharedPreference triggering ANR. For more information about SharedPreference triggering ANR, please pay attention to my other related articles!