Android SharedPreferences
这个可能是我们接触android用到的第一个用来存储类似键值对数据的工具。经典的不能在经典了。 这里就不讨论她的使用了。毕竟例子太多了。主要说下,被时代所抛弃的原因。
ANR问题!!!!!!
主要来说引起ANR的问题有三个地方:
- SP getValue时
- SP commit提交数据时
- SP apply后,生命周期方法调用时
一、getValue引起的ANR
首先调用SP方法时调用到了contextImpl类中最终调用到了SharedPreferencesImpl。 从构造函数我们可以看出,代码开了线程从文件中读取xml转换成map对象。这本身就是一个耗时操作。
//构造函数
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//从文件加载
startLoadFromDisk();
}
@UnsupportedAppUsage
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//开启新线程执行
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
此时,当我们进行getValue操作。我们看下代码
@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) {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
//wait等待。直到调用notify,才会唤醒。
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
所以在我们进行取值的时候,首先要对file文件加载、执行,转换成map对象,然后调用notify唤醒其他等待读取value的线程。
当存储的数据特别大,例如是一个json数据的时候。 那耗时就会非常严重,我们在主线程获取sp的值,可能就ANR了。
二、commit提交数据引起的ANR
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
MemoryCommitResult mcr = commitToMemory();
//进行一个写文件的任务
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);
//构建了一个runnable任务
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//是否是同步提交
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
//当wasEmpty为true,执行方法
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//将任务添加到了队列中,通过handler发送消息调用
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
看了commit方法,可以知道commit方法中的操作时在调用线程执行的,假如我们在主线程调用的话,可能就会引起ANR
三、Apply引起的ANR
今日头条技术团队,有一篇文章对此进行了分析,再此引用一下图片。
总的来说,看代码。
@Override
public void handleStopActivity(IBinder token, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
//调用方法
QueuedWork.waitToFinish();
}
}
public static void waitToFinish() {
//等待任务执行完成
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
synchronized (sLock) {
long waitTime = System.currentTimeMillis() - startTime;
if (waitTime > 0 || hadMessages) {
mWaitTimes.add(Long.valueOf(waitTime).intValue());
mNumWaits++;
if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
mWaitTimes.log(LOG_TAG, "waited: ");
}
}
}
}
当我们的生命周期stop的时候,调用waittofish。去等待执行完毕。等待什么? 等待我们之前QueueWork.queue提交的任务。就是addFinisher添加的Runnable,如果任务都已经完成了,那么就大家都开心。如果任务没有完成,那么就会阻塞 这个方法实在activity、service生命周期的主线程里调用的。所以也会间接的导致ANR。
one more thing
SP还有其他的缺点:
- 比如占用内存,加载后的SP对象,不会进行数据的移除或者释放操作
- 比如不支持局部更新,每次都会去全局覆盖更新。比如十个属性,我只想修改一个,但却十个都要覆盖。
- 比如不支持多进程,SP中的mode是指用来做数据同步的。类似于votile机制。而且会导致数据丢失和覆盖的问题。
本篇基本上说了SP的一些缺点,当然,我们是可以通过做缓存,hook的方式来弥补这些问题。
但是这里就不多叙述了,个人觉得没必要,有兴趣的可以搜索下,有了更好的工具我们为什么不去用呢。例如mmkv,google的Datastore。
SP就像前女友,总该是要被遗忘的,也许是时候了。