美文网首页
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