编写单例类以管理Android SharedPreferences

17

我正在尝试编写一个单例类,以监督涉及共享首选项的所有操作。

我有三个首选项文件:general、settings 和 temp

我希望能够使用这个类来写入给定类型的首选项,例如:

stg_full_screen: true // as boolean

以下是我目前所完成的:

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;

public class SharedPrefManager extends Activity {

    // Globals
    private int GENERAL             = 1000;
    private int SETTINGS            = 2000;
    private int TEMP_STORE          = 3000;

    private String PREF_GENERAL     = "com.example.general";
    private String PREF_SETTINGS    = "com.example.settings";
    private String PREF_TEMP_STORE  = "com.example.temp_store";


    private SharedPreferences general;
    private SharedPreferences settings;
    private SharedPreferences tempStore;

    private SharedPreferences.Editor general_editor;
    private SharedPreferences.Editor settings_editor;
    private SharedPreferences.Editor temp_store_editor;





    // Instantiate singleton object
    private static SharedPrefManager ourInstance = new SharedPrefManager();


    public static SharedPrefManager getInstance() { return ourInstance; }

    private SharedPrefManager() {
        // Get handle on all preference files
        general   = getSharedPreferences(PREF_GENERAL, Context.MODE_PRIVATE);
        settings  = getSharedPreferences(PREF_SETTINGS, Context.MODE_PRIVATE);
        tempStore = getSharedPreferences(PREF_TEMP_STORE, Context.MODE_PRIVATE);

        // provision editors for all preference files
        general_editor    = general.edit();
        settings_editor   = settings.edit();
        temp_store_editor = tempStore.edit();
    }



    private String read_prefs (String pref_name) {
        // this method reads a preference and returns it
        // ideally, i would want to be able to return appropriate types by request
        // e.g boolean, string
        return null;
    }

    private void write_prefs (String pref_name, String pref_val) {
        // this method would take a preference and write the appropriate type to prefs
    }


    // this method determines where to put a preference by checking the name of the key
    // this works because i use the following naming conventions
    // stg_name for settings, tmp_name for all that goes into tempStore

    private String resolve_pref_category (String path) {
        if (path.startsWith("stn"))         return PREF_SETTINGS;
        else if (path.startsWith("tmp"))    return PREF_TEMP_STORE;
        else                                return PREF_GENERAL;
    }

}

我的问题是:

  1. 这样做明智吗?
  2. 如何有效地确定首选项的类型?

谢谢


2
“这样做明智吗?” - 不!Android的“Activity”类是一个特殊的Java类,您不应该创建静态字段或方法并期望它们正常工作。Activity旨在作为UI框架 - 换句话说,如果您不需要UI,则不要使用Activity。尽管可以创建一个扩展SharedPreferences功能的类,但不要使用Activity来完成它。 - Squonk
1
我强烈建议研究一下AndroidAnnotations——我非常喜欢他们优雅的共享偏好管理。它似乎可以做到你想做的事情,但几乎不需要任何代码。 - jkraybill
@jkraybill AndroidAnnotations 看起来非常有趣,我只是想知道它是否存在任何已知的缺点? 由于我还没有深入研究过它的工作原理,因此我会感激任何高层次的见解。谢谢。 - SamAko
谢谢,我喜欢我所看到的,我觉得我已经着迷了 :) - SamAko
我在寻找一个好的指南来使用Gradle构建系统在Android Studio中进行设置,你知道有哪些吗? - SamAko
显示剩余2条评论
7个回答

39

一个合适的单例共享首选项类,它可能会在将来帮助其他人。

public class SharedPref
{
    private static SharedPreferences mSharedPref;
    public static final String NAME = "NAME";
    public static final String AGE = "AGE";
    public static final String IS_SELECT = "IS_SELECT";

    private SharedPref()
    {

    }

    public static void init(Context context)
    {
        if(mSharedPref == null)
            mSharedPref = context.getSharedPreferences(context.getPackageName(), Activity.MODE_PRIVATE);
    }

