Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/209.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
当密钥包含换行符时,Android中的SharedReference不会持久化到磁盘_Android_Sharedpreferences - Fatal编程技术网

当密钥包含换行符时,Android中的SharedReference不会持久化到磁盘

当密钥包含换行符时,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

在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".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,发现操作如下(我没有提到无用的操作):

  • android.app.SharedReferencesImpl.commit()
  • android.app.SharedReferencesImpl.commitToMemory()
  • android.app.SharedReferencesImpl.queueDiskWrite(MemoryCommitResult,可运行)

    3.1。writeMapXml(映射,输出流)

    3.2。writeMapXml(映射、字符串、XmlSerializer)

    3.3XmlUtils.writeValueXml(对象v,字符串名称,XmlSerializer ser)


  • 第一:如何转换数据? 方法
    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”的检查速度太快了:)很乐意帮忙;)