赞
踩
首先我们来看看,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<String>类型的数据
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<String>类型
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。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。