今天使用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同样调用以上方法!