SharedPreferences是由Android提供的轻量级的存储方式,为什么叫它轻量级,主要是由于它只能够存储Java基本类型的数据。其底层实现也是借助共享文件(具体实现为XML文件),只不过Android基于Java的文件系统又再次做了一些封装,使其调用起来更为简单和方便。
本文还是通过写Demo的形式,来对SharedPreferences的常见使用方式来进行一个介绍。
SharedPreferencesActivity的代码如下:
package com.itachi.android.sharedpreferencesdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.itachi.android.sharedpreferencesdemo.util.SharedPreferencesUtils;
public class SharedPreferencesActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "SharedPreferencesActivtiy";
private static final String CUSTOM_PREFERENCES_NAME = "CustomPreferences";
private EditText mUsername;
private EditText mAge;
private Button mWriteToApplications;
private Button mWriteToActivitys;
private Button mWriteToCustom;
private Button mToOtherActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shared_preferences);
mUsername = findViewById(R.id.user_name);
mAge = findViewById(R.id.user_age);
mWriteToApplications = findViewById(R.id.button_write_to_applications_preferences);
mWriteToActivitys = findViewById(R.id.button_write_to_current_activitys_preferences);
mWriteToCustom = findViewById(R.id.button_write_to_custom_preferences);
mToOtherActivity = findViewById(R.id.to_other_activity);
mWriteToApplications.setOnClickListener(this);
mWriteToActivitys.setOnClickListener(this);
mWriteToCustom.setOnClickListener(this);
mToOtherActivity.setOnClickListener(this);
}
private void writeToPreferences(SharedPreferences preferences) {
SharedPreferences.Editor editor = preferences.edit();
String username = mUsername.getText().toString();
int age = Integer.valueOf(TextUtils.isEmpty(mAge.getText()) ? "-1" : mAge.getText().toString());
editor.putString("Username", username);
editor.putInt("Age", age);
editor.commit();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_write_to_applications_preferences:
writeToPreferences(SharedPreferencesUtils.getCurrentApplicationSharedPreferences(this, MODE_PRIVATE));
break;
case R.id.button_write_to_current_activitys_preferences:
writeToPreferences(SharedPreferencesUtils.getCurrentActivityPreferences(this, MODE_PRIVATE));
break;
case R.id.button_write_to_custom_preferences:
writeToPreferences(SharedPreferencesUtils.getPreferencesByName(this, CUSTOM_PREFERENCES_NAME, MODE_PRIVATE));
break;
case R.id.to_other_activity:
Intent intent = new Intent(this, OtherActivity.class);
startActivity(intent);
break;
default:
break;
}
}
}
SharedPreferencesUtils的代码如下:
package com.itachi.android.sharedpreferencesdemo.util;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesUtils {
private static final String APPLICATION_NAME = "SharedPreferencesDemo";
public static SharedPreferences getCurrentApplicationSharedPreferences(Context context, int mode) {
return context.getSharedPreferences(APPLICATION_NAME, mode);
}
public static SharedPreferences getCurrentActivityPreferences(Activity activity, int mode) {
// 调用的是Activity类的getPreferences方法
return activity.getPreferences(mode);
}
public static SharedPreferences getPreferencesByName(Context context, String name, int mode) {
// 调用的是Context类的getPreferences方法
return context.getSharedPreferences(name, mode);
}
}
先来分析一下SharedPreferencesUtils这个工具类的方法,其中分别调用了Activity类的getPreferences方法和Context类的getSharedPreferences方法来返回我们所需要的SharedPreferences对象。
我们先看一下ContextImpl中实现的getSharedPreferences(String name, int mode)方法(Context只是个抽象类,真正实现是在ContextImpl类中),如下:
@Override
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 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;
}
具体细节就不仔细展开了,可以看到getSharedPreferences(String name, int mode)方法以我们传递进去的文件名创建了需要的xml文件,并调用getSharedPreferences(File file, int mode)方法返回我们需要的SharedPreferences对象。
我们再来看一下Activity类的getPreferences(int mode)方法,如下:
/**
* Retrieve a {@link SharedPreferences} object for accessing preferences
* that are private to this activity. This simply calls the underlying
* {@link #getSharedPreferences(String, int)} method by passing in this activity's
* class name as the preferences name.
*
* @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default
* operation.
*
* @return Returns the single SharedPreferences instance that can be used
* to retrieve and modify the preference values.
*/
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
可以看到最终也是通过调用Context类的getSharedPreferences(String name, int modef)方法(Activity类实现了Context类)来返回SharedPreferences对象,只不过它默认传递了当前的Activity名称作为底层文件名。并且从这个方法的注释上我们也能看出,getPreferences方法返回了一个当前Activity私有的SharedPreferences对象,它的实现方式就是通过将当前Activity名称作为Preferences的名称。
再来看一下Demo中写了什么吧,我们在SharedPreferencesActivity中分别定义了三个按钮,将Username和Age写入到当前Activity私有的Preferences和两个共享的Preferences中。并且在OtherActivity中通过直接将SharedPreferencesActivity作为文件名获取SharedPreferences,来访问到SharedPreferencesActivity私有的Preferences,验证了前面分析的Context和Activity类的代码。
Demo的演示效果如下:
通过Android Studio的Device File Explorer我们也可以看到,SharedPreferences默认被创建在"data/data/<应用包名>/shared_prefs/"目录下,如下图:
文件内容如下:
对于SharedPreferences,它的使用是非常简单的,总结来说有以下几个特点,
1.轻量级,基于XML文件存储,只支持Java基本数据类型。
2.不支持多进程,其实关于这个,我觉得并不严谨,只能说它的使用初衷本就不是为了解决跨进程通信,只是为了解决一些简单的数据共享问题。且也可以另辟蹊径来在不同进程中访问,通过Context.createPackageContext(String packageName, int flags)方法来创建其他进程的上下文对象,进而访问其他进程的SharedPreferences文件。
3.数据量过大会影响它的读写性能,这个没有具体研究,只是看到书中和其他博主有提过,后面如果有工作需求能够深入了解的话,会再来分析这个问题。