    public static String read(String key, String defValue) {
        return mSharedPref.getString(key, defValue);
    }

    public static void write(String key, String value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static boolean read(String key, boolean defValue) {
        return mSharedPref.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return mSharedPref.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putInt(key, value).commit();
    }
}

只需在MainActivity上调用SharedPref.init()。

SharedPref.init(getApplicationContext());

写入数据

    SharedPref.write(SharedPref.NAME, "XXXX");//save string in shared preference.
    SharedPref.write(SharedPref.AGE, "25");//save int in shared preference.
    SharedPref.write(SharedPref.IS_SELECT, true);//save boolean in shared preference.

读取数据

    String name = SharedPref.read(SharedPref.NAME, null);//read string in shared preference.
    String age = SharedPref.read(SharedPref.AGE, 0);//read int in shared preference.
    String isSelect = SharedPref.read(SharedPref.IS_SELECT, false);//read boolean in shared preference.

输出

Name : "XXXX";
Age : "25";
IsSelect : "true";

4
最好传递应用程序的上下文而不是活动的上下文。即,使用getApplicationContext()。 - Magesh Pandian
太棒了的回答! - spectre10
@M.Yogeshwaran 对于回复延迟表示抱歉。由于所有变量和函数都是静态的,因此您只需要在应用程序中初始化一次即可。 - spectre10
如何在多个共享首选项中使用它?谢谢。 - Danish Ansari

31
通常我会用这个:
没有静态Context的引用,每个属性都有静态的getter/setter方法,必要时你可以添加内存缓存来更快地从内存中获取某些属性,而不是从SharedPreferences读取。 API清晰易懂。
public class SharedPreferencesManager {

    private static final String APP_SETTINGS = "APP_SETTINGS";


    // properties
    private static final String SOME_STRING_VALUE = "SOME_STRING_VALUE";
    // other properties...


    private SharedPreferencesManager() {}

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(APP_SETTINGS, Context.MODE_PRIVATE);
    }

    public static String getSomeStringValue(Context context) {
        return getSharedPreferences(context).getString(SOME_STRING_VALUE , null);
    }

    public static void setSomeStringValue(Context context, String newValue) {
        final SharedPreferences.Editor editor = getSharedPreferences(context).edit();
        editor.putString(SOME_STRING_VALUE , newValue);
        editor.commit();
    }

    // other getters/setters
}

1
共享一个SharedPreferences实例而不是每次调用getSharedPreferences()怎么样?这可靠吗? - Daniele Ricci
1
这太棒了,比到处使用四行代码要容易得多。谢谢! - Jake
我也使用Kotlin扩展函数相同的模式。请查看此文章https://arkapp.medium.com/how-to-use-shared-preferences-the-easy-and-fastest-way-98ce2013bf51 - abdul rehman

24

Kotlin 解决方案:

object PrefsHelper {

    private lateinit var prefs: SharedPreferences

    private const val PREFS_NAME = "params"

    const val ID_USER = "id_user"
    const val TOKEN = "token"

    fun init(context: Context) {
        prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
    }

    fun read(key: String, value: String): String? {
        return prefs.getString(key, value)
    }

    fun read(key: String, value: Long): Long? {
        return prefs.getLong(key, value)
    }

    fun write(key: String, value: String) {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            putString(key, value)
            commit()
        }
    }

    fun write(key: String, value: Long) {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            putLong(key, value)
            commit()
        }
    }
}

在第一次运行应用程序时调用 init() 函数。


3

Kotlin示例,使用anrdoidx的security-crypto库(最低API 23)实现双重用途的加密和非加密共享首选项。


我使用Dagger2将其注入为@Singleton,以满足需要。
在Dagger模块中使用@Name注释来区分SharedPreferences实例,您可以拥有两个不同的.xml文件(一个加密,一个未加密)进行读/写操作。

