3种数据持久化:
- File:openFileInput(String fileName)、openFileOutput(String fileName, int mode)
不对存储的内容进行任何的格式化处理,比较合适存储一些简单的文本数据或二进制数据
- SharedPreferences
使用键值对的方式来存储数据,保存数据更加方便。数据以明文的方式保存在文件中,需要加密,一般保存应用设置。
- SQLite 继承SqLiteOpenHelper类创建数据库,
可以保存大量复杂的关系型数据
账号密码自动登陆:
在项目中,我们一般使用SharePrefences实现自动登录的功能,在登录成功后,将数据(用户名,密码)保存在SharePrefences中,然后再次进入app时,判断SharePrefences中有无数据,有的话就跳到主页面,没有的话就跳到登录页。
SharedPreferences 的源码实现:
SharedPreferences 是线程安全的。
final class SharedPreferencesImpl implements SharedPreferences {
// 1、使用注释标记锁的顺序
// Lock ordering rules:
// - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock
// - acquire mWritingToDiskLock before EditorImpl.mLock
// 2、通过注解标记持有的是哪把锁
@GuardedBy("mLock")
private Map<String, Object> mMap;
@GuardedBy("mWritingToDiskLock")
private long mDiskStateGeneration;
public final class EditorImpl implements Editor {
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
}
}
SharedPreferences 是由 Context 返回的,获取 SharedPreferences 的方法定义在抽象类 Context 中。
public abstract SharedPreferences getSharedPreferences(String name, int mode);
public abstract SharedPreferences getSharedPreferences(File file, int mode);
第一个方法是我们常用的,只要传入文件名,SP会自动寻找已有文件或创建,第二个是我们传入的文件。
所以 SharedPreferences 的操作,本质上就是对文件的操作,使用SharedPreferences时,只有第一次读取数据是有概率卡主线程几十到几百毫秒,而之后的读取时间几乎可以忽略不计。最后会落实到一个 xml 文件上。
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
标准路径在 /data/data/应用包名/shared_prefs 文件夹中,且都是 xml 文件。
commit 和 apply 的对比
EditorImpl 内部有一个内存缓存,用来保存用户修改后的操作:
private final Map<String, Object> mModified = Maps.newHashMap();
在执行 commit 或者 apply 前,比如editor.putString("Key","Value")会把修改存储在 mModified 中。
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
}
commit(同步):构造一个MemoryCommitResult来进行结果投递,在post任务以后会直接在当前线程进行wait。
apply(异步):构造一个MemoryCommitResult来调度IO(QueuedWork提供了singleThreadExecutor),apply()不会等待,而由QueuedWork的waitToFinish()方法的调用者(ActivityThread)来保证在某些时间点等待task的完成。
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
QueuedWork:一个内部工具类,用于跟踪那些未完成的或尚未结束的全局任务。由 waitToFinish 方法保证执行,在 Activity onStop 以及 Service 处理 onStop,onStartCommand 时等待写入操作,所有排队的异步任务都在一个独立、专用的线程上处理。平时使用的时候,尽量使用 apply 避免卡住主线程。
apply方法造成的ANR:
在apply()方法中,首先会创建一个等待锁,最终更新文件的任务会交给QueuedWork.singleThreadExecutor()单个线程或者HandlerThread去执行,当文件更新完毕后会释放锁。 但当Activity.onStop()以及Service处理onStop等相关方法时,则会执行 QueuedWork.waitToFinish()等待所有的等待锁释放,因此如果SharedPreferences一直没有完成更新任务,有可能会导致卡在主线程,最终超时导致ANR。
加载 xml 数据文件
SharedPreferences 的加载流程,就是把文件的内容载入内存的过程。
private void loadFromDisk() {
...
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
...
}
本质上,就是读取一个 xml 文件,被内容解析为 Map 对象。这个 map 包含了我们之前保存的所有键值对的数据。
SharedPreferences 的读取非常快,载入完成后,后面的读操作都是针对 mMap 的,响应速度是内存级别的非常快。
SharedPreferences 不存放大量数据原因:
- apply操作耗时过久会导致ANR
- 如果数据量很大的话,返回的map对象会占很大一块内存