Android SharedPreferences最佳实践

时间:2012-01-13 18:00:07

标签: java android

在我一直在构建的应用程序中,我们依赖于SharedPreferences,这让我想到了访问SharedPreferences时的最佳实践。例如,许多人说通过这个电话来访问它的合适方式是:

PreferenceManager.getDefaultSharedPreferences(Context context)

然而,这似乎很危险。如果您有一个依赖于SharedPreferences的大型应用程序,则可能存在密钥重复,尤其是在使用依赖于SharedPreferences的某些第三方库的情况下。在我看来,更好的使用呼叫是:

Context.getSharedPreferences(String name, int mode)

这样,如果您的某个类严重依赖于SharedPreferences,则可以创建仅由您的类使用的首选项文件。您可以使用类的完全限定名称来确保该文件很可能不会被其他人复制。

同样基于这个SO问题:Should accessing SharedPreferences be done off the UI Thread?,似乎访问SharedPreferences应该在UI线程上完成,这是有意义的。

Android开发人员在其应用程序中使用SharedPreferences时是否应该注意其他最佳做法?

5 个答案:

答案 0 :(得分:83)

我写了一篇文章,也可以找到here。它描述了SharedPreferences是什么:

最佳实践:SharedPreferences

Android提供了许多存储应用程序数据的方法。其中一种方法将我们引向 SharedPreferences 对象,该对象用于将私有原始数据存储在键值对中。

所有逻辑仅基于三个简单的类:

SharedPreferences

SharedPreferences是其中的主要内容。它负责获取(解析)存储的数据,提供用于获取Editor对象和接口的接口,以添加和删除OnSharedPreferenceChangeListener

  • 要创建SharedPreferences,您需要Context个对象(可以是应用Context
  • getSharedPreferences方法解析首选项文件并为其创建Map对象
  • 您可以在Context提供的几种模式下创建它,强烈建议使用MODE_PRIVATE,因为创建世界可读/可写文件非常危险,并且可能会在应用程序中造成安全漏洞

    // parse Preference file
    SharedPreferences preferences = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
    
    // get values from Map
    preferences.getBoolean("key", defaultValue)
    preferences.get..("key", defaultValue)
    
    // you can get all Map but be careful you must not modify the collection returned by this
    // method, or alter any of its contents.
    Map<String, ?> all = preferences.getAll();
    
    // get Editor object
    SharedPreferences.Editor editor = preferences.edit();
    
    //add on Change Listener
    preferences.registerOnSharedPreferenceChangeListener(mListener);
    
    //remove on Change Listener
    preferences.unregisterOnSharedPreferenceChangeListener(mListener);
    
    // listener example
    SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener
        = new SharedPreferences.OnSharedPreferenceChangeListener() {
      @Override
      public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
      }
    };
    

编辑

SharedPreferences.Editor是用于修改SharedPreferences对象中的值的接口。您在编辑器中进行的所有更改都是批处理的,在您调用commit()或apply()之前不会复制回原始SharedPreferences

  • 使用简单的界面将值放入Editor
  • 保存与commit()同步或与apply同步的值,这更快。实际上使用commit()使用不同的线程更安全。这就是为什么我更喜欢使用 commit()
  • 使用remove()删除单个值或使用clear()

    清除所有值
    // get Editor object
    SharedPreferences.Editor editor = preferences.edit();
    
    // put values in editor
    editor.putBoolean("key", value);
    editor.put..("key", value);
    
    // remove single value by key
    editor.remove("key");
    
    // remove all values
    editor.clear();
    
    // commit your putted values to the SharedPreferences object synchronously
    // returns true if success
    boolean result = editor.commit();
    
    // do the same as commit() but asynchronously (faster but not safely)
    // returns nothing
    editor.apply();
    