在 build.gradle 中添加依赖项:

implementation "androidx.security:security-crypto:1.0.0-beta01"


import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences

class Prefs(prefsName: String, context: Context) {

    private lateinit var ANDX_SECURITY_KEY_KEYSET: String
    private lateinit var ANDX_SECURITY_VALUE_KEYSET: String
    private lateinit var cntext: Context
    private lateinit var prefName: String

    private lateinit var prefs: SharedPreferences

    constructor(
        prefsName: String,
        context: Context,
        masterKeyAlias: String,
        prefKeyEncryptionScheme: EncryptedSharedPreferences.PrefKeyEncryptionScheme,
        prefValueEncryptionScheme: EncryptedSharedPreferences.PrefValueEncryptionScheme
    ): this(prefsName, context) {
        ANDX_SECURITY_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__"
        ANDX_SECURITY_VALUE_KEYSET =    "__androidx_security_crypto_encrypted_prefs_value_keyset__"
        cntext = context
        prefName = prefsName
        prefs =
            EncryptedSharedPreferences.create(
                prefsName,
                masterKeyAlias,
                context,
                prefKeyEncryptionScheme,
                prefValueEncryptionScheme
            )
    }

    init {
        if (!::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            prefs =
                context.getSharedPreferences(
                    prefsName,
                    Context.MODE_PRIVATE
                )
        }
    }

    companion object {
        const val INVALID_BOOLEAN: Boolean = false
        const val INVALID_FLOAT: Float = -11111111111F
        const val INVALID_INT: Int = -1111111111
        const val INVALID_LONG: Long = -11111111111L
        const val INVALID_STRING: String = "INVALID_STRING"
        val INVALID_STRING_SET: Set<String> = setOf(INVALID_STRING)
    }

    /**
     * OnChangeListener
     * */
    fun registerOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.registerOnSharedPreferenceChangeListener(listener)

    fun unregisterOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.unregisterOnSharedPreferenceChangeListener(listener)

    /**
     * Read Shared Prefs
     * */
    fun contains(key: String): Boolean =
        prefs.contains(key)

    fun getAll(): Map<String, *> =
        prefs.all

    // Returns null if the Boolean value is not in
    //  Shared Preferences
    fun read(key: String): Boolean? =
        if (contains(key)) {
            read(key, INVALID_BOOLEAN)
        } else {
            null
        }

    // Boolean
    fun read(key: String, returnIfInvalid: Boolean): Boolean =
        prefs.getBoolean(key, returnIfInvalid)

    // Float
    fun read(key: String, returnIfInvalid: Float): Float =
        prefs.getFloat(key, returnIfInvalid)

    // Int
    fun read(key: String, returnIfInvalid: Int): Int =
        prefs.getInt(key, returnIfInvalid)

    // Long
    fun read(key: String, returnIfInvalid: Long): Long =
        prefs.getLong(key, returnIfInvalid)

    // Set<String>
    fun read(key: String, returnIfInvalid: Set<String>): Set<String>? =
        prefs.getStringSet(key, returnIfInvalid)

    // String
    fun read(key: String, returnIfInvalid: String): String? =
        prefs.getString(key, returnIfInvalid)

