赞
踩
下面的源码都是基于Android api 31
val sharePref = getPreferences(Context.MODE_PRIVATE)
with(sharePref.edit())
{
putBoolean("isLogin", true)
putInt("age", 18)
apply()
}
val isLogin = sharePref.getBoolean("isLogin", false)
val age = sharePref.getInt("age", -1)
Log.d(TAG, "isLogin $isLogin age $age")
获取sharedpreferences实现类是ContextImpl,下面是ContextImpl中获取sharedpreferencesImpl的源码
public SharedPreferences getSharedPreferences(String name, int mode) { ...... File file; synchronized (ContextImpl.class) { //如果mSharedPrefsPaths为null,先创建一个mSharedPrefsPaths的Map。sharePreferences存放的地址 if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } //获取sharedPrefs的file file = mSharedPrefsPaths.get(name); if (file == null) { //如果file为null,创建file file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } //通过file获取sharepref return getSharedPreferences(file, mode); } //通过名称来获取file public File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }
在contextImpl中是先根据sharedpref的名称来获取对应的File文件,然后通过file来获取sharedprefImpl
public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //Map中存放file-spImpl final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); //先从cache中获取, sp = cache.get(file); if (sp == null) { //创建一个spImpl sp = new SharedPreferencesImpl(file, mode); //放入到cache中 cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { //多进程,会执行reload sp.startReloadIfChangedUnexpectedly(); } return sp; }
由上面代码可知获取sharedPreferencesImpl实例分为两步:
1、通过sp的name来获取存放sp的File。
2、通过file来获取sharedPreferencesImpl实例。
name和file,file和sharedPreferencesImpl在创建过一次后会以key-value的形式保存在map中,方便后面再次获取。所以并不是每次获取spImpl都会去new。
sp在磁盘中是以xml文件的形式存放的,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<defaultMap>
<entry>
<key>key</key>
<value>true</value>
</entry> <entry>
<key>key</key>
<value>Hello World!</value>
</entry>
<entry>
<key>key</key>
<value>5</value>
</entry>
</defaultMap>
SharedPreference本身是一个接口实现类是SharedPreferencesImpl。SharedPreferencesImpl构造方法如下:
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
SharedPreferencesImpl的构建方法中会先通过传参中的file,创建一个mBackupFile,并且开始从磁盘中获取数据。
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
startLoadFromDisk()中会创建一个名称为SharedPreferencesImpl-load的线程来加载磁盘中的数据。并将加载后的数据以key-value的形式放在mMap中。
写入数据
public final class EditorImpl implements Editor { private final Object mEditorLock = new Object(); @GuardedBy("mEditorLock") private final Map<String, Object> mModified = new HashMap<>(); @GuardedBy("mEditorLock") private boolean mClear = false; @Override public Editor putString(String key, @Nullable String value) { synchronized (mEditorLock) { mModified.put(key, value); return this; } } }
写入数据时并不是直接往磁盘中写,如上的putString,会先将数据方法到mModified的map中,然后在commit或者apply的时候再进行写磁盘操作。
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; }
在commit方法中会创建一个mcr对象,然后执行enqueueDiskWrite,最后返回mcr.writeToDiskResult。
private MemoryCommitResult commitToMemory() { long memoryStateGeneration; boolean keysCleared = false; List<String> keysModified = null; Set<OnSharedPreferenceChangeListener> listeners = null; Map<String, Object> mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { if (mDiskWritesInFlight > 0) { //先判断是不是有多个写磁盘的操作,如果有多个写磁盘的操作,先copy mMap的对象。 mMap = new HashMap<String, Object>(mMap); } //把map的对象赋值给mapToWriteDisk, mapToWriteToDisk = mMap; mDiskWritesInFlight++;//mDiskWritesInFlight加1 synchronized (mEditorLock) { boolean changesMade = false; //判断是否清除sp中的数据 if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); //如果清除sp中的数据将mapToWirteToDisk的数据清空。 } keysCleared = true; mClear = false; } //遍历mModified map for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // //当v==this,或者v为null时,也就是执行了remove()方法的 if (v == this || v == null) { if (!mapToWriteToDisk.containsKey(k)) { //如果mapToWriteToDisk中不包含这个k,继续循环 continue; } //否则从mapToWriteToDisk中移除这个k-v mapToWriteToDisk.remove(k); } else { if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } //如果mapTopWriteToDisk中的key-value和mModified中的相同不操作 } //把k-v方法哦mapToWriteToDisk中 mapToWriteToDisk.put(k, v); } changesMade = true; } //清除mModified中的数据 mModified.clear(); if (changesMade) { //Generation加1 mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified, listeners, mapToWriteToDisk); }
对应的流程图如下
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) { //减1 mDiskWritesInFlight--; } if (postWriteRunnable != null) { //运行postWriteRunnable postWriteRunnable.run(); } } }; if (isFromSyncCommit) { //如果通过commit提交的直接执行writeToDiskRunnable boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } //先进入队列 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }
由上面可知mDiskWritesInFlight>1,或者是通过sp.apply的方法调用的,会执行到queue方法中
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);//添加到work中
//shouldDelay和sCanDelay为true时Handler发一个Delayed消息,否则发送一个非延迟消息
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
上面的handler是通过HandlerThread创建的sHandler,源码如下
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
由上面流程可以知道,只有执行commit操作并且mDiskWritesInFlight == 1时才会在主线程中执行,卡住主线程,当mDiskWritesInFlight > 1或者apply的时候不是在主线程中的。
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();
}
}
}
public void apply() { final long startTime = System.currentTimeMillis(); //创建mcr对象 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { try { ///创建awaitCommit,用于等待写磁盘完成后调用 mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //添加awaitCommit QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { //执行awaitCommit awaitCommit.run(); ///移除awaitCommit QueuedWork.removeFinisher(awaitCommit); } }; //进入队列 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); }
apply方法,首先创建了一个 awaitCommit 的 Runnable,然后加入到 QueuedWork 的 sPendingWorkFinishers 队列中,awaitCommit 中包含了一个等待锁,需要在真正对 SP 进行持久化的时候进行释放。
先要了解QueuedWork.waitToFinish() 方法,QueuedWork.waitToFinish() 会等待所有的addFinisher的runnable执行完成后才会进行执行。在ActivityThread中下面的方法都会执行QueuedWork.waitToFinish()方法。
上面的问题转换成了为什么ActivityThread中上面的方法为什么都要等待sharePref的apply方法执行完写磁盘后才能被调用。
应用可能被系统回收、被用户杀死,会Crash,这些都是不确定的。apply 提交的任务,是先放到队列中去依次执行,而不是立即执行,意味着可能该任务还没被执行,app就被杀死。所以为了尽可能保证数据能被持久化,就要找到一些重要的时机去卡住,来保证完成写入。
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
获取数据时先要等数据从磁盘中加载完成,所以会先执行awaitLoadedLocked();方法,这里面的会通过mLoaded判断数据是否已经从磁盘中加载完成,如果没有加载完成则会等待。
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
val sharePref = getPreferences(Context.MODE_PRIVATE)
with(sharePref.edit())
{
putBoolean("isLogin", true)
putInt("age", 18)
val age = sharePref.getInt("age", -1)
Log.d(TAG, "----before apply--- age $age")
apply()
val age1 = sharePref.getInt("age", -1)
Log.d(TAG, "----after apply--- age $age1")
}
打印的log如下,在执行apply之前获取到的age是默认值-1,执行apply后获取的值是set的值18.
----before apply--- age -1
----after apply--- age 18
在使用SharedPreference 时,有如下一些模式: MODE_PRIVATE
私有模式,这是最常见的模式,一般情况下都使用该模式。 MODE_WORLD_READABLE
,MODE_WORLD_WRITEABLE
,文件开放读写权限,不安全,已经被废弃了,google建议使用FileProvider
共享文件。 MODE_MULTI_PROCESS
,跨进程模式,如果项目有多个进程使用同一个Preference,需要使用该模式,但是也已经废弃了。使用contentProvider可以实现sp的跨进程,
参考:https://www.jianshu.com/p/875d13458538
使用sp的项目中经常会出现下面的ANR
"main" prio=5 tid=1 Waiting | group="main" sCount=1 dsCount=0 obj=0x73f50268 self=0xefc05400 | sysTid=13726 nice=0 cgrp=default sched=0/0 handle=0xf30cf534 | state=S schedstat=( 1450815451 10991027618 7548 ) utm=40 stm=105 core=0 HZ=100 | stack=0xff1f4000-0xff1f6000 stackSize=8MB | held mutexes= at java.lang.Object.wait!(Object.java) - waiting on <0x04386712> (a java.lang.Object) at java.lang.Thread.parkFor$(Thread.java:2127) - locked <0x04386712> (a java.lang.Object) at sun.misc.Unsafe.park(Unsafe.java:325) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:161) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:840) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:994) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1303) at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:203) at android.app.SharedPreferencesImpl$EditorImpl$ 1. run(SharedPreferencesImpl.java: 366 ) at android.app.QueuedWork.waitToFinish(QueuedWork.java:88) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3400) at android.app.ActivityThread.-wrap21(ActivityThread.java:-1) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1632) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:203) at android.app.ActivityThread.main(ActivityThread.java:6251) at java.lang.reflect.Method.invoke!(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
由上面日志可以看出main线程在等待锁导致的ANR,具体原因是ActivityThread.handleServiceArgs,时执行QueueWork.waitToFinish。ActivityThread中handleServiceArgs的代码如下:
private void handleServiceArgs(ServiceArgsData data) { Service s = mServices.get(data.token); if (s != null) { try { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); data.args.prepareToEnterProcess(); } int res; if (!data.taskRemoved) { res = s.onStartCommand(data.args, data.flags, data.startId); } else { s.onTaskRemoved(data.args); res = Service.START_TASK_REMOVED_COMPLETE; } //QueuedWork.waitToFinish QueuedWork.waitToFinish(); try { ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_START, data.startId, res); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } catch (Exception e) { ...... } } }
如上代码在handleServiceArgs中执行了QueueWork.waitFinish(),waitFinish()的源码如下:
public static void waitToFinish() { ...... try { //循环获取sFinishers队列中的任务,当任务为null时退出 while (true) { Runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } //执行任务 finisher.run(); } } finally { sCanDelay = true; } ...... }
在handleServiceArgs时执行QueueWork.waitFinish(),会等待QueueWork中的任务执行完成。在SharedPreferencesImpl的apply方法中如下:
public void apply() { final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //添加await QueuedWork.addFinisher(awaitCommit); //写磁盘后再调用 Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; //添加到队列 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); }
postWriteRunnable执行时会调用awaitCommit.run();这里会执行mcr.writtenToDiskLatch.await();等待写磁盘完成,再调用,所以QueuedWork.removeFinisher(awaitCommit);是在写磁盘完成后移除的。
ActivityThread 中,下面的方法中都调用了QueuedWork.waitToFinish() :
以上四处都存在着 SP 的等待锁问题所导致的 ANR 风险。
由于sPendingWorkFinishers是QueuedWork的静态集合对象,而且ConcurrentLinkedQueue这个集合类是可以继承的,所以可以直接重新定义一个集合类继承自ConcurrentLinkedQueue,覆盖ConcurrentLinkedQueue集合在QueuedWork中暴露出来的接口,将poll接口的返回值改为固定返回null,用这个自定义的集合动态代理之前的集合,这个时候ActivityThread的H在处理消息的时候就再也不用执行等待行为了。
如下方式hook住QueueWork替换sPendingWorkFinishers为ConcurrentLinkedQueueProxy
public static void replaceQueueWorkPendingWorkFinishers() { Log.d(TAG, "start hook, time stamp = " + System.currentTimeMillis()); ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = null; Field field = null; try { Class<?> atClass = Class.forName("android.app.QueuedWork"); field = atClass.getDeclaredField("sPendingWorkFinishers"); field.setAccessible(true); sPendingWorkFinishers = (ConcurrentLinkedQueue<Runnable>) field.get(null); if (sPendingWorkFinishers != null) { field.set(null, new ConcurrentLinkedQueueProxy<Runnable>(sPendingWorkFinishers)); Log.d(TAG, "Below android 0,replaceQueueWorkPendingWorkFinishers success."); } } catch (Exception e) { // 出现异常 try { if (sPendingWorkFinishers != null && field != null) { field.set(null, sPendingWorkFinishers); } } catch (Exception ex) { //ignore } Log.e(TAG, "Below android 0,,hook sPendingWorkFinishers fail.", e); } Log.d(TAG, "end hook, time stamp = " + System.currentTimeMillis()); }
MMKV
|len|key|len|value||len|key|len|value||len|key|len|value|...
文件中key value紧密排布。另有一个crc文件。
MMKV写策略是,增量kv对象序列化后,直接append到内存末尾。这样同一个key会有新旧若干份数据,最新的数据在最后。
不断 append 的话,文件大小会增长得不可控。例如同一个 key 不断更新的话,是可能耗尽几百 M 甚至上 G 空间,而事实上整个 kv 文件就这一个 key,不到 1k 空间就存得下。这明显是不可取的。因此MMKV在性能和空间上做了折中:以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。
而这个重写策略,就是直接将内存中的hash表取出kv逐个写入文件,直接丢弃原先的文件内容。
存在问题:
链接:https://github.com/Tencent/MMKV
1、https://stackoverflow.com/questions/45358761/how-to-save-variable-value-even-if-app-is-destroyed
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。