表演&amp;提示

  • SharedPreferences是一个Singleton对象,因此您可以轻松获取所需数量的引用,只有在您第一次调用getSharedPreferences时才打开文件,或者只创建一个它的参考。

    // There are 1000 String values in preferences
    
    SharedPreferences first = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
    // call time = 4 milliseconds
    
    SharedPreferences second = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
    // call time = 0 milliseconds
    
    SharedPreferences third = context.getSharedPreferences("com.example.app", Context.MODE_PRIVATE);
    // call time = 0 milliseconds
    
  • 由于SharedPreferences是一个Singleton对象,您可以更改其中任何一个实例,并且不要害怕他们的数据会有所不同

    first.edit().putInt("key",15).commit();
    
    int firstValue = first.getInt("key",0)); // firstValue is 15
    int secondValue = second.getInt("key",0)); // secondValue is also 15
    
  • 请记住,Preference对象越大,getcommitapplyremoveclear操作就越长。因此,强烈建议将数据分成不同的小对象。

  • 应用程序更新后,您的偏好设置将不会被删除。因此,有些情况下您需要创建一些迁移方案。例如,你有Application在应用程序启动时解析本地JSON,只有在第一次启动后才决定保存boolean flag wasLocalDataLoaded。一段时间后,您更新了JSON并发布了新的应用程序版本。用户将更新他们的应用程序但他们不会加载新的JSON,因为他们已经在第一个应用程序版本中完成了它。

    public class MigrationManager {
     private final static String KEY_PREFERENCES_VERSION = "key_preferences_version";
     private final static int PREFERENCES_VERSION = 2;
    
     public static void migrate(Context context) {
         SharedPreferences preferences = context.getSharedPreferences("pref", Context.MODE_PRIVATE);
         checkPreferences(preferences);
     }
    
     private static void checkPreferences(SharedPreferences thePreferences) {
         final double oldVersion = thePreferences.getInt(KEY_PREFERENCES_VERSION, 1);
    
         if (oldVersion < PREFERENCES_VERSION) {
             final SharedPreferences.Editor edit = thePreferences.edit();
             edit.clear();
             edit.putInt(KEY_PREFERENCES_VERSION, currentVersion);
             edit.commit();
         }
     }
    }
    
  • SharedPreferences存储在应用数据文件夹

    中的xml文件中
    // yours preferences
    /data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PREFS_NAME.xml
    
    // default preferences
    /data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PACKAGE_NAME_preferences.xml
    

Android guide.

示例代码

public class PreferencesManager {

    private static final String PREF_NAME = "com.example.app.PREF_NAME";
    private static final String KEY_VALUE = "com.example.app.KEY_VALUE";

    private static PreferencesManager sInstance;
    private final SharedPreferences mPref;

    private PreferencesManager(Context context) {
        mPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    }

    public static synchronized void initializeInstance(Context context) {
        if (sInstance == null) {
            sInstance = new PreferencesManager(context);
        }
    }

