SoFunction
Updated on 2025-04-06

Detailed explanation of the principle of ANR triggering by SharedPreference

text

In daily development, usedSharedPreferenceStudents must have seen it on the surveillance platformSharedPreferenceThe 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 whySharedPreferenceIt is easy to generate ANR.

SharedPreferenceI 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

SharedPreferenceIn addition tocommitIn addition, most development students will call directly on the main thread, thinking that this is not time-consuming. But in fact,SharedPreferenceMany methods are time-consuming, and directing directly on the main thread may cause ANR problems. In addition, althoughapplyThe 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()GetsharedPrefsThe 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 againgetSharedPreferencesgenerateSharedPreferenceImplThe 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&lt;File, SharedPreferencesImpl&gt; packagePrefs = (packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap&lt;&gt;();
            (packageName, packagePrefs);
        }
        return packagePrefs;
    }

Let's take a look againSharedPreferenceImplThe construction method, take a look atSharedPreferenceHow 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&lt;String, Object&gt;) (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,getSharedPreferencesThe main point of lag is to obtainPreferencesDirAt 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.

fromSharedPreferencesImplWe 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(), releasedmLockThe object locks and enters the waiting pool until it is awakened.

Summary: So,getBooleanWait for obtainingkeyThe 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 knowsapplyThe method is asynchronous writing, but it may also cause ANR problems. Let's take a look belowapplySource 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 whyapplyWill it also cause ANR?

BecauseActivityandServiceSome 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,SharedPreferenceStuttering may occur in the following situations, causing ANR:

  • createSharedPreferenceWhen calledgetPreferenceDir, there may be behavior of creating directories
  • getBooleanWait untilSharedPreferenceRead all key-value pairs in the file into the cache before returning
  • commitThe method is written synchronously, and if it is accidentally called in the main thread, it will cause lag
  • applyAlthough the method is written in an asynchronous thread, it is becauseActivityandServiceThe life cycle will wait for allSharedPreferenceThe write is completed, so it may cause lag and ANR problems

SharedPreferenceFrom the beginning of design, it was to store a small amountkey-valueYes, 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.JSONStrings, etc., have caused its shortcomings to be clearly exposed. It is recommended to use itSharedPreferenceWhen 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!