共享preferences在Android中并不持久保存到磁盘时,项包含换行符并不、磁盘、持久、换行符

2023-09-05 03:51:27 作者:海蓝透了天

在Android的,我想写共享preferences 键值对,其中键是Base64的字符串。

In Android, I'd like to write SharedPreferences key-value pairs where the keys are Base64 strings.

// 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返回。所以这个键值对应该已经保存成功。

In the last line, the call to commit returns true. So this key-value pair should have been saved successfully.

当我关闭和销毁活动,其中这片code的使用,然后重新创建活动(再次运行此code),则返回了我们使用的键指定的值。

When I close and destroy the Activity where this piece of code was used and then re-create the Activity (running this code again), the specified value is returned for the key that we used.

但事实是,当我毁掉整个应用程序/程序(例如,使用强制停机的应用程序设置),为我们的客户价值是失去了对下一个推出的活动的

But it turns out that, when I destroy the whole application/process (e.g. using "Force stop" in app settings), the value for our key is lost on the next launch of the Activity.

当我不使用 Base64.URL_SAFE ,但 Base64.URL_SAFE | Base64.NO_WRAP 为标志的Base64编码,它工作正常。

When I don't use Base64.URL_SAFE but Base64.URL_SAFE | Base64.NO_WRAP as the flags for the Base64 encoding, it works fine.

所以这个问题已经引起新行,在所述的Base64键的末端。如农行键可以写,没有任何问题。但是,当最关键的是 ABC \ñ,它失败。

So this problem has been caused by the newlines at the end of the Base64 keys. Keys like abc can be written without any problems. But when the key is abc\n, it fails.

现在的问题是,它似乎没有问题,工作第一,返回提交()并返回在随后的调用正确的preference值。但是,当整个应用程序被破坏,重新开始,该值没有被保留。

The problem is that it appears to work without problems first, returning true on commit() and returning the correct preference value on subsequent calls. But when the whole application is destroyed and re-started, the value has not been persisted.

这是预期的行为?一只虫子?是否文档说的有效键名什么?

Is this intended behaviour? A bug? Does the documentation say anything about valid key names?

推荐答案

我看了一下grep的code和看到的操作将是以下(我不提没用的):

I took a look at GrepCode and see that the operations will be the following (I do not mention useless ones) :

android.app.Shared preferencesImpl.commit() android.app.Shared preferencesImpl.commitToMemory()

android.app.Shared preferencesImplen.queueDiskWrite(MemoryCommitResult,可运行) android.app.SharedPreferencesImpl.commit() android.app.SharedPreferencesImpl.commitToMemory()

android.app.SharedPreferencesImplen.queueDiskWrite(MemoryCommitResult,Runnable)

3.1。 XmlUtils.writeMapXml(地图,为OutputStream)

3.1. XmlUtils.writeMapXml(Map, OutputStream)

3.2。 XmlUtils.writeMapXml(地图,字符串,XmlSerializer的)

3.2. XmlUtils.writeMapXml(Map, String, XmlSerializer)

3.3。 XmlUtils.writeValueXml(宾语,字符串名称,XmlSerializer的SER)

方法 XmlUtils.writeValueXml 写入的对象值的XML标签与属性名称设置为字符串值。该字符串值包含的完全的你的共享preference名指定的值。

First : how your data are converted ?

The method XmlUtils.writeValueXml writes the Object value in a XML tag with the attribute name set to the String value. This String value contains exactly the value you specified at the SharedPreference's name.

(我做了一步一步的调试与你一张code证实了这一点)。

(And I confirmed this by doing a step-by-step debug with your piece of code).

XML将成为与转义换行符。事实上,XmlSerializer的实例是一个FastXmlSerializer实例,它并没有逃脱字符(见链接,这个班结束时,如果你想读的来源code)

The XML will be with an unescaped line break character. Actually, the XmlSerializer instance is a FastXmlSerializer instance and it does not escape the \n character (see the link for this class at the end if you want to read the source code)