    /**
     * Modify Shared Prefs
     * */
    fun clear() {
        if (::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            val clearTextPrefs = cntext.getSharedPreferences(prefName, Context.MODE_PRIVATE)
            val keyKeyset = clearTextPrefs.getString(ANDX_SECURITY_KEY_KEYSET, INVALID_STRING)
            val valueKeyset = clearTextPrefs.getString(ANDX_SECURITY_VALUE_KEYSET, INVALID_STRING)
            if (keyKeyset != null && keyKeyset != INVALID_STRING
                && valueKeyset != null && valueKeyset != INVALID_STRING) {
                if (!clearTextPrefs.edit().clear().commit()) {
                    clearTextPrefs.edit().clear().apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).apply()
                }
            }
        } else {
            if (!prefs.edit().clear().commit()) {
                prefs.edit().clear().apply()
            }
        }
    }

    fun remove(key: String) {
        if (!prefs.edit().remove(key).commit()) {
            prefs.edit().remove(key).apply()
        }
    }

    // Boolean
    fun write(key: String, value: Boolean) {
        if (!prefs.edit().putBoolean(key, value).commit()) {
            prefs.edit().putBoolean(key, value).apply()
        }
    }

    // Float
    fun write(key: String, value: Float) {
        if (!prefs.edit().putFloat(key, value).commit()) {
            prefs.edit().putFloat(key, value).apply()
        }
    }

    // Int
    fun write(key: String, value: Int) {
        if (!prefs.edit().putInt(key, value).commit()) {
            prefs.edit().putInt(key, value).apply()
        }
    }

    // Long
    fun write(key: String, value: Long) {
        if (!prefs.edit().putLong(key, value).commit()) {
            prefs.edit().putLong(key, value).apply()
        }
    }

    // Set<String>
    fun write(key: String, value: Set<String>) {
        if (!prefs.edit().putStringSet(key, value).commit()) {
            prefs.edit().putStringSet(key, value).apply()
        }
    }

    // String
    fun write(key: String, value: String) {
        if (!prefs.edit().putString(key, value).commit()) {
            prefs.edit().putString(key, value).apply()
        }
    }
}

除了使用Dagger2注入为@Singleton的替代选项可能是:

AppPrefs.kt

object AppPrefs {
    lateinit var encryptedPrefs: Prefs
    lateinit var prefs: Prefs

    // Add your key strings here...

    fun initEncryptedPrefs(context: Context) {
        encryptedPrefs =
            Prefs(
                "ENCRYPTED_PREFS",
                context,
                MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )
    }

    fun initPrefs(context: Context) {
        prefs = Prefs("PREFS", context)
    }
}

Application.kt

class Application: Application() {
    override fun onCreate() {
        super.onCreate()
        AppPrefs.initEncryptedPrefs(this.applicationContext)
        AppPrefs.initPrefs(this.applicationContext)
    }
}

然后只需要从任何地方调用AppPrefs.prefsAppPrefs.encryptedPrefs


1

使用ApplicationContext的SharedPreferences单例

public class SharedPrefsSingleton {

    private SharedPreferences sharedPref;
    private Context appContext;

    private static SharedPrefsSingleton instance;

    public static synchronized SharedPrefsSingleton getInstance(Context applicationContext){
        if(instance == null)
            instance = new SharedPrefsSingleton(applicationContext);
        return instance;
    }

    private SharedPrefsSingleton(Context applicationContext) {
        appContext = applicationContext;
        sharedPref = appContext.getSharedPreferences(
                appContext.getString(R.string.key_pref), Context.MODE_PRIVATE );
    }

    public void writeData(float value) {
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putFloat(appContext.getString(R.string.key_data), value);
        editor.apply();
    }

    public float readData() {
        return sharedPref.getFloat(appContext.getString(R.string.key_data), 0);
    }
}

1
我提出了自己的版本,从Max Zonov的回答开始。 我创建了一个抽象类,实现读写操作,独立于特定项目,因此适合在“核心”模块中定义。
import android.content.SharedPreferences

abstract class SharedPreferenceWrapper {

    protected lateinit var prefs: SharedPreferences

    fun isInitialized() = ::prefs.isInitialized

    open fun read(key: String, defValue: Any): Any? {
        return when(defValue){
            is String   -> prefs.getString(key, defValue)
            is Int      -> prefs.getInt(key, defValue)
            is Boolean  -> prefs.getBoolean(key, defValue)
            is Long     -> prefs.getLong(key, defValue)
            is Float    -> prefs.getFloat(key, defValue)
            is Set<*>   -> {
                        if(defValue.isNotEmpty() && defValue.elementAt(0) is String)    prefs.getStringSet(key, defValue as Set<String>)
                        else                                                            return null
            }
            else        -> null
        }
    }

