Share SharedPreferences data through ContentProvider multi-process,

Source: Internet
Author: User

Share SharedPreferences data through ContentProvider multi-process,
Share SharedPreferences data through ContentProvider multi-process

When developing a multi-process application, we often cannot avoid sharing data between multiple processes.
There are many methods for multi-process data sharing, which are commonly used in Android: SharedPreferences (multi-process mode), broadcast, Socket, ContentProvider, Messenger, AIDL, etc. These methods are applicable to different scenarios and have their own limitations.

This article will introduce the function of sharing configuration items between processes through ContentProvider and SharedPreferences (SP. This method is applicable to the following scenarios: You need to perform some settings in one process, but you need to read the settings in another process in real time and execute the functions according to these settings.

1. Limitations of SharedPreferences in multi-process Sharing

Some may find it strange: it is clear that SP (multi-process mode) is also a method for multi-process data sharing. Why do we need to use ContentProvider. The answer is simple,Because SP does not guarantee synchronization between multiple processesIs a comment on MODE_MULTI_PROCESS. I will not translate it in full.

Comments of MODE_MULTI_PROCESS Mode

As we all know, SP stores the key-value pair in the file under the data/you. package. name/shared_prefs/directory of the mobile phone. To reduce the performance loss caused by IO, the SP uses the cache mechanism to store the data in the memory and read the data directly from the memory, it is saved to the file only when it is written. That is to say, a common SP is a read-only, multi-write mode. Therefore, if a common SP is used in multiple processes and the SP is saved separately, the process will overwrite each other. After MODE_MULTI_PROCESS is set, when multiple processes are used, the file will be reloaded to the memory when the file changes are detected. This causes some performance loss,PartMulti-process synchronization is implemented.

Why is multi-process synchronization realized in "part? Because when SP operations are performed frequently, the problem of mutual coverage still occurs. I initially estimated that the two processes performed file write operations at the same time. With this speculation, I read the source code.

Let's take a look at how MODE_MULTI_PROCESS is handled.
The getSharedPreferences method of ContextImpl is as follows:

@Overridepublic SharedPreferences getSharedPreferences(File file, int mode) {    checkMode(mode);    SharedPreferencesImpl sp;    synchronized (ContextImpl.class) {        final ArrayMap
 
   cache = getSharedPreferencesCacheLocked();        sp = cache.get(file);        if (sp == null) {            sp = new SharedPreferencesImpl(file, mode);            cache.put(file, sp);            return sp;        }    }    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {        // If somebody else (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;}
 

The startReloadIfChangedUnexpectedly method of SharedPreferencesImpl is as follows:

    void startReloadIfChangedUnexpectedly() {        synchronized (this) {            // TODO: wait for any pending writes to disk?            if (!hasFileChangedUnexpectedly()) {                return;            }            startLoadFromDisk();        }    }

It can be seen that when MODE_MULTI_PROCESS is encountered, the SP will be forced to perform a read operation to ensure that the data is up-to-date. Therefore, if you save an SP object externally, you will not be able to enjoy the synchronization effect of MODE_MULTI_PROCESS, from the source code, we can see that ContextImpl caches the SP object. Every time you re-getSharedPreferences, it will not cause too much performance loss.
The Code of getSharedPreferences does not show the problem that multiple processes will overwrite each other. Let's look at the Editor's commit method,
Commit of EditorImpl and related methods:

final class SharedPreferencesImpl implements SharedPreferences {    ...    public final class EditorImpl implements Editor {        ...        private MemoryCommitResult commitToMemory() {            ...        }        public boolean commit() {            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;        }    }    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);        // Typical #commit() path with fewer allocations, doing a write on        // the current thread.        if (isFromSyncCommit) {            boolean wasEmpty = false;            synchronized (SharedPreferencesImpl.this) {                wasEmpty = mDiskWritesInFlight == 1;            }            if (wasEmpty) {                writeToDiskRunnable.run();                return;            }        }        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);    }}

It can be seen that each operation is saved to the memory and then written to the file. The file write operations are executed sequentially in the Child thread (the last line of the above Code ).
The conclusion is as follows:

When multiple processes call the commit method at the same time and frequently, the file will be overwritten and written repeatedly, but not read in time, so the data between processes will not be synchronized.

2. implement multi-process sharing SharedPreferences through ContentProvider

Since SP has the hidden danger of multi-process non-synchronization, how can we solve it?
Among the multi-Process Synchronization Methods, ContentProvider, Messenger, and AIDL are implemented based on Binder, so there is no big difference in nature, and ContentProvider is a data delivery component advocated by Android, so I chose it to implement multi-process SP operations.

How can this problem be solved?
The calling method of SP has provided high access convenience. Therefore, we only need to encapsulate a SPHelper to call SPContentProvider. SPContentProvider is used to ensure synchronization across processes, it can be implemented using SPHelperImpl internally.

Encapsulation structure of SP

So how can we use SPContentProvider to implement data access? Several methods are required to implement ContentProvider. These methods correspond to the same name methods in ContentResolver. We can call these methods through ContentResolver to transfer data and perform Command Parsing.


ContentProvider Implementation Method

Methods in SP include sava, get, remove, clean, and getAll. Therefore, we can use update or insert to implement save; Use delete to implement clean and remove, and use getType or query to implement get and getAll.
The final call method is as follows:

SP calls the transfer process of getInt ()

Let's take a look at how the code is handled, or take getInt () as an example:

Public class SPHelper {... public static final String CONTENT = "content: //"; public static final String AUTHORITY = "com. pl. sphelper "; public static final String SEPARATOR ="/"; public static final String CONTENT_URI = CONTENT + AUTHORITY; public static final String TYPE_INT =" int "; public static final String NULL_STRING = "null"; public static int getInt (String name, int defaultValue) {ContentResolver cr = context. getContentResolver (); Uri uri = Uri. parse (CONTENT_URI + SEPARATOR + TYPE_INT + SEPARATOR + name); String rtn = cr. getType (uri); if (rtn = null | rtn. equals (NULL_STRING) {return defaultValue;} return Integer. parseInt (rtn );}...} public class SPContentProvider extends ContentProvider {... public static final String SEPARATOR = "/"; public String getType (Uri uri) {// use this to obtain the value String [] path = uri. getPath (). split (SEPARATOR); String type = path [1]; String key = path [2]; return "" + SPHelperImpl. get (getContext (), key, type );}...} class SPHelperImpl {... public static final String TYPE_INT = "int"; static String get (Context context, String name, String type) {if (type. inclusignorecase (TYPE_STRING) {return getString (context, name, null);} else if (type. inclusignorecase (TYPE_BOOLEAN) {return getBoolean (context, name, false);} else if (type. inclusignorecase (TYPE_INT) {return getInt (context, name, 0);} else if (type. inclusignorecase (TYPE_LONG) {return getLong (context, name, 0L);} else if (type. inclusignorecase (TYPE_FLOAT) {return getFloat (context, name, 0f);} else if (type. inclusignorecase (TYPE_STRING_SET) {return getString (context, name, null);} return null;} static int getInt (Context context, String name, int defaultValue) {SharedPreferences sp = getSP (context); if (sp = null) return defaultValue; return sp. getInt (name, defaultValue );}...}

SPContentProvider must be declared in AndroidManifest and set android: authorities = "com. pl. sphelper ".

3. Optimized Performance and memory

I thought that the above work was finished, but in the actual use process, I found a problem, that is, the memory consumption is relatively large. After tracking, it is found that the SharedPreferences is generated. the Editor object occupies a large amount of memory, because in my application scenarios, several data in the running process are frequently stored in the SP, resulting in a large number of Editor objects. But in fact, many stored values are the same. You can use the cache mechanism instead of writing them repeatedly. The Code is as follows:

private static SoftReference
 
  > sCacheMap;private static Object getCachedValue(String name) {    if (sCacheMap != null) {        Map
  
    map = sCacheMap.get();        if (map != null) {            return map.get(name);        }    }    return null;}private static void setValueToCached(String name, Object value) {    Map
   
     map;    if (sCacheMap == null) {        map = new HashMap<>();        sCacheMap = new SoftReference
    
     >(map);    } else {        map = sCacheMap.get();        if (map == null) {            map = new HashMap<>();            sCacheMap = new SoftReference
     
      >(map);        }    }    map.put(name, value);}synchronized static 
      
        void save(Context context, String name, T t) { SharedPreferences sp = getSP(context); if (sp == null) return; if (t.equals(getCachedValue(name))) { return; } SharedPreferences.Editor editor = sp.edit(); if (t instanceof Boolean) { editor.putBoolean(name, (Boolean) t); } if (t instanceof String) { editor.putString(name, (String) t); } if (t instanceof Integer) { editor.putInt(name, (Integer) t); } if (t instanceof Long) { editor.putLong(name, (Long) t); } if (t instanceof Float) { editor.putFloat(name, (Float) t); } editor.commit(); setValueToCached(name, t);}
      
     
    
   
  
 

The tested memory usage dropped by 80% (this is partly because I had consumed too much memory.

4. How much performance loss does SPHelper cause?

Since it is multi-process interaction, it will certainly cause some performance loss. What is it? I tested it with the following code:

@RunWith(AndroidJUnit4.class)public class ExampleInstrumentedTest {    @Test    public void useAppContext() throws Exception {        Context appContext = InstrumentationRegistry.getTargetContext();        SPHelper.init((Application) appContext.getApplicationContext());        Random random = new Random();        long start = System.currentTimeMillis();        for (int i = 0; i < 100; i++) {            SPHelper.save("key" + random.nextInt(200), i);        }        long end = System.currentTimeMillis();        Log.e("ExampleInstrumentedTest", "SPHelper takes " + (end - start) + "millis");        start = System.currentTimeMillis();        for (int i = 0; i < 100; i++) {            SharedPreferences sp = appContext.getSharedPreferences("text", Context.MODE_PRIVATE);            SharedPreferences.Editor editor = sp.edit();            editor.putInt("key" + random.nextInt(200), i);            editor.commit();        }        end = System.currentTimeMillis();        Log.e("ExampleInstrumentedTest", "SharedPreferences takes " + (end - start) + "millis");    }}

In the code, SPHelper and the system's default SharedPreferences are used for 100 SP storage operations, and the output time (unit: milliseconds) is consumed ).

When the caller and SPContentProviderSameIn the process, the performance is as follows:

Call Method First time Second Third time Fourth Fifth
SPHelper 966 903 944 987 951
SharedPreferences 850 836 904 844 838
Performance gap 0.14 0.08 0.04 0.17 0.13

It can be seen that the performance loss is not big, about 10%. This is because of the working method of the Binder. In the same process of the two, it is only equivalent to function call and will not cause too much consumption. For details, refer to the Binder source code. Therefore, the 10% gap is basically caused by data packaging and packaging.

The following table shows the consumption of inter-process communication.DifferentResults In the process:

Call Method First time Second Third time Fourth Fifth
SPHelper 1374 1283 1366 1286 1296
SharedPreferences 829 850 861 886 869
Performance gap 0.66 0.51 0.59 0.45 0.49

It can be seen that the call can be forwarded through three functions, coupled with inter-process communication, resulting in a considerable performance loss, reaching more than 50%.

Note that if the SPContentProvider process only contains this ContentProvider but does not have other components, it will start the process during the first call, so it will take more than 50 ms, in addition, this process will be automatically consumed after the call, so it is best to put the SPContentProvider in the Process of the most common SP, so as to ensure minimum performance consumption.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.