有趣的一张code:

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
}

二:为什么结果是真实的

commit方法有以下code:

Second : why the result is true ?

android中SharedPreferences查看存储的数据

The commit method has the following code :

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 这是在设置共享preferencesImpl.writeToFile(MemoryCommitResult)方法。有趣的一块code:

So it returns the mcr.writeToDiskResult which is set in the SharedPreferencesImpl.writeToFile(MemoryCommitResult) method. Interesting piece of code :

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
}

正如我们在previous点看:XML写的是OK(不要扔东西,不失败),所以文件中的同步将是一个流的另一种太(只是一个副本之一,没有检查XML内容在这里!)。

As we see at the previous point : the XML writing is "ok" (do not throw anything, do not fails), so the sync in the file will be too (just a copy of a Stream in another one, nothing checks the XML content here !).

目前:你的关键转化成(格式错误),XML和正确地写在文件中。整个操作的结果是作为一切正常。你改变comitted磁盘,并在内存中

Currently : your key was converted to (badly formatted) XML and correctly wrote in a File. The result of the whole operation is true as everything went OK. Your changes are comitted to the disk and in the memory.

快速浏览一下的时候,我们犯共享preferences.Editor.commitToMemory(...)办法(唯一感兴趣的部分更改为内存发生什么... :)):

Take a quick look to what happen when we commit the changes to memory in SharedPreferences.Editor.commitToMemory(...) method (interesting part only... :)):

for (Map.Entry<String, Object> e : mModified.entrySet()) {
    String k = e.getKey();
    Object v = e.getValue();
    if (v == this) {  // magic value for a removal mutation
        if (!mMap.containsKey(k)) {
            continue;
        }
        mMap.remove(k);
    } else {
        boolean isSame = false;
        if (mMap.containsKey(k)) {
            Object existingValue = mMap.get(k);
            if (existingValue != null && existingValue.equals(v)) {
                continue;
            }
        }
        mMap.put(k, v);
    }

    mcr.changesMade = true;
    if (hasListeners) {
        mcr.keysModified.add(k);
    }
}

重要的一点:这些更改将提交给 MMAP 属性

然后,我们快速浏览一下我们如何得到一个值:

Then, take a quick look to how we get back a value :

public boolean getBoolean(String key, boolean defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        Boolean v = (Boolean)mMap.get(key);
        return v != null ? v : defValue;
    }
}

我们正在回程从 MMAP 键(不读取文件中的价值现在)。因此,我们必须为这一次的正确值:)

We are taking back the key from mMap (no reading of the value in the file for now). So we have the correct value for this time :)

当您重新加载应用程序时,将加载回数据从磁盘,因此共享preferencesImpl 构造函数被调用,它将调用共享preferencesImpl.loadFromDiskLocked()方法。这种方法将读取该文件的内容,并加载到 MMAP 属性(我让你看到code自己,在最后提供的链接)。

When you reload your application, you will load the data back from the disk, and so the SharedPreferencesImpl constructor will be called, and it will call the SharedPreferencesImpl.loadFromDiskLocked() method. This method will read the file content and load it in the mMap attribute (I let you see the code by yourself, link provided at the end).

显示我一步一步调试了 ABC \ñ被写成农行(带空白字符)。所以,当你将尝试把它找回来,你永远不会成功。

A step-by-step debug shown me that the abc\n was written as abc (with a whitespace character). So, when you will try to get it back, you will never success.

要结束,谢谢@CommonsWare给我一个提示,有关注释的文件内容:)

To finish, thank you to @CommonsWare to give me a hint about the file content in the comment :)

链接

XmlUtils

FastXmlSerializer

共享preferencesImpl

共享preferencesImpl.EditorImpl.commit()

共享preferencesImpl.EditorImpl.commitToMemory()

共享preferencesImpl.enqueueDiskWrite(MemoryCommitResult,可运行)

共享preferencesImpl.writeToFile(MemoryCommitResult)

共享preferencesImpl.loadFromDiskLocked()