我有一个广播接收器,它清除共享的首选项文件,然后提交。我的应用程序运行到ANR,因为清除工作,但由于某种原因,提交无法读取文件
广播接收器也超时。。
我无法理解堆栈跟踪的含义。有人能帮我了解这里发生了什么吗?有没有办法避免这种情况?
这是堆栈跟踪:
"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)
这是示例代码:
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();
}
}
这里发生的情况是,您正在主线程上加载一个文件,这花费了太长时间,并导致应用程序ANR。
为什么
代码中存在两个问题,可能会导致ANR。
当你第一次调用context.getSharedPreferences()
时,Android会启动一个作业,读取底层的首选项文件(一个XML文件(并将所有数据加载到内存中。在进行此加载时,从SharedPreferences
对象获取任何数据的所有调用都将被阻止,直到加载作业完成。这包括您在onReceive()
中进行的对edit()
的调用。
第二个问题是对commit()
的调用。这将对SharedPreferences
中的底层首选项文件执行同步写入,从而阻塞调用线程(在您的情况下,这也是主线程(。这也将导致ANR。
可能的解决方案
首先,我会将调用移动到更接近应用程序启动的位置,以加载您的偏好。一个好地方是Application.onCreate()
。在此方法中,您应该只调用getSharedPreferences()
only。你根本不想在这里阻塞主线程。这将在调用onReceive()
之前为您的首选项提供加载时间。
其次,不要使用commit()
。我建议使用apply()
。这样可以确保数据立即存储在内存中,同时将数据异步写入底层首选项文件。然而,ANR仍然有机会,特别是因为当接收器完成onReceive()
时(以及在其他情况下(,Android用于异步执行写入作业的队列会刷新到主线程上。我不会太担心这种情况,只是需要注意一些。
最后,我不建议使用grandcentrx/tray,因为它不再受支持,如果使用不当,可能会导致其他ANR。如果您的应用程序是单进程应用程序,请不要使用多进程解决方案。
每个接收程序总是在一个新进程中启动,但不同进程之间不支持共享首选项。因此,如果是your app < API Level 23
,则可以使用标志Context.MODE_MULTI_PROCESS
:
context.getSharedPreferences("mypreferences", Context.MODE_PRIVATE |Context.MODE_MULTI_PROCESS);
注意:由于API级别23是不推荐使用的标志Context.MODE_MULTI_PROCESS
,因此应使用ContentProvider在进程之间共享属性。此库https://github.com/grandcentrix/tray可以提供帮助。基本上,它是一个具有一些附加功能的ContentProvider包装器。