本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Android源码进阶之深入理解SharedPreference原理机制
奔跑的男人 · 500浏览 · 发布于2021-10-15 +关注

SharedPreferences的本身实现就是分为两步,一步是内存,一部是磁盘,而主线程又依赖SharedPreferences的写入,所以可能当io成为瓶颈的时候,App会因为SharedPreferences变的卡。

前言

很久没有分析源码了,今天我们来分析下SharedPreferences;

大家一起来学习;

一、SharedPreferences简单使用

1、创建

第一个参数是储存的xml文件名称,第二个是打开方式,一般就用

Context.MODE_PRIVATE; 
SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);

    2、写入

    //可以创建一个新的SharedPreference来对储存的文件进行操作 
    SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE); 
    //像SharedPreference中写入数据需要使用Editor 
    SharedPreference.Editor editor = sp.edit(); 
    //类似键值对 
    editor.putString("name", "string"); 
    editor.putInt("age", 0); 
    editor.putBoolean("read", true); 
    //editor.apply(); 
    editor.commit();

      • apply和commit都是提交保存,区别在于apply是异步执行的,不需要等待。不论删除,修改,增加都必须调用apply或者commit提交保存;

      • 关于更新:如果已经插入的key已经存在。那么将更新原来的key;

      • 应用程序一旦卸载,SharedPreference也会被删除;

      3、读取

      SharedPreference sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE); 
      //第一个参数是键名,第二个是默认值 
      String name=sp.getString("name", "暂无"); 
      int age=sp.getInt("age", 0); 
      boolean read=sp.getBoolean("isRead", false);

        4、检索

        SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE); 
        //检查当前键是否存在 
        boolean isContains=sp.contains("key"); 
        //使用getAll可以返回所有可用的键值 
        //Map<String,?> allMaps=sp.getAll();

          5、删除

          当我们要清除SharedPreferences中的数据的时候一定要先clear()、再commit(),不能直接删除xml文件;

          SharedPreference sp=getSharedPreferences("名称", Context.MODE_PRIVATE); 
          SharedPrefence.Editor editor=sp.edit(); 
          editor.clear(); 
          editor.commit();

            • getSharedPreference() 不会生成文件,这个大家都知道;

            • 删除掉文件后,再次执行commit(),删除的文件会重生,重生文件的数据和删除之前的数据相同;

            • 删除掉文件后,程序在没有完全退出停止运行的情况下,Preferences对象所存储的内容是不变的,虽然文件没有了,但数据依然存在;程序完全退出停止之后,数据才会丢失;

            • 清除SharedPreferences数据一定要执行editor.clear(),editor.commit(),不能只是简单的删除文件,这也就是最后的结论,需要注意的地方

            二、SharedPreferences源码分析

            1、创建

            SharedPreferences preferences = getSharedPreferences("test", Context.MODE_PRIVATE);

             

              实际上context的真正实现类是ContextImp,所以进入到ContextImp的getSharedPreferences方法查看:

              @Override 
                 public SharedPreferences getSharedPreferences(String name, int mode) { 
                     ...... 
                     File file; 
                     synchronized (ContextImpl.class) { 
                         if (mSharedPrefsPaths == null) { 
                         //定义类型:ArrayMap<String, File> mSharedPrefsPaths; 
                             mSharedPrefsPaths = new ArrayMap<>(); 
                         } 
                         //从mSharedPrefsPaths中是否能够得到file文件 
                         file = mSharedPrefsPaths.get(name); 
                         if (file == null) {//如果文件为null 
                         //就创建file文件 
                             file = getSharedPreferencesPath(name); 
                             将name,file键值对存入集合中 
                             mSharedPrefsPaths.put(name, file); 
                         } 
                     } 
                     return getSharedPreferences(file, mode); 
                 }

                ArrayMap<String, File> mSharedPrefsPaths;对象是用来存储SharedPreference文件名称和对应的路径,获取路径是在下列方法中,就是获取data/data/包名/shared_prefs/目录下的

                @Override 
                public File getSharedPreferencesPath(String name) { 
                    return makeFilename(getPreferencesDir(), name + ".xml"); 
                } 
                private File getPreferencesDir() { 
                        synchronized (mSync) { 
                            if (mPreferencesDir == null) { 
                                mPreferencesDir = new File(getDataDir(), "shared_prefs"); 
                            } 
                            return ensurePrivateDirExists(mPreferencesDir); 
                        } 
                }

                  路径之后才开始创建对象

                  @Override 
                    public SharedPreferences getSharedPreferences(File file, int mode) { 
                    //重点1 
                        checkMode(mode); 
                    ....... 
                        SharedPreferencesImpl sp; 
                        synchronized (ContextImpl.class) { 
                        //获取缓存对象(或者创建缓存对象) 
                            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); 
                            //通过键file从缓存对象中获取Sp对象 
                            sp = cache.get(file); 
                            //如果是null,就说明缓存中还没后该文件的sp对象 
                            if (sp == null) { 
                            //重点2:从磁盘读取文件 
                                sp = new SharedPreferencesImpl(file, mode); 
                                //添加到内存中 
                                cache.put(file, sp); 
                                //返回sp 
                                return sp; 
                            } 
                        } 
                        //如果设置为MODE_MULTI_PROCESS模式,那么将执行SP的startReloadIfChangedUnexpectedly方法。 
                        if ((mode & Context.MODE_MULTI_PROCESS) != 0 || 
                            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { 
                            sp.startReloadIfChangedUnexpectedly(); 
                        } 
                        return sp; 
                    }

                    就是重载之前的方法,只是入参由文件名改为File了,给创建过程加锁了synchronized ,通过方法getSharedPreferencesCacheLocked()获取系统中存储的所有包名以及对应的文件,这就是每个sp文件只有一个对应的SharedPreferencesImpl实现对象原因

                    流程:

                    • 获取缓存区,从缓存区中获取数据,看是否存在sp对象,如果存在就直接返回

                    • 如果不存在,那么就从磁盘获取数据,

                    • 从磁盘获取的数据之后,添加到内存中,

                    • 返回sp;

                    getSharedPreferencesCacheLocked

                    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { 
                            if (sSharedPrefsCache == null) { 
                                sSharedPrefsCache = new ArrayMap<>(); 
                            } 
                            final String packageName = getPackageName(); 
                            ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName); 
                            if (packagePrefs == null) { 
                                packagePrefs = new ArrayMap<>(); 
                                sSharedPrefsCache.put(packageName, packagePrefs); 
                            } 
                            return packagePrefs; 
                        }

                      • getSharedPreferences(File file, int mode)方法中,从上面的系统缓存中分局File获取SharedPreferencesImpl对象,如果之前没有使用过,就需要创建一个对象了,通过方法checkMode(mode);

                      • 先检查mode是否是三种模式,然后通过sp = new SharedPreferencesImpl(file, mode);

                      • 创建对象,并将创建的对象放到系统的packagePrefs中,方便以后直接获取;

                      SharedPreferencesImpl(File file, int mode) { 
                              mFile = file; //存储文件 
                              //备份文件(灾备文件) 
                              mBackupFile = makeBackupFile(file); 
                              //模式 
                              mMode = mode; 
                              //是否加载过了 
                              mLoaded = false; 
                              // 存储文件内的键值对信息 
                              mMap = null; 
                              //从名字可以知道是:开始加载数据从磁盘 
                              startLoadFromDisk(); 
                          }

                        • 主要是设置了几个参数,mFile 是原始文件;mBackupFile 是后缀.bak的备份文件;

                        • mLoaded标识是否正在加载修改文件;

                        • mMap用来存储sp文件中的数据,存储时候也是键值对形式,获取时候也是通过这个获取,这就是表示每次使用sp的时候,都是将数据写入内存,也就是sp数据存储数据快的原因,所以sp文件不能存储大量数据,否则执行时候很容易会导致OOM;

                        • mThrowable加载文件时候报的错误;

                        • 下面就是加载数据的方法startLoadFromDisk();从sp文件中加载数据到mMap中

                        2、startLoadFromDisk()

                        private void startLoadFromDisk() { 
                               synchronized (mLock) { 
                                   mLoaded = false; 
                               } 
                               //开启子线程加载磁盘数据 
                               new Thread("SharedPreferencesImpl-load") { 
                                   public void run() { 
                                       loadFromDisk(); 
                                   } 
                               }.start(); 
                           } 
                           private void loadFromDisk() { 
                               synchronized (mLock) { 
                               //如果加载过了 直接返回 
                                   if (mLoaded) { 
                                       return; 
                                   } 
                                   //备份文件是否存在, 
                                   if (mBackupFile.exists()) { 
                                   //删除file原文件 
                                       mFile.delete(); 
                                       //将备份文件命名为:xml文件 
                                       mBackupFile.renameTo(mFile); 
                                   } 
                               } 
                               ....... 
                               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; 
                                   //数据不是null 
                                   if (map != null) { 
                                   //将map赋值给全局的存储文件键值对的mMap对象 
                                       mMap = map; 
                                       //更新内存的修改时间以及文件大小 
                                       mStatTimestamp = stat.st_mtime; 
                                       mStatSize = stat.st_size; 
                                   } else { 
                                       mMap = new HashMap<>(); 
                                   } 
                                   //重点:唤醒所有以mLock锁的等待线程 
                                   mLock.notifyAll(); 
                               } 
                           }

                          • 首先判断备份文件是否存在,如果存在,就更该备份文件的后缀名;接着就开始读取数据,然后将读取的数据赋值给全局变量存储文件键值对的mMap对象,并且更新修改时间以及文件大小变量;

                          • 唤醒所有以mLock为锁的等待线程;

                          • 到此为止,初始化SP对象就算完成了,其实可以看出来就是一个二级缓存流程:磁盘到内存;

                          3、get获取SP中的键值对

                          @Nullable 
                             public String getString(String key, @Nullable String defValue) { 
                                 synchronized (mLock) { 锁判断 
                                     awaitLoadedLocked(); //等待机制 
                                     String v = (String)mMap.get(key); //从键值对中获取数据 
                                     return v != null ? v : defValue; 
                                 } 
                             } 
                          private void awaitLoadedLocked() { 
                                 ....... 
                                 while (!mLoaded) { //在加载数据完毕的时候,值为true 
                                     try { 
                                     //线程等待 
                                         mLock.wait(); 
                                     } catch (InterruptedException unused) { 
                                     } 
                                 } 
                             }

                            如果数据没有加载完毕(也就是说mLoaded=false),此时将线程等待;

                            4、putXXX以及apply源码

                            public Editor edit() { 
                                    //跟getXXX原理一样 
                                    synchronized (mLock) { 
                                        awaitLoadedLocked(); 
                                    } 
                                    //返回EditorImp对象 
                                    return new EditorImpl(); 
                                } 
                             public Editor putBoolean(String key, boolean value) { 
                                  synchronized (mLock) { 
                                       mModified.put(key, value); 
                                       return this; 
                                     } 
                             } 
                                   public void apply() { 
                                        final long startTime = System.currentTimeMillis(); 
                                        //根据名字可以知道:提交数据到内存 
                                        final MemoryCommitResult mcr = commitToMemory(); 
                                       ........ 
                            //提交数据到磁盘中 
                                        SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); 
                                        //重点:调用listener 
                                        notifyListeners(mcr); 
                                    }

                              • 先执行了commitToMemory,提交数据到内存;然后提交数据到磁盘中;

                              • 紧接着调用了listener;

                              5、commitToMemory

                                                                 android
                                                               

                              相关推荐

                              android下vulkan与opengles纹理互通

                              talkchan · 1176浏览 · 2020-11-23 10:37:39
                              Android 使用RecyclerView实现轮播图

                              奔跑的男人 · 2175浏览 · 2019-05-09 17:11:13
                              微软发布新命令行工具 Windows Terminal

                              吴振华 · 869浏览 · 2019-05-09 17:15:04
                              Facebook 停止屏蔽部分区块链广告

                              · 754浏览 · 2019-05-09 17:20:08
                              加载中

                              0评论

                              评论
                              分类专栏
                              小鸟云服务器
                              扫码进入手机网页