SharedPreferences ANR 总结
一.简介
SharedPreferences 是 Android 中一种轻量级的数据存储方式,数据以键值对,文件以 xml 的形式存储在 /data/data/<package name>/shared_prefs目录下(在真机上需要 root 权限才能查看)
在源码中 SharedPreferences 是一个接口,具体的实现是
SharedPreferencesImpl
/**
* Interface for accessing and modifying preference data returned by {@link
* Context#getSharedPreferences}. For any particular set of preferences,
* there is a single instance of this class that all clients share.
* Modifications to the preferences must go through an {@link Editor} object
* to ensure the preference values remain in a consistent state and control
* when they are committed to storage. Objects that are returned from the
* various <code>get</code> methods must be treated as immutable by the application.
*
* <p><em>Note: This class does not support use across multiple processes.</em>
*
* ...
*/
public interface SharedPreferences {
...
}
final class SharedPreferencesImpl implements SharedPreferences {
...
}
在注释中对 SharedPreferences 以下称(SP)解释如下:
- 对于任何特定的 SP ,所有客户端共享此类的单个实例(也就是应该使用单例模式)。对 SP 数据的修改必须通过一个 SharedPreferences.Editor 对象来确保 SP 数据保持一致状态,并在它们提交存储时进行控制。从各种get 方法返回的对象必须被应用程序视为不可变的。
注意:此类提供强大的一致性保证。它使用昂贵的操作可能会减慢应用程序的速度。经常改变可以容忍损失的属性或属性应该使用其他机制。有关详细信息读取上的评论 SharedPreferences.Editor.commit() 和SharedPreferences.Editor.apply()。
(换句话说就是 commit 和 apply 用于对数据进行保存,为了保证一致性这个过程可能会减慢应用程序的速度,如果对一致性要求不高则可以使用其他数据存储机制。)
==注意:此类不支持跨多个进程使用。==
二.获取 Sp
获取一个 Sp 有三种方式
- 在一个 Activity 中调用 getPreferences(int mode)
- 使用 PreferenceManager.getDefaultSharedPreferences(Context context)
- context.getSharedPreferences(String name, int mode)
第一种方式和第二种方式最后都会使用 第三种方式,不同的使用的名字不同,模式现在都是为 MODE_PRIVATE 其他的都已经废弃不使用
//第一种
//Activity
//
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
//第二种
//PreferenceManager
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
/**
* Returns the name used for storing default shared preferences.
*
* @see #getDefaultSharedPreferences(Context)
* @see Context#getSharedPreferencesPath(String)
*/
public static String getDefaultSharedPreferencesName(Context context) {
return context.getPackageName() + "_preferences";
}
private static int getDefaultSharedPreferencesMode() {
return Context.MODE_PRIVATE;
}
下面到 context 的具体实现类 contextImpl 中
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
// 允许名字为 null, 即文件的名字为 null.xml
if (mLoadedApk.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
//mSharedPrefsPaths 的数据结构为 ArrayMap<String, File>
//用于保存名字 和对应的文件
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
//如果没有这个文件就创建这个文件
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
//使用 synchronized 进行线程安全的保证
synchronized (ContextImpl.class) {
//一个 SP xml 文件对应 一个 SharedPreferencesImpl 实例
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
...
//创建 SharedPreferencesImpl
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
....
return sp;
}
// 获取用于缓存 SharedPreferencesImpl 的 packagePrefs, 一个包名对应一个
// packagePrefs
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;
}
下面就到 SharedPreferencesImpl.java 中 查看创建一个 SharedPreferencesImpl 的过程。
//SharedPreferencesImpl 的构造器
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);//创建备份文件,用于 xml 写失败时进行恢复
mMode = mode;
mLoaded = false;//判断从磁盘加载到内存的标志
mMap = null;
mThrowable = null;
startLoadFromDisk();//创建 SharedPreferencesImpl 的时候就将 xml 文件中的数据从磁盘加载到内存
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//开启一个线程进行加载
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) {
//如果已经加载完成就返回
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>)
//从 xml 文件中加载数据到 map
//键值对为 String 和 Object
XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
// 设置加载完成
mLoaded = true;
mThrowable = thrown;
//对出现异常情况进行处理
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
//
} catch (Throwable t) {
mThrowable = t;
} finally {
//注意这一句,唤醒其他等待的线程
mLock.notifyAll();
}
}
}
获取 SharedPreferences 总结:
- 获取 SP 的过程是通过 synchronized 关键字保证多线程安全的。
- 通过 Map 进行缓存 Sp 实例,因此多次调用 getSharedPreferences 几乎没有性能上的差别。
- 获取 Sp 的时候就会通过一个线程将 xml 数据从磁盘加载到内存中。这个过程会加锁,加载完成后会设置 mLoaded 标志,并唤醒其他线程。
三.get 方法
Sp 支持的数据类型为 int , long , float, boolean ,String 和 Set<String> .
下面以 getString 为例
//SharedPreferencesImpl.java
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
//进行线程安全保证
synchronized (mLock) {
//等待加载完成后才能读
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@GuardedBy("mLock")
private void awaitLoadedLocked() {
//如果没有加载就开启一个线程进行加载
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
//循环判断
while (!mLoaded) {
try {
//在上面的分析知道只有加载后 mLoaded 就设置 为 true
//并调用 notifyAll 唤醒其他线程
//所以这个时候从 wait 进行返回
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
getXXX 方法 总结:
- 通过 synchronized 进行线程安全保证
- 在主线程进行获取,但是需要等加载的完成后才能进行读,所以get 方法可能造成主线程阻塞,从而导致 ANR 。
- 加载完成后读的过程只涉及内存的读。
四.putXXX 和 apply/commit
提交数据的时候首先要获取 Editor 对象
@Override
public Editor edit() {
//这里也是需要等待加载 xml 文件到内存完成后
//才能创建 EditorImpl
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
以 putString 为例
@Override
public Editor putString(String key, @Nullable String value) {
//使用 synchronized 进行线程安全保证
synchronized (mEditorLock) {
//将数据暂时保存到 mModified 这个 Map 中
mModified.put(key, value);
return this;
}
}
1.apply
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
//将修改先写入内存
final MemoryCommitResult mcr = commitToMemory();
//先看 commitToMemory 方法
private MemoryCommitResult commitToMemory() {
...
//将 mModified 的中暂存的数据写入内存
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 (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
....
//返回一个 MemoryCommitResult 对象。
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
//MemoryCommitResult 的实现
private static class MemoryCommitResult {
final long memoryStateGeneration;
@Nullable final List<String> keysModified;
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
final Map<String, Object> mapToWriteToDisk;
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);//注意这里 有一个 CountDownLatch 设置为 1,说明只要有一个线程调用了 countDown 就可以从 writtenToDiskLatch.await(); 返回
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
Map<String, Object> mapToWriteToDisk) {
this.memoryStateGeneration = memoryStateGeneration;
this.keysModified = keysModified;
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
//调用这个就会从 执行 countDown ,相应的 writtenToDiskLatch.await() 就能返回
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
回到 apply 方法中
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
//将修改先写入内存
final MemoryCommitResult mcr = commitToMemory();
//这里只是创建了一个 Runnable ,并不是一个线程
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//注意这里会进行等待也就是 需要 MemoryCommitResult 的 setDiskWriteResult 方法执行后
//才能返回
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
//添加到队列中
QueuedWork.addFinisher(awaitCommit);
//这里也只创建一个 Runnable
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
//这里执行了上面的 awaitCommit 的 run 方法
//不是 start
//并将队列中的 awaitCommit 移除
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//添加到队列中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
}
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
//创建一个 Runnable ,同样也没有 start
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//如果是 commit 则执行这里并返回
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//如果是 apply 就执行这里
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
在 QueuedWork.java 中
public static void queue(Runnable work, boolean shouldDelay) {
//getHandler 获取的是一个 handlerThread 的hanlder ,也就是一个子线程
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
//发送一个消息 MSG_RUN 到 handler 所在线程,也就是 handlerThread 子线程中去
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
//创建一个 handlerThread ,并执行 start 方法
//这就是 apply 写到磁盘的线程
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
// Handler 的处理
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() {
long startTime = 0;
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
//复制前面的工作队列
work = (LinkedList<Runnable>) sWork.clone();
sWork.clear();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
//一个一个执行 run 方法,
if (work.size() > 0) {
for (Runnable w : work) {
w.run();
}
}
}
}
}
image.png
QueuedWork.addFinisher(awaitCommit);
在上面的方法中注意到 对每个 apply 都会创建一个相应的 awaitCommit,并添加到 QueuedWork 的一个队列中,但是在 QueuedWork 注意到有这样一个方法 waitToFinish
/**
* Trigger queued work to be processed immediately. The queued work is processed on a separate
* thread asynchronous. While doing that run and process all finishers on this thread. The
* finishers can be implemented in a way to check weather the queued work is finished.
*
* Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
* after Service command handling, etc. (so async work is never lost)
*/
public static void waitToFinish() {}
这个方法会在 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理后被调用,并且调用这个方法的目的是为了确保异步任务被及时完成。
image.png
可以看到 waitToFinish 都是在 ActivityThread 中,也就是主线程调用的
public static void waitToFinish() {
boolean hadMessages = false;
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
//这个方法就会执行 所有的 Runnable 的run 返回
//这个时候 processPendingWork 是执行在主线程中
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
...
}
这种设计是为了保证 SP 可靠的、保证写入完成的存储机制。
apply 总结
- apply 没有返回值
- apply 是在主线程将修改数据提交到内存, 然后再子线程(HandleThread)提交到磁盘
- apply 会将 Runnble 添加到 QueueWork 中,如果主线程 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理,就会在主线程执行 提交到硬盘的方法,这个过程就会造成主线程 ANR
2.commit
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//这个也是先写入内存
MemoryCommitResult mcr = commitToMemory();
//这里传入 null 即 postWriteRunnable 为 null
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
// 为 true
// 直接在主线程中执行 run 方法。
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
image.png
commit 总结
- commit 有返回值
- commit 是在主线程将修改数据提交到内存, 然后再在主线程提交到磁盘
- 用 commit 方法最保险。如果担心在主线程调用 commit 方法会出现 ANR,可以将所有的 commit 任务放到单线程池的线程里去执行。
总结
- Sp 主线程 getXX 方法会 ANR
- Sp apply 方法会 ANR
- Sp 主线程调用 commit 方法 ANR