CodeEggDailyInterview
CodeEggDailyInterview copied to clipboard
SharedPreferences 的加载实现是在子线程,但是为什么说 getX(key) 操作可能还会阻塞主线程呢?
//getSharedPreferences 获取 SP 的核心源码
class ContextImpl extends Context {
//Context会把对应 SP 缓存起来
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
......
@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) {
//第一次或者新进程中时缓存没有就新 new 一个对应实例
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
......
return sp;
}
......
}
接着看看 SharedPreferencesImpl 实例化及通过 SharedPreferences 获取一个指定 key 的值的核心源码部分
//一个File对应的一个SP实例
final class SharedPreferencesImpl implements SharedPreferences {
//用来存储该SP的所有Key-Value对
private Map<String, Object> mMap;
SharedPreferencesImpl(File file, int mode) {
......
//构造方法从磁盘加载SP文件
startLoadFromDisk();
}
private void startLoadFromDisk() {
......
//启动一个名字为SharedPreferencesImpl-load的线程从磁盘读文件
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//子线程中读取文件解析成key-value对Map
private void loadFromDisk() {
......
Map map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (mLock) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
//子线程读取SP文件到mMap成功后通过notify通知释放阻塞锁
mLock.notifyAll();
}
}
//通过SharedPreferences对象获取指定key的值
//(一般与SharedPreferences获取在一个线程,主线程)
public int getInt(String key, int defValue) {
synchronized (mLock) {
//阻塞等待SP读取到内存后再get
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
private void awaitLoadedLocked() {
......
while (!mLoaded) {
try {
//阻塞等待SP读取到内存完成
mLock.wait();
} catch (InterruptedException unused) {
}
}
}
......
}
所以说 SharedPreferences 读取 Map 虽然在子线程,但是其 getX(key) 系列方法想要调用的前提是 SharedPreferences 子线程已经读取完成,否则就会阻塞,所以 SharedPreferences 中如果存储太大内容或者太多内容导致 XML 解析等变慢就会导致后面的 getX(key) 阻塞主线程,从而导致主线程卡顿,所以说 SharedPreferences 是轻量级的持久化工具。