Android - 使用可序列化对象的SharedPreferences

54
我知道SharedPreferences有putString()putFloat()putLong()putInt()putBoolean()方法,但我需要存储一个类型为Serializable的对象到SharedPreferences中。我该怎么做?
7个回答

51

被接受的答案是误导性的,我们可以使用GSON将可序列化对象存储到SharedPreferences中。在google-gson了解更多信息。

您可以在Gradle文件中添加以下依赖项:

compile 'com.google.code.gson:gson:2.7'

这是代码片段:

首先,创建您通常的sharedPreferences:

//Creating a shared preference
SharedPreferences  mPrefs = getPreferences(MODE_PRIVATE);

将可序列化对象保存到首选项:

 Editor prefsEditor = mPrefs.edit();
 Gson gson = new Gson();
 String json = gson.toJson(YourSerializableObject);
 prefsEditor.putString("SerializableObject", json);
 prefsEditor.commit();
从偏好设置获取可序列化的对象:
Gson gson = new Gson();
String json = mPrefs.getString("SerializableObject", "");
yourSerializableObject = gson.fromJson(json, YourSerializableObject.class);

1
使用Json是危险的。如果我们更改了数据结构,那么反序列化的数据将不匹配。 - Elye
1
@Elye,SharedPreferences 中存储的任何信息都与运行时更改不同步,就像在任何其他数据库中一样。作为程序员,您有责任在数据更改时进行存储和管理。一些数据库(如 Realm 和 Room)可能会通知您数据库中的更改,但如果数据已加载,则只有您有责任将其放回数据库中。 - Vitaliy A

48

简而言之,你不能这样做,尝试将你的对象序列化到一个私有文件中,它相当于同样的效果。示例类如下:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

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

/**
 *
 * Writes/reads an object to/from a private local file
 * 
 *
 */
public class LocalPersistence {


    /**
     * 
     * @param context
     * @param object
     * @param filename
     */
    public static void witeObjectToFile(Context context, Object object, String filename) {

        ObjectOutputStream objectOut = null;
        try {

            FileOutputStream fileOut = context.openFileOutput(filename, Activity.MODE_PRIVATE);
            objectOut = new ObjectOutputStream(fileOut);
            objectOut.writeObject(object);
            fileOut.getFD().sync();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (objectOut != null) {
                try {
                    objectOut.close();
                } catch (IOException e) {
                    // do nowt
                }
            }
        }
    }


    /**
     * 
     * @param context
     * @param filename
     * @return
     */
    public static Object readObjectFromFile(Context context, String filename) {

        ObjectInputStream objectIn = null;
        Object object = null;
        try {

            FileInputStream fileIn = context.getApplicationContext().openFileInput(filename);
            objectIn = new ObjectInputStream(fileIn);
            object = objectIn.readObject();

        } catch (FileNotFoundException e) {
            // Do nothing
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (objectIn != null) {
                try {
                    objectIn.close();
                } catch (IOException e) {
                    // do nowt
                }
            }
        }

        return object;
    }

}

你的代码中的 filename 应该是绝对路径(例如:"/data/com.example.myapp/foobar.dat")还是相对路径(例如:"foobar.dat")? - Matt Huggins
3
只需提供文件名,路径分隔符不允许,正如API文档中所注明的那样。具体请参考http://developer.android.com/reference/android/content/Context.html#openFileOutput(java.lang.String, int)。 - Chris.D
1
您好,您可以通过以下方法删除上述保存的文件吗? - Konstantin Konopko
我认为只需要使用context.getFileStreamPath(filename).delete()即可。 - Chris.D
只是出于好奇 - 您的FileInputStream和FileOutputStream应该在finally块中关闭,以及ObjectInputStream和ObjectOutputStream吗? - Tim Malseed
不关闭对象流将会同时关闭包装的流。 - Chris.D

20
如果您的对象是简单的POJO,您可以将对象转换为JSON字符串,并使用putString()保存在共享首选项中。

17

可以不使用文件完成此操作。

我将信息序列化为base64,然后将其保存为偏好设置中的字符串。

以下代码将可序列化对象串行化为base64字符串,反之亦然: import android.util.Base64;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class ObjectSerializerHelper {
    static public String objectToString(Serializable object) {
        String encoded = null;
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.close();
            encoded = new String(Base64.encodeToString(byteArrayOutputStream.toByteArray(),0));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return encoded;
    }

    @SuppressWarnings("unchecked")
    static public Serializable stringToObject(String string){
        byte[] bytes = Base64.decode(string,0);
        Serializable object = null;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream( new ByteArrayInputStream(bytes) );
            object = (Serializable)objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        return object;
    }

}

以上类可以粘贴到您的POJO类中,您可以轻松实现public YourPojo getFromPreferences(Context context)public void saveToPreferences(Context context),这两个方法使用了上述两个方法以及PreferenceManager.getDefaultSharedPreferences(context) - Dale

5
我们可以使用Kotlin创建易于使用的语法。
@Throws(JsonIOException::class)
fun Serializable.toJson(): String {
   return Gson().toJson(this)
}

@Throws(JsonSyntaxException::class)
 fun <T> String.to(type: Class<T>): T where T : Serializable {
 return Gson().fromJson(this, type)
}

@Throws(JsonIOException::class)
fun SharedPreferences.Editor.putSerializable(key: String, o: Serializable?) = apply {
   putString(key, o?.toJson())
}

@Throws(JsonSyntaxException::class)
   fun <T> SharedPreferences.getSerializable(key: String, type: Class<T>): T? where T : Serializable {
    return getString(key, null)?.to(type)
}

然后使用类似的 get/put() 方法将任何可序列化对象保存到SharedPreferences中。

完整的代码片段请见使用 Kotlin 和 GSON 在共享首选项中保存可序列化对象

如其他答案中所提到的,当数据类的结构发生变化时,您可能需要考虑迁移。或者至少必须更改用于存储的键。


1
Kotlin是前进的道路;) - Carnal

0

2020年:如果您想保存对象,那么最好使用Proto DataStore而不是SharedPreferences。

与“旧”的SharedPreferences相比,它带来了巨大的好处,包括:

  • 异步API用于存储和读取数据(Flow)
  • 开箱即用的类型安全性(Protocol Buffers)
  • 可以安全地从UI线程调用(底层为Dispatchers.IO)

还有许多其他优点,例如事务性API,可以保证一致性。

查看我的博客文章如何轻松实现Proto DataStore以了解更多信息。


0
如果您的对象具有嵌套对象并且您可能需要在某一天删除一个嵌套对象,那么这是一种不好的机制,因为您将需要实现自己的迁移策略(除非扔掉所有数据没问题)。
如果它是大量数据,则此机制缺乏事务支持。那么您是编写自己的代码?还是使用更合适的存储机制?我强烈建议使用更合适的存储机制,例如文件系统或SQLite数据库。

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