In Android development, SharedPreferences are often used. It is a lightweight data storage method that is usually used to store simple configuration information. After reading some articles on the Internet, I am not particularly satisfied with them, so I hope to write an article analyzing SharedPreferences based on my experience and understanding. This article will not explain the basic usage of SharedPreferences, but will be combined with the source code to analyze the working principle of SharedPreferences, as well as some problems existing in the use.

From this article, you can learn:

  • How does SharedPreferences work

  • What pits are used in SharedPreferences

  • How do you avoid the problems with SharedPreferences

First, we need to understand the nature of SharedPreferences. Its essence is key-value key-value data stored based on XML files, which is stored in the directory /data/data/ package name /shared_prefs. Because it is stored in the application’s private directory, it is not directly accessible externally. In other words, it is actually an XML file, no different from normal XML, and the content is similar to the content of the strings.xml file in our project code.

Source code analysis

Below we through the source code analysis, explain its working principle. Let’s look at the basic usage of SharePreferences.

SharedPreferences sp = context. GetSharedPreferences (” file1 “, the context. MODE_PRIVATE);

Sp. Edit (). PutBoolean (” key1 “, false), commit ();

Sp. GetBoolean (” key1 “)

So let’s start with the getSharedPreferences() method, and actually the Context ultimately calls the getSharedPreferences method in ContextImpl, so let’s take a look at that method.

This contains an mSharedPrefsPaths object, which is of type ArrayMap. We can create multiple SP files in our App. MSharedPrefsPaths stores the relationships between different SP files and their names. The getSharedPreferencesPath method here essentially creates an XML file on disk. Look at the getSharedPreferences method in the last line of the figure above.

We see that this method actually returns a SharedPreferencesImpl object, so look at the SharedPreferencesImpl constructor.

StartLoadFromDisk calls loadFromDisk in the child thread. Before executing the thread, mLoaded is set to false. Let’s look at the loadFromDisk method.

This method has a lot of code, so let’s just get to the heart of it. It reads the file through xmlutils.readmapxml () into mMap, which is a HashMap, and sets mLoaded to true. Remember the variable mLoaded, we’ll come back to it later. That is, the contents of the SP file are read into memory and cached in mMap, and subsequent operations on sp are related to the memory cache. Since the contents of sp files are cached in memory, it is important to note that if a large amount of data is stored in the file, it will take up a large amount of memory space.

Now that the SharedPreferences creation process is over, let’s look at the PUT process. The put operation begins by calling the edit() method,

If mLoaded is found to be false in awaitLoadedLocked, and if mLoaded is found to be false in awaitLoadedLocked, The wait method is called, which blocks the current thread until the SP file is read. NotifyAll () is called to notify the blocked thread to continue. That is, if the operation to read the SP file takes a long time, it may block the main thread and cause ANR.

How can you avoid this problem as much as possible? First of all, we need to divide sp files into multiple small files according to their functions and characteristics, for example, according to different functional modules, or according to the frequency of reading and writing, or according to whether the App needs to be loaded when it starts. If each file is small enough, it will take less time to read the file into memory. Especially when the App is started, it only needs to load the SP configuration required for startup, which can reduce the startup time to a certain extent.

Continue to look at the source code. The edit() method returns an Editor object of the actual type EditorImpl.

The EditorImpl contains a HashMap member of type mModified, and calls to Editor methods such as putString simply store data in mModified. This is just a staging area for the data, so if you forget to call commit or apply, the data is not actually written to disk. It is important to note that every time we call the Edit method, an mModified object is created, so it is necessary to reduce the number of edit method calls.

Finally, call commit or apply. Commit is a synchronous write that returns the execution result; The Apply method writes asynchronously and does not return an execution result. The following through the source code to analyze their implementation.

CommitToMemory and enqueueDiskWrite are called in the commit method. The commitToMemory method updates the data cached in the previously mentioned mModified to the previously mentioned mMap, which is eventually written to the file. The enqueueDiskWrite method passes null as the second argument, so isFromSyncCommit is true, and writeToDiskRunnable.run() is executed directly. Write the mMap configuration to sp file by calling writeToFile.

As you can see from the source code, commit execution is synchronous and full write. Do not use commit to save the SP configuration unless necessary to prevent write files from blocking the main thread.

Let’s look at how the implementation of the Apply method differs.

The difference here is that the second argument to enqueueDiskWrite is not null, so the file write is executed asynchronously inside the single-threaded thread pool:

QueuedWork. SingleThreadExecutor (). The execute (writeToDiskRunnable).

Since it is a single thread, any Runnable that is too late for execution is queued for execution. WriteToDiskRunnable writes sp to a file with writeToFile and then calls postWriteRunnable’s run(), which in turn calls awaitCommit’s run(). The last call MCR. WrittenToDiskLatch. Await (). So what does the writtenToDiskLatch do? The writeToFile method will eventually countDown the writtenToDiskLatch. If the sp file is not written, the writtentoDisklato.await () call will block. The countDown to writtenToDiskLatch must be “await” before “await”. Again, notice that there is a line of code: QueuedWork.add(awaitCommit). So what is QueuedWork?

With some code omitted, the Add method actually adds runnable to a ConcurrentLinkedQueue. The following waitToFinish method iterates through each Runnable in the queue and executes its run method. So where is the waitToFinish method called? Let’s look at one of the handlePauseActivity and handleStopActivity methods of the ActivityThread class.

We see that queuedWork.waittoFinish () is called when the Activity calls onStop, iterating through the runnable. If we call apply frequently and onStop is followed by onStop, then onStop may wait for Queuedwork.waittoFinish to complete, resulting in ANR. That is, even if the Apply method is called to commit asynchronously, it is not completely safe. If the Apply method is not used properly, you may encounter a problem similar to the following figure.

The put operation was mentioned above, but since the GET operation is relatively simple, we will not analyze it separately here.

conclusion

From the above analysis we find that the use of SharedPreferences is not so simple, improper use may lead to program exceptions, we will sum up some of the problems mentioned above:

  • Do not write all the SP configuration in one file, which will not only be slow to load the first time, but also take up a lot of memory. It is best to divide into multiple SP files according to certain rules. Frequently and infrequently written configurations, for example, are stored in two separate files.

  • Sp files are written in full. Even if a configuration is changed, operations will be performed on the entire file. Therefore, it is best to batch write operations and do not commit every time.

  • Start the sp configuration needs to read the best asynchronous, if you must read synchronous, start the SP file to be as small as possible.

  • Do not store large configuration items (including keys and values) in the SP, otherwise it will take up a lot of memory.

  • When obtaining the SharedPreferences object, the SP file will be read. If the file is not read, the GET and PUT operations will be performed, which may cause waiting. Therefore, it is best to obtain the SharedPreferences object in advance.

  • Each call to the Edit method creates a new EditorImpl object; do not call the Edit method too often.

  • The Apply method asynchronously writes configuration to a file in a thread, but if there are many tasks and each task takes a long time to execute, it can also cause ANR to occur when an Activity or Service is stopped.

Welcome to follow my wechat public number and receive the latest push articles