SharedPreferences is a means of data persistence provided by Android. It is suitable for single-process, small-batch data storage and access. Why do you say that? Because SharedPreferences are implemented based on a single XML file, and all persistent data is loaded into memory at one time, if the data is too large, it is not suitable to store SharedPreferences. Android native file access does not support mutual exclusion of multiple processes, so SharePreferences does not support mutual exclusion. If multiple processes update the same XML file, mutual exclusion may occur. We will analyze these problems in detail later.

The implementation principle of SharedPreferences is: persistent data loading

First, the implementation principle of SharedPreferences is as follows:

    mSharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(key, value);
    editor.apply();Copy the code

Context. GetSharedPreferences is simple call ContextImpl getSharedPreferences, concrete implementation is as follows

       @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        if (sSharedPrefs == null) {
            sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
        }

        final String packageName = getPackageName();
        ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
            sSharedPrefs.put(packageName, packagePrefs);
        }
        sp = packagePrefs.get(name);
        if(sp == null) { <! --> File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); <! --> packageprefs. put(name, sp);returnsp; }} <! -- Cross-process synchronization problem -->if((mode & Context.MODE_MULTI_PROCESS) ! = 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { sp.startReloadIfChangedUnexpectedly(); }return sp;
}Copy the code

If the SharePreferences corresponding to XML have been created and loaded, then create and load it. After loading, save all key-values to the inside file. Of course, if the first access is made, The XML file corresponding to SharePreferences is usually located in the /data/data/ package name /shared_prefs directory with the suffix of. XML. The data storage style is as follows

Sp corresponding XML data storage model

Is SharePreferences data loaded synchronously or asynchronously? Data loading starts with the new SharedPreferencesImpl object,

 SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    startLoadFromDisk();
}Copy the code

StartLoadFromDisk simply reads the XML configuration, and if other threads try to use it before reading, they block and wait until the data is read.

    private void loadFromDiskLocked() {... Map map = null; StructStatstat = null;
    try {
        stat = Os.stat(mFile.getPath());
        if(mFile.canRead()) { BufferedInputStream str = null; try { <! STR = new FileInputStream(mFile), 16*1024; map = XmlUtils.readMapXml(str); }... mLoaded =true; . <! Call up other wait threads --> notifyAll(); }Copy the code

Therefore, if the XML file is a little too large, try not to read it in the main thread. After reading, the configuration items in the XML will be loaded into memory. When accessing it again, it will actually access the memory cache.

The implementation principle of SharedPreferences is: persistent data update

To update SharedPreferences, you first get a SharedPreferences.Editor, which caches a batch of operations, and then commits them as transactions, similar to a batch update of a database:

SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putString(key1, value1); editor.putString(key2, value2); editor.putString(key3, value3); editor.apply(); / / or commitCopy the code

Editor is an interface, and the implementation here is an EditorImpl object, which preprocesses the update operation in bulk first, and then commits the update. There are two methods for committing transactions: Apply and commit. When to persist data to XML files, the former is asynchronous and the latter is synchronous. Google recommends using the former method because, for a single process, the correct memory cache is enough to ensure that the runtime data is correct, while persistence, not too timely, is still quite common on Android, as is updating permissions. Google doesn’t want SharePreferences to be used for multiple processes because it’s not secure, so you can make the difference between Apply and COMMIT

    public void apply() {<! -- add to memory --> Final MemoryCommitResult MCR = commitToMemory(); final Runnable awaitCommit = newRunnable() {
                public void run() {
                    try {
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
                }
            };

        QueuedWork.add(awaitCommit);
        Runnable postWriteRunnable = new Runnable() {
                public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); }}; <! - the delay written to an XML file -- > SharedPreferencesImpl. Enclosing enqueueDiskWrite (MCR, postWriteRunnable); <! -- Notice of data changes -- Notice of data changes } public booleancommit() {
        MemoryCommitResult mcr = commitToMemory();
        SharedPreferencesImpl.this.enqueueDiskWrite(
            mcr, null /* sync write on this thread okay */);
        try {
            mcr.writtenToDiskLatch.await();
        } catch (InterruptedException e) {
            return false;
        }
        notifyListeners(mcr);
        return mcr.writeToDiskResult;
    }     Copy the code

CommitToMemory is the same as commitToMemory, and enqueueDiskWrite is the same as enqueueDiskWrite. Commit usually writes to the file on the current thread. Apply commits a transaction to the assigned thread pool and returns it directly.

 private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr);
                }
                synchronized (SharedPreferencesImpl.this) {
                    mDiskWritesInFlight--;
                }
                if(postWriteRunnable ! = null) { postWriteRunnable.run(); }}}; final boolean isFromSyncCommit = (postWriteRunnable == null);if (isFromSyncCommit) {
        boolean wasEmpty = false; synchronized (SharedPreferencesImpl.this) { wasEmpty = mDiskWritesInFlight == 1; } <! If no other thread is writing to the file, execute the file directly on the current thread.if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
   QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}Copy the code

If a thread is writing to a file, the file cannot be written directly, which is the same as apply. However, if the difference between the two methods is straightforward, commit should be said to be synchronous, while apply should be asynchronous.

SharePreferences multi-process usage problem

MODE_MULTI_PROCESS (MODE_MULTI_PROCESS, MODE_MULTI_PROCESS, MODE_MULTI_PROCESS, MODE_MULTI_PROCESS) It will only be reloaded from XML when getSharedPreferences is used. If we update XML in one process without notifying the other process, the SharePreferences in the other process will not be automatically updated.

@Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; .if((mode & Context.MODE_MULTI_PROCESS) ! = 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebodyelse (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;
}Copy the code

In other words, MODE_MULTI_PROCESS is a weak Flag, with almost zero support for multiple processes. Here’s Google Docs, which in short, is: Don’t use it.

MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, They should use an explicit cross-process data management approach such as ContentProvider.

Google responded by providing a mutual-exclusion solution for data synchronization for multiple processes, a ContentProvider implemented with Binder.

conclusion

  • SharePreferences is an XML-based approach to data persistence implemented by Android
  • SharePreferences does not support multiple processes
  • Commit and Apply in SharePreferences are synchronous and asynchronous (in most scenarios)
  • Don’t use SharePreferences to store too much data

SharePreference principle and cross-process data sharing problem for reference only, welcome to correct