Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

preface

Haven’t analyzed source code for a long time, today we will analyze the next SharedPreferences;

Let’s learn together;

This article has been published in the public account [Android development programming]

SharedPreferences Easy to use

1, create,

The first parameter is the name of the stored XML file, and the second parameter is the opening method, which is generally used

The Context. MODE_PRIVATE; SharedPreferences sp = context. GetSharedPreferences (" name ", the context, MODE_PRIVATE);Copy the code

2, write

/ / can create a new SharedPreference operation SharedPreferences storage file for sp. = the context getSharedPreferences (" name ", the context, MODE_PRIVATE); / / like SharedPreference writes data need to use EditorSharedPreference. The Editor Editor = sp. Edit (); Editor.putstring ("name", "string"); editor.putInt("age", 0); editor.putBoolean("read", true); //editor.apply(); editor.commit();Copy the code
  • Apply and COMMIT are commit saves. The difference is that apply is executed asynchronously, without waiting. No matter delete, modify, add must call apply or commit commit save;

  • About updates: If the inserted key already exists. The original key will be updated;

  • Once the application is uninstalled, SharedPreference is also deleted;

3, read

SharedPreference sp = context. GetSharedPreferences (" name ", the context, MODE_PRIVATE); String name=sp.getString("name", "not yet "); // The first argument is the key name, the second is the default value. int age=sp.getInt("age", 0); boolean read=sp.getBoolean("isRead", false);Copy the code

4, retrieval,

SharedPreferences sp = context. GetSharedPreferences (" name ", the context, MODE_PRIVATE); // Check whether the current key exists Boolean isContains=sp.contains("key"); // Use getAll to return all available keys. > allMaps=sp.getAll();Copy the code

5, delete,

Clear () and commit() before deleting data in SharedPreferences.

SharedPreference SP =getSharedPreferences(" Name ", context.mode_private); SharedPrefence.Editor editor=sp.edit(); editor.clear(); editor.commit();Copy the code
  • GetSharedPreference () doesn’t generate a file, you know that;

  • After the file is deleted, run commit() again. The deleted file is reborn. The data of the reborn file is the same as the data before the deletion.

  • After deleting the file, the content stored in the Preferences object remains the same until the program exits completely and stops running. The file is gone, but the data still exists. Data will not be lost until the program has completely quit and stopped;

  • To clear SharedPreferences data, do editor.clear(), editor.mit (), and not simply delete the file

SharedPreferences source code analysis

1, create,

SharedPreferences preferences = getSharedPreferences("test", Context.MODE_PRIVATE);
Copy the code

The actual implementation class of the context is ContextImp, so go to ContextImp’s getSharedPreferences method:

@Override public SharedPreferences getSharedPreferences(String name, int mode) { ...... File file; Synchronized (contextimpl.class) {if (mSharedPrefsPaths == null) {ArrayMap<String, File> mSharedPrefsPaths; mSharedPrefsPaths = new ArrayMap<>(); } // Whether file is available from mSharedPrefsPaths file = mSharedPrefsPaths. Get (name); If (file == null) {file = getSharedPreferencesPath(name); Place the name, file key-value pair into the collection mSharedPrefsPaths. Put (name, file); } } return getSharedPreferences(file, mode); }Copy the code

ArrayMap<String, File> mSharedPrefsPaths; The SharedPreference object is used to store the SharedPreference file name and the corresponding path. The path is obtained in the following method, which is to obtain the data/data/ package name /shared_prefs/ directory

@Overridepublic File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }private File getPreferencesDir() { synchronized (mSync) { if (mPreferencesDir == null) { mPreferencesDir = new File(getDataDir(), "shared_prefs"); } return ensurePrivateDirExists(mPreferencesDir); }}Copy the code

Path before the object is created

@override public SharedPreferences getSharedPreferences(File File, int mode) {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) {sp = new SharedPreferencesImpl(file, mode); // Add to memory cache.put(file, sp); // return sp; }} / / if set to MODE_MULTI_PROCESS mode, then will perform startReloadIfChangedUnexpectedly method of SP. if ((mode & Context.MODE_MULTI_PROCESS) ! = 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { sp.startReloadIfChangedUnexpectedly(); } return sp; }Copy the code

Is overloaded methods, only into the participation by the name of the File to the File, lock the synchronized to create process, through the method of getSharedPreferencesCacheLocked () to obtain all the package name stored in the system and the corresponding files, This is why each SP file has only one corresponding SharedPreferencesImpl implementation object

Process:

  • Get cache, get data from cache, see if there is sp object, if there is directly return

  • If not, get the data from disk,

  • After the data is retrieved from disk, it is added to memory,

  • Return to sp;

getSharedPreferencesCacheLocked

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;    }
Copy the code
  • GetSharedPreferences (File File, int mode) getSharedPreferences(File File, int mode) CheckMode (mode);

  • Sp = new SharedPreferencesImpl(file, mode);

  • Create an object, and put the created object in the system’s packagePrefs, easy to obtain directly later;

    SharedPreferencesImpl(File file, int mode) { mFile = file; // Save files // Backup files (Dr Files) mBackupFile = makeBackupFile(file); // mMode = mode; // mLoaded = false; // Store key-value pair information in file mMap = null; // Start loading data from disk startLoadFromDisk(); }

  • MFile is the original file; MBackupFile is a backup file with the suffix. Bak.

  • MLoaded indicates whether a modification file is being loaded.

  • MMap used to store data in a file sp, storage time and form of key-value pairs, access time is also obtained by this, every time this is said to use sp, is to write the data into the memory, also is the reason why sp data storage data quickly, so sp documents cannot store large amounts of data, or execution time can easily lead to OOM;

  • [Fixed] mThrowable failed to load file

  • Here’s how to load the data: startLoadFromDisk(); Load data from sp files into mMap

2, startLoadFromDisk ()

private void startLoadFromDisk() { synchronized (mLock) { mLoaded = false; } new Thread(" sharedpreferencesimp-load ") {public void run() {loadFromDisk(); } }.start(); } private void loadFromDisk() {synchronized (mLock) {if (mLoaded) {return; } // Whether the backup file exists, if (mbackupfile.exists ()) {// delete file the original file mfile.delete (); // Name the backup file as XML file mbackupfile.renameto (mFile); }}... Map map = null; StructStat stat = null; Stat = os.stat (mfile.getPath ()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16*1024); map = XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); }} catch (ErrnoException e) {/* ignore */} synchronized (mLock) {mLock = true; // Data is not null if (map! = null) {// Assign map to the mMap object of the global storage file key-value pair mMap = map; MStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } // Key: wake up all wait threads with mLock lock mlock. notifyAll(); }}Copy the code
  • First, check whether the backup file exists. If so, specify the suffix of the backup file. It then reads the data, assigns the read data to the mMap object where the global variables store the file key-value pairs, and updates the modification time and file size variables;

  • Wake up all waiting threads with mLock as their lock.

  • At this point, the initialization of the SP object is complete, in fact, it can be seen as a level 2 cache process: disk to memory;

3, get obtains the key/value pairs in SP

@Nullable public String getString(String key, @nullable String defValue) {synchronized (mLock) {awaitLoadedLocked(); // Wait mechanism String v = (String) map.get (key); Return v! = null ? v : defValue; } } private void awaitLoadedLocked() { ....... while (! Mlock. wait(); // The thread waits for mlock. wait(); } catch (InterruptedException unused) { } } }Copy the code

If the data is not loaded (i.e., mLoaded=false), the thread waits;

PutXXX and Apply source code

Public Editor Edit () {synchronized (mLock) {awaitLoadedLocked(); } // Return EditorImp object return EditorImpl(); } public Editor putBoolean(String key, boolean value) { synchronized (mLock) { mModified.put(key, value); return this; } } public void apply() { final long startTime = System.currentTimeMillis(); // Commit data to memory. Final MemoryCommitResult MCR = commitToMemory(); . / / submit data to disk SharedPreferencesImpl. Enclosing enqueueDiskWrite (MCR, postWriteRunnable); // Note: Listeners are notifyListeners }Copy the code
  • CommitToMemory to commit data to memory. Then commit the data to disk;

  • The listener is then called;

5, 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); } // assign the cache set to mapToWriteToDisk = mMap; . synchronized (mLock) { boolean changesMade = false; If (mClear) {if (! mMap.isEmpty()) { changesMade = true; Mmap.clear (); } mClear = false; } // loop mModified to update mMap 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. if (v == this || v == null) { if (! mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue ! = null && existingValue.equals(v)) { continue; Mmap.put (k, v); }... } // Clear the temporary collection mmodified.clear (); . } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }Copy the code
  • MModified is the set of key-value pairs that we are updating this time;

  • MClear is assigned when we call the clear() method;

  • The general process is as follows: first determine whether memory data needs to be emptied, then loop through the mModified set and add updated data to the set of key-value pairs in memory;

6. Commit method

public boolean commit() { ....... // Update data to memory MemoryCommitResult MCR = commitToMemory(); / / update the data to disk SharedPreferencesImpl. This. EnqueueDiskWrite (MCR, null / * sync write on this thread okay * /); Try {/ / wait: waiting for disk update data to complete the MCR. WrittenToDiskLatch. Await (); } catch (InterruptedException e) { return false; } finally { if (DEBUG) { Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " committed after " + (System.currentTimeMillis() - startTime) + " ms"); } // Listeners are called notifyListeners(MCR); return mcr.writeToDiskResult; }Copy the code
  • Apply returns no value, commit returns no value.

  • The apply callback is executed in parallel with the data write to disk, while the COMMIT callback is executed after the data write is complete.

QueuedWork

1, QueuedWork

QueuedWork is a class that uses QueuedWork when sp is initialized. As we saw earlier, both apply and commit methods are implemented with QueuedWork.

QueuedWork is a management class that, as its name implies, has a queue that manages and schedules all queued works.

The most important of these is having a HandlerThread

private static Handler getHandler() { synchronized (sLock) { if (sHandler == null) { HandlerThread handlerThread = new HandlerThread("queued-work-looper", Process.THREAD_PRIORITY_FOREGROUND); handlerThread.start(); sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } return sHandler; }}Copy the code

Join the queue

// If commit, delay is not allowed. If apply, Delay public static void queue(Runnable work, Boolean shouldDelay) {Handler Handler = getHandler(); synchronized (sLock) { sWork.add(work); If (shouldDelay && sCanDelay) {/ / default is 100 ms delay time handler. SendEmptyMessageDelayed (QueuedWorkHandler MSG_RUN, delay); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); }}}Copy the code

3. Message processing

private static class QueuedWorkHandler extends Handler { static final int MSG_RUN = 1; QueuedWorkHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { if (msg.what == MSG_RUN) { processPendingWork(); } } } private static void processPendingWork() { synchronized (sProcessingWork) { LinkedList<Runnable> work; synchronized (sLock) { work = (LinkedList<Runnable>) sWork.clone(); sWork.clear(); getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } if (work.size() > 0) { for (Runnable w : work) { w.run(); }}}}Copy the code
  • As you can see, scheduling is very simple, with an internal sWork that iterates through all runnable executions when needed;

  • For apply, there is a delay before work is executed, but for commit, scheduling is triggered immediately, not just for the task sent by the COMMIT, but for all works in the queue.

4, waitToFinish

Many parts of the system wait for the SP write file to complete by calling Queuedwork.waittoFinish ();

public static void waitToFinish() { Handler handler = getHandler(); Synchronized (sLock) {// Remove all messages, Work if (handler. HasMessages (QueuedWorkHandler.MSG_RUN)) {handler.removemessages (QueuedWorkHandler. } sCanDelay = false; } StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); Try {// If waitToFinish is called, all work processPendingWork() is executed immediately; } finally { StrictMode.setThreadPolicy(oldPolicy); QueuedWork. AddFinisher (awaitCommit); QueuedWork. AddFinisher (awaitCommit); QueuedWork. While (true) {runnable finisher; while (true) {runnable finisher; while (true) {runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } finisher.run(); } } finally { sCanDelay = true; }... }Copy the code

Queuedwork.waittofinish () calls queuedWork.waittoFinish () and queuedWork.waittoFinish (). Ensure that app data is correctly written to disk;

5. Suggestions on sp use

  • The requirements for real-time data are not high, so try to use Apply

  • If the business requires that data be written successfully, use COMMIT

  • Reduce the frequency of SP operations and try to write all data at one commit

  • It is appropriate to consider not accessing sp on the main thread

  • Data written to sp should be lightweight

Conclusion:

The implementation of SharedPreferences itself is divided into two steps, one is memory, the other is disk, and the main thread depends on the writing of SharedPreferences, so when IO becomes a bottleneck, the App will be slow because of SharedPreferences. ANR will occur in severe cases, which can be summarized as follows:

  • Data stored in XML files is stored in memory, so retrieving data is fast

  • Apply is an asynchronous operation that commits data to memory, not immediately to disk

  • Commit is a synchronization operation that waits for data to be written to disk and returns a result

  • If the same thread commits multiple times, subsequent threads wait for the previous execution to finish

  • If multiple threads commit to the same SP, all subsequent tasks are QueuedWork and wait for the first one to complete

Java thread concurrency AQS principle mechanism comprehensive detailed explanation

Multithreaded AsyncTask using a detailed explanation and in-depth understanding of AsyncTask mechanism from the source code

**Android master advanced multithreaded HandlerThread use and source analysis in detail **

Android source code advanced easy to get FlexboxLayout principle mechanism

Java Advanced: An in-depth understanding of the CAS mechanism for thread concurrency