    open fun write(key: String, value: Any):Any? {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            when(value){
                is String   -> putString(key, value)
                is Int      -> putInt(key, value)
                is Boolean  -> putBoolean(key, value)
                is Long     -> putLong(key, value)
                is Float    -> putFloat(key, value)
                is Set<*>   -> {
                                if(value.isNotEmpty() && value.elementAt(0) is String)    putStringSet(key, value as Set<String>)
                                else                                                            return null
                }
                else        -> return null
            }
            commit()
        }
        return value
    }
}

在我的具体项目中,我创建了一个单例,初始化先前定义的SharedPreference,并对给定的键进行了一次健全性检查。
import android.content.Context
import androidx.preference.PreferenceManager
import org.albaspazio.core.sharedpreferences.SharedPreferenceWrapper


object PsySuitePreferences: SharedPreferenceWrapper() {

    private val validKeys:List<String> = listOf(
        "pref_delay_a1", "pref_delay_a2", "pref_delay_a3",
        "pref_delay_t1", "pref_delay_t2",
        "pref_delay_v1", "pref_delay_v2",
        "pref_main_email")


    //call it once
    fun init(context:Context, pref_name:String="", mode:Int = Context.MODE_PRIVATE){

        if(isInitialized()) return      // prevent multiple init

        prefs = if(pref_name.isEmpty()) PreferenceManager.getDefaultSharedPreferences(context)
                else                    context.getSharedPreferences(pref_name, mode)
    }

    override fun read(key: String, defValue: Any): Any?{
        return  if(!validKeys.contains(key) || !isInitialized()) null
                else super.read(key, defValue)
    }

    override fun write(key: String, value: Any): Any?{
        return  if(!validKeys.contains(key) || !isInitialized())    null
                else super.write(key, value)
    }
}

ciao


1

import android.content.Context; import android.content.SharedPreferences;

import com.example.day.weeklypapers.model.LoginModel; import com.google.gson.Gson;

WeeklyPreference类:

private static WeeklyPreference mInstance = null;
private static SharedPreferences mPreferences;
private static SharedPreferences.Editor mEditor;
private Context context;
private static String SharedPreferenceKey = "Weekly"   ;

private WeeklyPreference() {

}

public static WeeklyPreference getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new WeeklyPreference();
    }
    if (mPreferences == null) {
        mPreferences = context.getApplicationContext().getSharedPreferences(SharedPreferenceKey, Context.MODE_PRIVATE);
        mEditor = mPreferences.edit();
        mEditor.commit();
    }
    return mInstance;
}

public void saveInPreference(String key, String value) {
    mEditor.putString(key, value);
    mEditor.commit();
}

public String getFromPreference(String key) {
    return mPreferences.getString(key, "");
}

public LoginModel getUserDetails() {
    String userJson = mPreferences.getString(PrefrenceConstants.KEY_USER_JSON_DETAILS, "");
    LoginModel user = null;
    if (userJson != null && !userJson.equals("")) {
        user = new Gson().fromJson(userJson, LoginModel.class);
    }
    return user;
}

public void saveUserDetails(LoginModel user) {
    mEditor.putString(PrefrenceConstants.KEY_USER_JSON_DETAILS, new Gson().toJson(user));
    mEditor.commit();
}

public boolean isAutoLogin() {
    String userJson = mPreferences.getString(PrefrenceConstants.KEY_USER_JSON_DETAILS, "");
    LoginModel user = null;
    if (userJson != null && !userJson.equals("")) {
        user = new Gson().fromJson(userJson, LoginModel.class);
        return user != null;
    } else {
        return false;
    }
}

}

你可以直接将POJO类或Model类传递给这个单例类,以减少你的工作量。
注意: 保重自己, 愉快地使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接