当密钥包含换行符时,Android中的SharedReference不会持久化到磁盘
在Android中,我想编写当密钥包含换行符时,Android中的SharedReference不会持久化到磁盘,android,sharedpreferences,Android,Sharedpreferences,在Android中,我想编写SharedReferences键值对,其中键是Base64字符串 // get a SharedPreferences instance SharedPreferences prefs = getSharedPreferences("some-name", Context.MODE_PRIVATE); // generate the base64 key String someKey = new String(Base64.encode("some-key".get
SharedReferences
键值对,其中键是Base64字符串
// get a SharedPreferences instance
SharedPreferences prefs = getSharedPreferences("some-name", Context.MODE_PRIVATE);
// generate the base64 key
String someKey = new String(Base64.encode("some-key".getBytes("UTF-8"), Base64.URL_SAFE), "UTF-8");
// write the value for the generated key
prefs.edit().putBoolean(someKey, true).commit();
在最后一行中,commit调用返回true
。因此,该键值对应该已成功保存
当我关闭并销毁使用此段代码的活动
,然后重新创建活动
(再次运行此代码)时,将为我们使用的密钥返回指定的值
但事实证明,当我销毁整个应用程序/流程时(例如,在应用程序设置中使用“强制停止”),我们的密钥值在下次启动活动时丢失
当我不使用Base64.URL_SAFE
而是使用Base64.URL_SAFE | Base64.NO_WRAP
作为Base64编码的标志时,它可以正常工作
所以这个问题是由Base64键末尾的换行引起的。像abc
这样的键可以毫无问题地编写。但是当键为abc\n
时,它会失败
问题是,它首先似乎可以正常工作,在commit()
上返回true
,然后在后续调用中返回正确的首选项值。但是当整个应用程序被销毁并重新启动时,该值并没有被持久化
这是故意的行为吗?虫子?文档中是否提到了有效的键名?我查看了GrepCode,发现操作如下(我没有提到无用的操作):
第一:如何转换数据? 方法
XmlUtils.writeValueXml
将对象值写入XML标记中,属性name
设置为字符串值。此字符串值正好包含您在SharedReference名称处指定的值
(我通过对您的代码进行一步一步的调试来确认这一点)
XML将具有一个未转义的换行字符。实际上,XmlSerializer实例是一个FastXmlSerializer实例,它不会转义\n
字符(如果您想阅读源代码,请参阅末尾的此类链接)
有趣的代码:
writeValueXml(Object v, String name, XmlSerializer out) {
// -- "useless" code skipped
out.startTag(null, typeStr);
if (name != null) {
out.attribute(null, "name", name);
}
out.attribute(null, "value", v.toString());
out.endTag(null, typeStr);
// -- "useless" code skipped
}
writeToFile(MemoryCommitResult mcr) {
// -- "useless" code skipped
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Libcore.os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// -- "useless" code skipped
}
第二:为什么结果是真的?
提交方法具有以下代码:
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
因此,它返回mcr.writeToDiskResult
,该结果在SharedReferencesImpl.writeToFile(MemoryCommitResult)
方法中设置。有趣的代码:
writeValueXml(Object v, String name, XmlSerializer out) {
// -- "useless" code skipped
out.startTag(null, typeStr);
if (name != null) {
out.attribute(null, "name", name);
}
out.attribute(null, "value", v.toString());
out.endTag(null, typeStr);
// -- "useless" code skipped
}
writeToFile(MemoryCommitResult mcr) {
// -- "useless" code skipped
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Libcore.os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// -- "useless" code skipped
}
正如我们在前面所看到的:XML写入是“ok”(不要抛出任何东西,不要失败),因此文件中的同步也会是一样的(只是另一个流中的一个副本,这里没有任何东西检查XML内容!)
当前:您的密钥已转换为(格式错误)XML并正确写入文件。整个操作的结果是true
,因为一切正常。您所做的更改将提交到磁盘和内存中
第三点也是最后一点:为什么我第一次返回正确的值,第二次返回错误的值
快速查看一下在SharedReferences.Editor.commitToMemory(…)
方法(仅有趣的部分…:)中将更改提交到内存时发生的情况:
我们正在从mMap
取回密钥(暂时不读取文件中的值)。因此,我们得到了这次的正确值:)
重新加载应用程序时,将从磁盘加载数据,因此将调用SharedReferencesImpl
构造函数,它将调用SharedReferencesImpl.loadFromDiskLocked()
方法。此方法将读取文件内容并将其加载到mMap
属性中(我让您自己查看代码,最后提供了链接)
一步一步的调试告诉我,abc\n
被写为abc
(带有空格字符)。所以,当你试图找回它时,你永远不会成功
最后,感谢@commonware给我一个关于注释中文件内容的提示:) 链接
您是否查看了实际的
shared\u prefs/
XML文件以查看存储了什么?您使用“some key”
字符串值而不是最后一行中的some key
变量值是否有误?@mithrop是的,已修复。非常感谢。哇,干得好!非常感谢你!看来这是Android类中的一个bug,对吧?在将密钥写入XML之前,可能应该对其进行转义,以便以后读取时使用相同的格式。是的,这似乎是一个bug。如果不允许使用特殊字符,他们应该在文档中告诉我们。也许“FastXmlSerializer”的检查速度太快了:)很乐意帮忙;)