首先我们来看看,SharedPreferences在什么场景比较适用:
性能:
获取SharedPreferences对象:
//根据name查找SharedPreferences,若已经存在则获取,若不存在则创建一个新的
public abstract SharedPreferences getSharedPreferences (String name, int mode)
参数:
name:命名
mode:模式,包括
若该Activity只需要创建一个SharedPreferences对象的时候,可以使用getPreferences方法,不需要为SharedPreferences对象命名,只要传入参数mode即可
public SharedPreferences getPreferences (int mode)
获取Editor对象(由SharedPreferences对象调用):
abstract SharedPreferences.Editor edit()
写入数据(由Editor对象调用):
参数:
key:指定数据对应的key
value:指定的值
//写入boolean类型的数据
abstract SharedPreferences.Editor putBoolean(String key, boolean value)
//写入float类型的数据
abstract SharedPreferences.Editor putFloat(String key, float value)
//写入int类型的数据
abstract SharedPreferences.Editor putInt(String key, int value)
//写入long类型的数据
abstract SharedPreferences.Editor putLong(String key, long value)
//写入String类型的数据
abstract SharedPreferences.Editor putString(String key, String value)
//写入Set类型的数据
abstract SharedPreferences.Editor putStringSet(String key, Set<String> values)
移除指定key的数据(由Editor对象调用):
abstract SharedPreferences.Editor remove(String key)
清空数据(由Editor对象调用):
abstract SharedPreferences.Editor clear()
提交数据(由Editor对象调用):
abstract boolean commit()
读取数据(由SharedPreferences对象调用):
参数
key:指定数据的key
defValue:当读取不到指定的数据时,使用的默认值defValue
//读取所有数据
abstract Map<String, ?> getAll()
//读取的数据为boolean类型
abstract boolean getBoolean(String key, boolean defValue)
//读取的数据为float类型
abstract float getFloat(String key, float defValue)
//读取的数据为int类型
abstract int getInt(String key, int defValue)
//读取的数据为long类型
abstract long getLong(String key, long defValue)
//读取的数据为String类型
abstract String getString(String key, String defValue)
//读取的数据为Set类型
abstract Set<String> getStringSet(String key, Set<String> defValues)
1)写入数据:
//步骤1:创建一个SharedPreferences对象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步骤2: 实例化SharedPreferences.Editor对象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步骤3:将获取过来的值放入文件
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步骤4:提交
editor.commit();
2)读取数据:
SharedPreferences sharedPreferences= getSharedPreferences("data", Context .MODE_PRIVATE);
String userId=sharedPreferences.getString("name","");
3)删除指定数据
editor.remove("name");
editor.commit();
4)清空数据
editor.clear();
editor.commit();
是否在Acitivty里执行?
在Acitivty中执行的:
getSharedPreferences (String name, int mode)
getPreferences (int mode)
MODE_PRIVATE
不在Activity中:
context.getSharedPreferences (String name, int mode)
context.getPreferences (int mode)
Conetxt.MODE_PRIVATE
同时执行这两句代码的时候,第一行代码所写的内容会被第二行代码取代。
editor.putInt("age", 20);
//覆盖key为age的数据,得到的结果:age = 32
editor.putInt("age", 32);
editor.putString("age", "20");
//覆盖key为age的数据,得到的结果:age = 32 (int类型)
editor.putInt("age", 32);
执行以下代码会出现异常。
(指定key所保存的类型和读取时的类型不同)
editor.putInt("age", 32);//保存为int类型
String age = userInfo.getString("age", "null");//读取时为String类型,出现异常
在这些动作之后,记得commit
editor.putInt("age", 20);//写入操作
editor.remove("age"); //移除操作
editor.clear(); //清空操作
editor.commit();//记得commit
其实SharedPreferences作为一种轻量级的数据存储方式,使用起来也非常方便,以键值对的形式存储在本地,初始化 SharedPreference 的时候,会将整个文件内容加载内存中,因此会带来以下问题:
因为getXXX()都是同步的,在主线程调用 get 方法时,同步方法内调用了 wait() 方法,会一直等待 getSharedPreferences() 方法开启的线程读取完数据才能继续往下执行,如果数据量读取的小,并没有什么影响,如果读取的文件较大会导致主线程阻塞
具体大家可以查看haredPreferences源码:
frameworks/base/core/java/android/app/SharedPreferencesImpl.java
val key = "DataStore"
val sp = getSharedPreferences("文件名", Context.MODE_PRIVATE)
sp.edit { putInt(key, 0) } // 使用 Int 类型的数据覆盖相同的 key
sp.getString(key, ""); // 使用相同的 key 读取 Sting 类型的数据
使用 Int 类型的数据覆盖掉相同的 key,然后使用相同的 key 读取 Sting 类型的数就会造成ClassCastException异常
而通过 getSharedPreferences() 方法加载的数据,最后会将数据存储在静态的成员变量中。
异步的提交为什么会发生ANR呢?
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();
}
}
}
}
}
在上面的方法中注意到 对每个 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 的命令处理后被调用,并且调用这个方法的目的是为了确保异步任务被及时完成。
可以看到 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 可靠的、保证写入完成的存储机制。
总结一下就是:
Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。
如果您当前在使用 SharedPreferences 存储数据,请考虑迁移到 DataStore。
DataStore | Android开发者平台