DirectBoot模式是什么
DirectBoot(簡(jiǎn)稱DB)是Android N新引入的一個(gè)特性蔚润,本質(zhì)上是對(duì)數(shù)據(jù)訪問(wèn)做了限制奸远。在用戶開(kāi)機(jī)但未解鎖之前既棺,應(yīng)用只能訪問(wèn)這個(gè)安全區(qū)內(nèi)的數(shù)據(jù)讽挟,從而保護(hù)用戶隱私安全。
Android N上把數(shù)據(jù)分成了兩塊丸冕,分別是:
- 憑據(jù)保護(hù)存儲(chǔ)區(qū)(credential-protected)耽梅,這是所有應(yīng)用的默認(rèn)存儲(chǔ)位置,僅在用戶解鎖設(shè)備后可用晨仑。
- 設(shè)備保護(hù)存儲(chǔ)區(qū)(device-protected)褐墅,這是一個(gè)新的存儲(chǔ)位置,當(dāng)設(shè)備啟動(dòng)后(包括DB階段)隨時(shí)都可訪問(wèn)該位置洪己。
SharedPreference簡(jiǎn)述
SharedPreference(簡(jiǎn)稱SP)是Android原生提供的一種數(shù)據(jù)持久化方式妥凳,因?yàn)槠銩PI友好而收到開(kāi)發(fā)者的青睞。
先來(lái)看下SP是怎么存儲(chǔ)數(shù)據(jù)的吧答捕。
SP的實(shí)現(xiàn)在SharedPreferenceImpl
中逝钥,直接在Android Studio中無(wú)法查找到這個(gè)類,可以進(jìn)入目錄/Android/sdk/source/android-xx/android/app
中找到這個(gè)類拱镐。
SP的getInt
方法:
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
可以看到艘款,所謂的SharedPreference
其實(shí)就是維護(hù)了一個(gè)map,所有數(shù)據(jù)的存儲(chǔ)和讀取都通過(guò)操作這個(gè)map來(lái)實(shí)現(xiàn)沃琅。而且哗咆,這個(gè)map是常駐內(nèi)存的。這就帶來(lái)了一個(gè)問(wèn)題:內(nèi)存泄漏益眉!當(dāng)存儲(chǔ)的數(shù)目過(guò)多或者其中一個(gè)kay-value鍵值對(duì)過(guò)大的時(shí)候晌柬,就很可能造成OOM!
那么這個(gè)map是怎么來(lái)的呢郭脂?看看下面這個(gè)方法
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
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<>();
}
mLock.notifyAll();
}
}
其中的關(guān)鍵方法是下面這行:
map = XmlUtils.readMapXml(str);
也就是說(shuō)年碘,在SharedPreferenceImpl
的構(gòu)造函數(shù)中,啟動(dòng)了一個(gè)線程展鸡,異步把外存上的數(shù)據(jù)(其實(shí)就是一個(gè)xml文件屿衅,每一行是一個(gè)key-value鍵值對(duì))讀入內(nèi)存中,并以map的形式保存起來(lái)莹弊。
那么將數(shù)據(jù)寫(xiě)回外存呢涤久?SP提供了兩個(gè)方法供開(kāi)發(fā)者調(diào)用,分別是
Editor#apply()
和Editor#commit
箱硕,區(qū)別是拴竹,apply
是異步的,而commit
則是同步保存數(shù)據(jù)剧罩,在UI線程中調(diào)用,會(huì)阻塞主線程座泳。
看一下commit
方法做了些什么:
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
// 提交到內(nèi)存中
MemoryCommitResult mcr = commitToMemory();
// 將內(nèi)存數(shù)據(jù)寫(xiě)到外存上
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
關(guān)鍵方法commitToMemory()
和 enqueueDiskWrite(MemoryCommitResult, Runnable postWriteRunnable)
惠昔。前者把提交到editor中的新鍵值對(duì)提交到內(nèi)存中(即前面提到的map中)幕与。后者把map中的數(shù)據(jù)保存到xml文件中。
DB與SP的矛盾
說(shuō)到這里镇防,SP的消失之謎大概也有答案了:其實(shí)就是在DB模式中啦鸣,由于沒(méi)有訪問(wèn)憑據(jù)保護(hù)存儲(chǔ)區(qū)的權(quán)限,因此無(wú)法將外存中的數(shù)據(jù)讀取到內(nèi)存中来氧。在用戶解除DB模式后诫给,由于緩存了SP的實(shí)例,因此內(nèi)存中的空白數(shù)據(jù)覆蓋了xml文件啦扬,導(dǎo)致所有的鍵值對(duì)都消失中狂。
這里順帶附上Context#getSharedPreference(File file, int mode)
的代碼,看下Android源碼中是怎么對(duì)SP對(duì)象進(jìn)行緩存的:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
&& !getSystemService(StorageManager.class).isUserKeyUnlocked(
UserHandle.myUserId())
&& !isBuggy()) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
}
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
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) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
// 這里的sSharedPrefsCache緩存了sp的實(shí)例
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}