美文网首页
Android数据持久化篇(二)—— SharedPrefere

Android数据持久化篇(二)—— SharedPrefere

作者: 乌托邦式的爱情 | 来源:发表于2021-08-05 14:11 被阅读0次

    在android开发中,SharedPreference属于比较常用的数据持久化方案之一,通常情况下我们会把那些隐私的而且属于轻量级的信息存储在SharedPreference里面,比如登录之后的用户信息。SharedPreference的存储其本质上是通过XML文件来进行存储,其XML文件放置的位置位于data/data/包名/shared_prefs中,属于内部存储的范畴。

    SharedPreference的使用

    SharedPreferences sharedPreferences = IApplication.getContext().getSharedPreferences("cache", 0);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putInt(key, value);
    editor.apply();
    

    SharedPreference的使用比较简单,这里就不做过多讲解,下面我们就从源码的角度来探究SharedPreference的具体使用细节。

    SharedPreference的源码分析

    1》首先Context.getSharedPreferences()会进入到ContextImpl的getSharedPreferences方法中。
    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        // 如果当前手机版本小于4.4且设置的文件名为空的话默认文件  为null.xml   
       if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";
            }
        }
        
        File file;
        // 使用synchronized 关键字防止多线程导致的数据错误
        synchronized (ContextImpl.class) {
            // 判断存放文件的集合是否为空,为空则直接创建
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            // 通过文件名拿到对应的文件
            file = mSharedPrefsPaths.get(name);
            // 如果文件名为空则直接创建并添加到集合中
            if (file == null) {
            // getPreferencesDir() 拿到data/data/包名/shared_prefs文件
            // makeFilename创建以传入的名称+“.xml”为后缀的文件
           // 所以最终的file文件完整路径为data/data/包名/shared_prefs/文件名.xml
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        // 重点分析这个,看下文件到底是怎么操作的
        return getSharedPreferences(file, mode);
    }
    
    // 生成XML文件并创建其File对象
    @Override
    public File getSharedPreferencesPath(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }
    
    // 返回/data/data/shared_prefs/目录的file对象
    @UnsupportedAppUsage
    private File getPreferencesDir() {
        synchronized (mSync) {
            if (mPreferencesDir == null) {
                mPreferencesDir = new File(getDataDir(), "shared_prefs");
            }
            return ensurePrivateDirExists(mPreferencesDir);
        }
    }
    

    SharedPreference最终的处理在这里

    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            // 查看缓存里面有没有相同实例
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
          // 没有实例先检查模式,如果模式是MODE_WORLD_READABLE
         //  或者MODE_WORLD_WRITEABLE直接抛出异常
                checkMode(mode);
                if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
                    if (isCredentialProtectedStorage()
                            && !getSystemService(UserManager.class)
                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                        throw new IllegalStateException("SharedPreferences in credential encrypted "
                                + "storage are not available until after user is unlocked");
                    }
                }
                // 创建SharedPreferencesImpl并添加到集合中
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, 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;
    }
    

    简单来说,ContextImpl使用sSharedPrefsCache集合保存了新创建的SharedPreferencesImpl对象,每次获取SharedPreferencesImpl对象之前,先查询sSharedPrefsCache集合,如果集合中有,则直接返回,如果没有,则新创建一个SharedPreferencesImpl对象,并加入到sSharedPrefsCache集合中。

    2》SharedPreferences.Editor editor = sharedPreferences.edit();

    根据上面的分析可知,最后返回的SharedPreferences其实是SharedPreferencesImpl实例,所以我们找到SharedPreferencesImpl的edit()方法。

    @Override
    public Editor edit() {
        // 如果该线程获取到了mLock对象锁,但是mLoaded为false,也就是加载xml过程没结束,那么线程会一直等待。
        synchronized (mLock) {
            awaitLoadedLocked();
        }
    
        
        // 创建Editor的实例EditorImpl并返回
        return new EditorImpl();
    }
    
    // 如果mLoad为false,线程会一直处于等待的状态
    @GuardedBy("mLock")
    private void awaitLoadedLocked() {
        if (!mLoaded) {
            // Raise an explicit StrictMode onReadFromDisk for this
            // thread, since the real read will be in a different
            // thread and otherwise ignored by StrictMode.
            BlockGuard.getThreadPolicy().onReadFromDisk();
        }
        while (!mLoaded) {
            try {
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
        if (mThrowable != null) {
            throw new IllegalStateException(mThrowable);
        }
    }
    

    也就是说,在执行sharedPreferences.edit()方法的时候,首先会去判断加载xml的过程有没有结束,如果没有结束线程就会一直处于等待状态直到完成,最后返回EditorImpl实例。

    3》editor.putInt(key, value);
    @Override
    public Editor putInt(String key, int value) {
        synchronized (mEditorLock) {
            mModified.put(key, value);
            return this;
        }
    }
    

    通过EditorImpl写入数据,写入的数据保存在Map集合里面。

    4》editor.apply()
    @Override
    public void apply() {
        final long startTime = System.currentTimeMillis();
        // 把内部类Editor的mModified集合中的数据保存到SharedPreferenceImpl类的mMap集合中。
        final MemoryCommitResult mcr = commitToMemory();
        final Runnable awaitCommit = new Runnable() {
                @Override
                public void run() {
                    try {
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
    
                    if (DEBUG && mcr.wasWritten) {
                        Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                + " applied after " + (System.currentTimeMillis() - startTime)
                                + " ms");
                    }
                }
            };
    
        QueuedWork.addFinisher(awaitCommit);
    
        Runnable postWriteRunnable = new Runnable() {
                @Override
                public void run() {
                    awaitCommit.run();
                    QueuedWork.removeFinisher(awaitCommit);
                }
            };
        // 把mMap集合中的数据写入到XML文件中
        SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    
        // Okay to notify the listeners before it's hit disk
        // because the listeners should always get the same
        // SharedPreferences instance back, which has the
        // changes reflected in memory.
        notifyListeners(mcr); // 通过注册监听者进行刷新
    }
    
    // 把mMap集合中的数据写入到xml文件中
    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        // 如果是apply()形式,则postWriteRunnable !=null,isFromSyncCommit 为true
        final boolean isFromSyncCommit = (postWriteRunnable == null);
    
        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        // 写入磁盘XML
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };
    
        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        //如果是commit()的形式,且当前没有写磁盘任务(mDiskWritesInFlight == 1),则直接调用
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                // 执行writeToFile()写入操作,不起新线程
                writeToDiskRunnable.run();
                return;
            }
        }
    
       //如果是apply()的形式,所有的线程都加入到QueuedWork中,以队列的形式保存,逐个线程启动    
       QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }
    

    综上,SharedPreferencesImpl定义了ArrayMap集合,其对象初始化时,加载XML文件内容到ArrayMap集合中。SharedPreferencesImpl对外提供get()接口,返回其ArrayMap集合中的数据。Editor也定义了ArrayMap集合,对外提供put()接口,接受调用者传入的值并存入其ArrayMap中,在commit()或者apply()时,同步Editor和SharedPreferencesImpl中的ArrayMap值,把ArrayMap中的数据保存到XML文件中。为考虑多线程情况下的使用,SharedPreferencesImpl和Editor中的多数操作均设置了相应的对象锁和同步代码块synchronized。由于SharedPreferencesImpl的初始化是一次性加载XML,为了性能上考虑,ContextImpl设置了缓冲区(ArrayMap)保存SharedPreferencesImpl对象,以便反复使用已有对象。

    总结

    1.SharePreferences是Android基于xml实现的一种数据持久化手段。
    2.SharePreferences不支持多进程。
    3.SharePreferences的commit与apply一个是同步一个是异步(大部分场景下)。
    4.不要使用SharePreferences存储太大的数据。
    5.尽量使用apply来代替commit。

    相关文章

      网友评论

          本文标题:Android数据持久化篇(二)—— SharedPrefere

          本文链接:https://www.haomeiwen.com/subject/joumvltx.html