Android轻量级数据存储
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesUtil {
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;
public SharedPreferencesUtil(Context context, String fileName) {
this.sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
this.editor = sharedPreferences.edit();
}
public void putString(String key, String value) {
editor.putString(key, value);
editor.apply(); //异步提交
}
public String getString(String key, String defaultValue) {
return sharedPreferences.getString(key, defaultValue);
}
public void putInt(String key, int value) {
editor.putInt(key, value);
editor.apply();
}
public int getInt(String key, int defaultValue) {
return sharedPreferences.getInt(key, defaultValue);
}
public void putBoolean(String key, boolean value) {
editor.putBoolean(key, value);
editor.apply();
}
public boolean getBoolean(String key, boolean defaultValue) {
return sharedPreferences.getBoolean(key, defaultValue);
}
public void remove(String key) {
editor.remove(key);
editor.apply();
}
public void clear() {
editor.clear();
editor.apply();
}
}
一个键值对保存的xml文件,存储在data/data/package/shared_prefs文件夹下
获取Sp的源码:
public SharedPreferences getSharedPreferences(String name, int mode) {
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
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 (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache =
getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
SharedPreferences是个接口,其实现类是SharedPreferencesImpl,其构造方法如下:
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
在startLoadFromDisk()中会从磁盘中加载xml文件,并将内容映射到mMap中
每次获取Sp对象时,都会把所有的存储数据从磁盘读取到内存,所以如果存储内容过多,会导致OOM
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
从上面的代码可以看出,在调用getString()的时候加锁了,如果在主线程调用,意味着主线程被锁,如果此时Sp对象还未被获取,那么只有等Sp对象获取之后才能释放锁,这里可能会造成阻塞
public boolean commit() {
long startTime = 0;
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* 在当前线程同步写入 */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
从上面代码可以看出commit()调用时,将内存中的Sp写入到磁盘,是个耗时操作,所以不要频繁的调用commit(),频繁的调用可能会导致ANR
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
从上面的源码可以看出每次apply都会创建一个任务添加到 QueueWork中,如果频繁的执行写入操作,会造成ANR,
Android8.0之后优化,如果连续多次写入,只执行最后一次
Sp通过锁来保证线程安全,没有进程安全
文件备份机制
Sp在写入内容打磁盘时,会把原来的内容备份,写入成功后,会删掉备份内容
如果写入时发生异常,那么在下次启动时,如果有备份文件,会把备份文件作为源文件,未成功的文件删掉