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

     
  • 相关阅读:
    element 三级复选框
    element 复选框问题
    vue 的样式穿透(深度选择器) >>>
    随笔,用于直接复制粘贴
    element 弹窗无法重新赋值的问题
    @vue/cli 4.2.3版本的本地json读取和跨域配置(与旧版本vue不同)
    element表格及接口的对接
    axios的post请求即自动刷新
    Puppeteer 安装及失败原因
    Redis的安装
  • 原文地址:https://www.cnblogs.com/zzq-include/p/5895892.html
Copyright © 2020-2023  润新知