每日一问:谈谈 SharedPreferences 的 apply() 和 commit()

SharedPreferences 应该是任何一名 Android 初学者都知道的存储类了,它轻量,适合用于保存软件配置等参数。以键值对的 XML 文件形式存储在本地,程序卸载后也会一并清除,不会残留信息。

使用起来也非常简单。

// 读取 val sharedPreferences = getSharedPreferences("123", Context.MODE_PRIVATE) val string = sharedPreferences.getString("123","") // 写入 val editor = sharedPreferences.edit() editor.putString("123","123") editor.commit()

当我们写下这样的代码的时候,IDE 极易出现一个警告,提示我们用 apply() 来替换 commit()。原因也很简单,因为 commit() 是同步的,而 apply() 采用异步的方式通常来说效率会更高一些。但是,当我们把 editor.commit() 的返回值赋给一个变量的时候,这时候就会发现 IDE 没有了警告。这是因为 IDE 认为我们想要使用 editor.commit() 的返回值了,所以,通常来说,在我们不关心操作结果的时候,我们更倾向于使用 apply() 进行写入的操作。

获取 SharedPreferences 实例

我们可以通过 3 种方式来获取 SharedPreferences 的实例。
首先当然是我们最常见的写法。

getSharedPreferences("123", Context.MODE_PRIVATE)

Context 的任意子类都可以直接通过 getSharedPreferences() 方法获取到 SharedPreferences 的实例,接受两个参数,分别对应 XML 文件的名字和操作模式。其中 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 这两种模式已在 Android 4.2 版本中被废弃。

Context.MODE_PRIVATE: 指定该 SharedPreferences 数据只能被本应用程序读、写;

Context.MODE_WORLD_READABLE: 指定该 SharedPreferences 数据能被其他应用程序读,但不能写;

Context.MODE_WORLD_WRITEABLE: 指定该 SharedPreferences 数据能被其他应用程序读;

Context.MODE_APPEND:该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件;

另外在 Activity 的实现中,还可以直接通过 getPreferences() 获取,实际上也就把当前 Activity 的类名作为文件名参数。

public SharedPreferences getPreferences(@Context.PreferencesMode int mode) { return getSharedPreferences(getLocalClassName(), mode); }

此外,我们也可以通过 PreferenceManager 的 getDefaultSharedPreferences() 获取到。

public static SharedPreferences getDefaultSharedPreferences(Context context) { return context.getSharedPreferences(getDefaultSharedPreferencesName(context), getDefaultSharedPreferencesMode()); } public static String getDefaultSharedPreferencesName(Context context) { return context.getPackageName() + "_preferences"; } private static int getDefaultSharedPreferencesMode() { return Context.MODE_PRIVATE; }

可以很明显的看到,这个方式就是在直接把当前应用的包名作为前缀来进行命名的。

注意:如果在 Fragment 中使用 SharedPreferences 时,SharedPreferences 的初始化尽量放在 onAttach(Activity activity) 里面进行 ,否则可能会报空指针,即 getActivity() 会可能返回为空。

SharedPreferences 源码(基于 API 28)

有较多 SharedPreferences 使用经验的人,就会发现 SharedPreferences 其实具备挺多的坑,但这些坑主要都是因为不熟悉其中真正的原理所导致的,所以,笔者在这里,带大家一起揭开 SharedPreferences 的神秘面纱。

SharedPreferences 实例获取

前面讲了 SharedPreferences 有三种获取实例的方法,但归根结底都是调用的 Context 的 getSharedPreferences() 方法。由于 Android 的 Context 类采用的是装饰者模式,而装饰者对象其实就是 ContextImpl,所以我们来看看源码是怎么实现的。

// 存放的是名称和文件夹的映射,实际上这个名称就是我们外面传进来的 name private ArrayMap<String, File> mSharedPrefsPaths; public SharedPreferences getSharedPreferences(String name, int mode) { // 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"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); } @Override public File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); } private File makeFilename(File base, String name) { if (name.indexOf(File.separatorChar) < 0) { return new File(base, name); } throw new IllegalArgumentException( "File " + name + " contains a path separator"); }

可以很明显的看到,内部是采用 ArrayMap 来做的处理,而这个 mSharedPrefsPaths 主要是用于存放名称和文件夹的映射,实际上这个名称就是我们外面传进来的 name,这时候我们通过 name 拿到我们的 File,如果当前池子中没有的话,则直接新建一个 File,并放入到 mSharedPrefsPaths 中。最后还是调用的重载方法 getSharedPreferences(File,mode)

// 存放包名与ArrayMap键值对,初始化时会默认以包名作为键值对中的 Key,注意这是个 static 变量 private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache; @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) { 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"); } } 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; } 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; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyfpx.html