Search code examples
javaandroidsharedpreferences

Android sharedPreferences.commit unable to read file


I have a Broadcast Receiver that clears the shared preferences file and then commits. My app runs into ANR since the clear works but the commit is not able to read the file for some reason

The broadcast receiver also times out..

I am unable to understand what the stack trace means. Can someone help me understand what happened here? And is there a way to avoid this?

Here is the stack trace :

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x7599a000 self=0xb84a5e98
  | sysTid=15259 nice=0 cgrp=bg_non_interactive sched=0/0 handle=0xb6f1d000
  | state=S schedstat=( 137176743 6798788178 493 ) utm=4 stm=9 core=0 HZ=100
  | stack=0xbe0d0000-0xbe0d2000 stackSize=8MB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/15259/stack)
  native: #00 pc 00012ab0  /system/lib/libc.so (syscall+28)
  native: #01 pc 000a98af  /system/lib/libart.so (_ZN3art17ConditionVariable4WaitEPNS_6ThreadE+82)
  native: #02 pc 001c1529  /system/lib/libart.so (_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh+672)
  native: #03 pc 000139eb  /system/lib/libjavacore.so (???)
  native: #04 pc 00020dfd  /system/lib/libjavacore.so (???)
  native: #05 pc 002859e3  /system/framework/arm/boot.oat (Java_libcore_io_Posix_open__Ljava_lang_String_2II+118)
  at libcore.io.Posix.open(Native method)
  at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
  at libcore.io.IoBridge.open(IoBridge.java:442)
  at java.io.FileOutputStream.<init>(FileOutputStream.java:89)
  at java.io.FileOutputStream.<init>(FileOutputStream.java:74)
  at android.app.SharedPreferencesImpl.createFileOutputStream(SharedPreferencesImpl.java:578)
  at android.app.SharedPreferencesImpl.writeToFile(SharedPreferencesImpl.java:631)
  at android.app.SharedPreferencesImpl.access$900(SharedPreferencesImpl.java:53)
  at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:532)
  - locked <@addr=0x22d600f0> (a java.lang.Object)
  at android.app.SharedPreferencesImpl.enqueueDiskWrite(SharedPreferencesImpl.java:551)
  at android.app.SharedPreferencesImpl.access$100(SharedPreferencesImpl.java:53)
  at android.app.SharedPreferencesImpl$EditorImpl.commit(SharedPreferencesImpl.java:473)

Here is the sample code :

public class AppBroadcastReceiver extends BroadcastReceiver {

   @Override
    public void onReceive(Context context, Intent intent) {

       SharedPreferences mSharedPre = context.getSharedPreferences("sharedPrefFile", Context.MODE_PRIVATE);
       SharedPreferences.Editor sharedPrefEditor = mSharedPre.edit();
       sharedPrefEditor.clear();

       sharedPrefEditor.commit();

    }

}

Solution

  • What is happening here is that you are loading a file on the main thread, which is taking too long and causing the app to ANR.

    Why?

    There are two problems with what you have in your code that could lead to an ANR.

    When you call context.getSharedPreferences() for the first time, Android launches a job that reads the underlying preference file (an XML file) and loads all the data into memory. While this load is occurring, any and all calls to get any data from the SharedPreferences object you have will block until the load job is completed. This includes calls to edit() which you are doing in your onReceive().

    The second problem is with the call to commit(). This performs a synchronous write to the underlying preference file in your SharedPreferences, which blocks the calling thread (in your case, this is again the Main thread). This will also lead to ANRs.

    Possible Solution

    Firstly, I would move the call to load your preferences closer to the start of the app start. One good place is Application.onCreate(). In this method, you should only call getSharedPreferences() ONLY. You don't want to block the main thread here at all. This will give your preferences time to load before onReceive() is called.

    Secondly, don't use commit(). I would recommend using apply() instead. This ensures the data is stored in memory immediately, while writing the data asynchronously to the underlying preference file. There is still a chance for an ANR however, especially because the queue that Android uses to do the write jobs asynchronously flushes onto the main thread when a receiver completes onReceive() (as well as in other cases). I wouldn't worry about this situation all too much, just something to be aware of.

    Finally, I would not recommend using grandcentrix/tray, as it is no longer supported, and could cause other ANRs if used improperly. If your app is a single process app, don't use a multi-process solution.