    public static synchronized PreferencesManager getInstance() {
        if (sInstance == null) {
            throw new IllegalStateException(PreferencesManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }
        return sInstance;
    }

    public void setValue(long value) {
        mPref.edit()
                .putLong(KEY_VALUE, value)
                .commit();
    }

    public long getValue() {
        return mPref.getLong(KEY_VALUE, 0);
    }

    public void remove(String key) {
        mPref.edit()
                .remove(key)
                .commit();
    }

    public boolean clear() {
        return mPref.edit()
                .clear()
                .commit();
    }
}

答案 1 :(得分:39)

  

如果您有一个依赖于SharedPreferences的大型应用程序,则可能存在密钥重复,尤其是在使用某些依赖于SharedPreferences的第三方库的情况下。

图书馆不应该使用该特定SharedPreferences。默认SharedPreferences只应由应用程序使用。

  

这样,如果您有一个严重依赖于SharedPreferences的类,则可以创建仅由您的类使用的首选项文件。

当然欢迎你这样做。在应用程序级别,我不会认为SharedPreferences的主要原因是在应用程序中的组件之间共享它们。开发团队应该没有问题来管理这个命名空间,就像管理类,包,资源或其他项目级别的东西时应该没有问题。此外,默认SharedPreferences是您的PreferenceActivity将使用的。

但是,回到您的库点,可重用的库应该仅为其库使用单独的SharedPreferences。我不会将它建立在一个类名上,因为那样你就可以重新打破你的应用了。相反,选择一个唯一的名称(例如,基于库名称,例如"com.commonsware.cwac.wakeful.WakefulIntentService")但是稳定。

  

似乎访问SharedPreferences应该在UI线程上完成,这是有意义的。

理想情况下,是的。我最近发布了一个SharedPreferencesLoader来帮助解决这个问题。

  

Android开发人员在其应用程序中使用SharedPreferences时是否应该注意其他最佳做法?

不要过分依赖它们。它们存储在XML文件中,不是事务性的。数据库应该是您的主要数据存储,特别是对于您不想丢失的数据。

答案 2 :(得分:2)

这是我的方式

写作

SharedPreferences settings = context.getSharedPreferences("prefs", 0);
SharedPreferences.Editor editore = settings.edit();
editore.putString("key", "some value");
editore.apply();

阅读

SharedPreferences settings = getSharedPreferences("prefs", 0);
Strings value = settings.getString("key", "");

答案 3 :(得分:0)

让我们假设在一个项目中,有多个开发人员在其中工作,他们正在像这样的Activity中定义SharedPreference:

SharedPreferences sharedPref = context.getSharedPreferences("prefName", 0);

在某个时刻或另外两个开发人员可以使用相同的名称定义SharedPreference或插入等效的“键-值”对,这将导致使用键时出现问题。

该解决方案取决于是否使用两个选项;

  1. SharedPreferences使用字符串键的单例。

  2. SharedPreferences使用枚举键的单例。

个人并且根据此Sharepreference Documentation,我更喜欢使用Enum键,因为当有多个程序员在一个项目上工作时,它会执行更严格的控制。程序员别无选择,只能在适当的枚举类中声明一个新密钥,因此所有密钥都位于同一位置。

为避免编写样板代码,请创建SharedPreference单例。 SharedPreferences singleton Class有助于集中和简化Android应用中对SharedPreferences的读写。

两个提供的解决方案的源代码可以在GitHub

中找到

答案 4 :(得分:0)

在kotlin中,可以通过以下方式简化SharedPreferences的使用。

class Prefs(context: Context) {

    companion object {
        private const val PREFS_FILENAME = "app_prefs"

        private const val KEY_MY_STRING = "my_string"
        private const val KEY_MY_BOOLEAN = "my_boolean"
        private const val KEY_MY_ARRAY = "string_array"
    }

    private val sharedPrefs: SharedPreferences =
        context.getSharedPreferences(PREFS_FILENAME, Context.MODE_PRIVATE)

    var myString: String
        get() = sharedPrefs.getString(KEY_MY_STRING, "") ?: ""
        set(value) = sharedPrefs.edit { putString(KEY_MY_STRING, value) }

    var myBoolean: Boolean
        get() = sharedPrefs.getBoolean(KEY_MY_BOOLEAN, false)
        set(value) = sharedPrefs.edit { putBoolean(KEY_MY_BOOLEAN, value) }

    var myStringArray: Array<String>
        get() = sharedPrefs.getStringSet(KEY_MY_ARRAY, emptySet())?.toTypedArray()
            ?: emptyArray()
        set(value) = sharedPrefs.edit { putStringSet(KEY_MY_ARRAY, value.toSet()) }
  

此处,sharedPrefs.edit{...}由android核心ktx库提供,应通过在应用级别implementation "androidx.core:core-ktx:1.0.2"中添加依赖项build.gradle来实现。

您可以使用以下代码获取SharedPreferences的实例:

val prefs = Prefs(context)

此外,您可以创建Singleton的{​​{1}}对象并在应用程序中的任何位置使用。

Prefs

其中val prefs: Prefs by lazy { Prefs(App.instance) } 扩展了App,应包含在Application

App.kt

AndroidManifest.xml

AndroidManifest.xml

class App:Application() {
    companion object {
        lateinit var instance: App
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

用法示例:

<?xml version="1.0" encoding="utf-8"?>
<manifest .....

   <application
        android:name=".App"
        ....