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

时间:2022-01-24 23:19:19

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