SharedPreferences第一次使用后HashMap将常驻内存

时间:2023-03-08 23:38:32
SharedPreferences第一次使用后HashMap将常驻内存

今天使用SharedPreferences的时候突然想到了这个问题,因为我们要存储应用级别的上下文信息,包括用户信息等一系列信息;这个时候使用getSharedPreferences是否合适呢!

其实这个问题是相对的 ,如果存储少量信息那么使用getSharedPreferences确实是非常便捷的,如果要存储大量的信息那么尽量不要使用SharedPreferences。

首先探讨SharedPreferences这个问题的时候我们先来分析下Context,Context什么意思呢!顾名思义是“上下文”的含义;那么兄弟们又要喷水了,和没有解释一样;哈哈

如果说整个android环境是一个“软件”公司的话,那么我可以把android中的Context比喻成“项目研发团队”,那么其中的android四大组件以甚至可以说成是6大件或者7大件(包含intent,Notification等),就相当于“项目团队”中的->攻城狮,他们各司其职。而且 ,这样比喻你可以体会到,在整个android的环境中我们的activity或者service并不是可以你说new就new的,因为招聘什么样的“攻城狮”是组织才能决定的;和j2ee很不相似。

好啦!闲话少叙言归正传;那么SharedPreferences是和Context密不可分的。Context在application启动时,由Native层给予;而且同学们也都知道Context是一个抽象的,它的具体实现在哪里呢?而且ContextWapper也是要通过attachBaseContext之后才能获得一个mBase实例。

 /**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

实际上Native中将一个叫做ContextImpl的类型给予了ContextWapper。这样我们就可以在ContextImpl类型中去寻找SharedPreferences的足迹,因为SharedPreferences是通过上下文Context来获得的!让我们直接上getSharedPreferences方法代码:

 @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
} final String packageName = getPackageName();
ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
} // At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
} sp = packagePrefs.get(name);
if (sp == null) {
File prefsFile = getSharedPrefsFile(name);
sp = new SharedPreferencesImpl(prefsFile, mode);
packagePrefs.put(name, 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;
}

那么纵观整个方法他始终围绕着sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();为核心;最后返回的是sp,细节如下:

 if (sp == null) {
File prefsFile = getSharedPrefsFile(name);
sp = new SharedPreferencesImpl(prefsFile, mode);
packagePrefs.put(name, sp);
return sp;
}

注意标红的类型,这一步骤将File prefsFile = getSharedPrefsFile(name);获得到的文件传入了构造方法中!!!其实如果文件不存在的话getSharedPrefsFile也是在new File();

那么关键的地方就在于SharedPreferencesImpl这个类。

它的构造函数如下:

SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
startLoadFromDisk();
} private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
synchronized (SharedPreferencesImpl.this) {
loadFromDiskLocked();
}
}
}.start();
} private void loadFromDiskLocked() {
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 (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
}
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<String, Object>(); notifyAll();
}

拔山涉水之后讨论的关键渐渐的浮现出来:依旧注意标红。HashMap就是当存储内容过大时准备放弃使用SharedPreferences的缘故,相信走到这里的同学已经明白了。当我们第一次使用

getSharedPreferences的时候它就会把你存储的内容注入内存中一直保留!!所以说我们使用过多的Key,Value存储时,可以尝试使用java中的Properties等方式。

这样就已经豁然开朗,好!我们继续。使用SharedPreferences获取数据时的实际方法在这,包括edit的操作:

public int getInt(String key, int defValue) {
synchronized (this) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
public long getLong(String key, long defValue) {
synchronized (this) {
awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}

我们已经发现他一直是在从缓存的HashMap中提取值,拿给我们!还有我们熟悉的commit()方法都在SharedPreferencesImpl类型中;顺便说下apply为异步操作,commit为同步IO操作,耗时可能会比较长。commit代码如下:

public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);//by zzq 写入文件
try {
mcr.writtenToDiskLatch.await();//by zzq 同步等待
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);//by zzq 通知,监听者
return mcr.writeToDiskResult;
}

commitToMemory这个方法中会将提交进来的新key,value一直做put或update操作到名为mMap的HashMap中。

private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners =
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
} synchronized (this) {
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
} for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
} mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
} mModified.clear();
}
}
return mcr;
}

apply同样调用以上方法!