android:如何持久存储一个跨越?

时间:2012-05-12 18:50:55

标签: android serialization parcelable spanned

我想持久保存Spanned对象。 (我现在正在保存基于持久性的String,但是在它上面运行Html.fromHtml()需要1秒钟以上,显着减慢了用户界面。)

我看到像ParcelableSpan和SpannedString以及SpannableString这样的东西,但我不确定使用哪个。

7 个答案:

答案 0 :(得分:6)

目前,Html.toHtml()是您唯一的内置选项。 Parcelable用于进程间通信,并不是设计为持久的。如果toHtml()未涵盖您正在使用的所有特定类型的跨度,则必须使用自己的序列化机制。

由于保存对象涉及磁盘I / O,无论如何都应该在后台线程中执行此操作,无论toHtml()的速度如何。

答案 1 :(得分:4)

我有类似的问题;我使用SpannableStringBuilder来保存字符串和一堆跨度,我希望能够保存和恢复此对象。我编写此代码以使用SharedPreferences手动完成此操作:

    // Save Log
    SpannableStringBuilder logText = log.getText();
    editor.putString(SAVE_LOG, logText.toString());
    ForegroundColorSpan[] spans = logText
            .getSpans(0, logText.length(), ForegroundColorSpan.class);
    editor.putInt(SAVE_LOG_SPANS, spans.length);
    for (int i = 0; i < spans.length; i++){
        int col = spans[i].getForegroundColor();
        int start = logText.getSpanStart(spans[i]);
        int end = logText.getSpanEnd(spans[i]);
        editor.putInt(SAVE_LOG_SPAN_COLOUR + i, col);
        editor.putInt(SAVE_LOG_SPAN_START + i, start);
        editor.putInt(SAVE_LOG_SPAN_END + i, end);
    }

    // Load Log
    String logText = save.getString(SAVE_LOG, "");
    log.setText(logText);
    int numSpans = save.getInt(SAVE_LOG_SPANS, 0);
    for (int i = 0; i < numSpans; i++){
        int col = save.getInt(SAVE_LOG_SPAN_COLOUR + i, 0);
        int start = save.getInt(SAVE_LOG_SPAN_START + i, 0);
        int end = save.getInt(SAVE_LOG_SPAN_END + i, 0);
        log.getText().setSpan(new ForegroundColorSpan(col), start, end, 
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

我的情况我知道所有的跨度都是ForegroundColorSpan类型和标志SPAN_EXCLUSIVE_EXCLUSIVE,但是这个代码可以很容易地适应其他类型。

答案 2 :(得分:2)

我的用例是关于将跨区域放入捆绑包中,Google将我带到了这里。 @CommonsWare是正确的,Parcelable对于持久存储没有好处,但它可以存储到Bundle中。大多数跨度似乎扩展了ParcelableSpan,所以这对我在onSaveInstanceState中起作用了:

ParcelableSpan spanObjects[] = mStringBuilder.getSpans(0, mStringBuilder.length(), ParcelableSpan.class);
int spanStart[] = new int[spanObjects.length];
int spanEnd[] = new int[spanObjects.length];
int spanFlags[] = new int[spanObjects.length];
for(int i = 0; i < spanObjects.length; ++i)
{
    spanStart[i] = mStringBuilder.getSpanStart(spanObjects[i]);
    spanEnd[i] = mStringBuilder.getSpanEnd(spanObjects[i]);
    spanFlags[i] = mStringBuilder.getSpanFlags(spanObjects[i]);
}

outState.putString("mStringBuilder:string", mStringBuilder.toString());
outState.putParcelableArray("mStringBuilder:spanObjects", spanObjects);
outState.putIntArray("mStringBuilder:spanStart", spanStart);
outState.putIntArray("mStringBuilder:spanEnd", spanEnd);
outState.putIntArray("mStringBuilder:spanFlags", spanFlags);

然后可以用这样的东西恢复状态:

mStringBuilder = new SpannableStringBuilder(savedInstanceState.getString("mStringBuilder:string"));
ParcelableSpan spanObjects[] = (ParcelableSpan[])savedInstanceState.getParcelableArray("mStringBuilder:spanObjects");
int spanStart[] = savedInstanceState.getIntArray("mStringBuilder:spanStart");
int spanEnd[] = savedInstanceState.getIntArray("mStringBuilder:spanEnd");
int spanFlags[] = savedInstanceState.getIntArray("mStringBuilder:spanFlags");
for(int i = 0; i < spanObjects.length; ++i)
    mStringBuilder.setSpan(spanObjects[i], spanStart[i], spanEnd[i], spanFlags[i]);

我在这里使用过SpannableStringBuilder但是它应该适用于任何实现Spanned的类,据我所知。可能可以将此代码包装到ParcelableSpanned中,但此版本现在看来还不错。

答案 3 :(得分:2)

来自Dan的想法:

public static String spannableString2JsonString(SpannableString ss) throws JSONException {
    JSONObject json = new JSONObject();
    json.put("text",ss.toString());
    JSONArray ja = new JSONArray();

    ForegroundColorSpan[] spans = ss.getSpans(0, ss.length(), ForegroundColorSpan.class);
    for (int i = 0; i < spans.length; i++){
        int col = spans[i].getForegroundColor();
        int start = ss.getSpanStart(spans[i]);
        int end = ss.getSpanEnd(spans[i]);
        JSONObject ij = new JSONObject();
        ij.put("color",col);
        ij.put("start",start);
        ij.put("end",end);
        ja.put(ij);
    }
    json.put("spans",ja);
    return json.toString();
}
public static SpannableString jsonString2SpannableString(String strjson) throws JSONException{
    JSONObject json = new JSONObject(strjson);
    SpannableString ss = new SpannableString(json.getString("text"));
    JSONArray ja = json.getJSONArray("spans");
    for (int i=0;i<ja.length();i++){
        JSONObject jo = ja.getJSONObject(i);
        int col = jo.getInt("color");
        int start = jo.getInt("start");
        int end = jo.getInt("end");
        ss.setSpan(new ForegroundColorSpan(col),start,end,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    return ss;
}

答案 4 :(得分:0)

我的用例是将TextView的内容(包括颜色和样式)转换为十六进制字符串或从十六进制字符串转换为十六进制字符串。基于Dan的答案,我想到了以下代码。希望有人使用类似的用例,可以为您省去一些麻烦。

将textBox的内容存储到字符串:

String actualText = textBox.getText().toString();
SpannableString spanStr = new SpannableString(textBox.getText());

ForegroundColorSpan[] fSpans = spanStr.getSpans(0,spanStr.length(),ForegroundColorSpan.class);
StyleSpan[] sSpans = spanStr.getSpans(0,spanStr.length(),StyleSpan.class);

int nSpans = fSpans.length;
String spanInfo = "";
String headerInfo = String.format("%08X",nSpans);
for (int i = 0; i < nSpans; i++) {
    spanInfo += String.format("%08X",fSpans[i].getForegroundColor());
    spanInfo += String.format("%08X",spanStr.getSpanStart(fSpans[i]));
    spanInfo += String.format("%08X",spanStr.getSpanEnd(fSpans[i]));
}

nSpans = sSpans.length;
headerInfo += String.format("%08X",nSpans);
for (int i = 0; i < nSpans; i++) {
    spanInfo += String.format("%08X",sSpans[i].getStyle());
    spanInfo += String.format("%08X",spanStr.getSpanStart(sSpans[i]));
    spanInfo += String.format("%08X",spanStr.getSpanEnd(sSpans[i]));
}

headerInfo += spanInfo;
headerInfo += actualText;
return headerInfo;

从字符串中检索textBox的内容:

        String header = tvString.substring(0,8);
        int fSpans = Integer.parseInt(header,16);
        header = tvString.substring(8,16);
        int sSpans = Integer.parseInt(header,16);
        int nSpans = fSpans + sSpans;
        SpannableString tvText = new SpannableString(tvString.substring(nSpans*24+16));
        tvString = tvString.substring(16,nSpans*24+16);

        int cc, ss, ee;
        int begin;
        for (int i = 0; i < fSpans; i++) {
            begin = i*24;
            cc = (int) Long.parseLong(tvString.substring(begin,begin+8),16);
            ss = (int) Long.parseLong(tvString.substring(begin+8,begin+16),16);
            ee = (int) Long.parseLong(tvString.substring(begin+16,begin+24),16);
            tvText.setSpan(new ForegroundColorSpan(cc), ss, ee, 0);
        }
        for (int i = 0; i < sSpans; i++) {
            begin = i*24+fSpans*24;
            cc = (int) Long.parseLong(tvString.substring(begin,begin+8),16);
            ss = (int) Long.parseLong(tvString.substring(begin+8,begin+16),16);
            ee = (int) Long.parseLong(tvString.substring(begin+16,begin+24),16);
            tvText.setSpan(new StyleSpan(cc), ss, ee, 0);
        }

        textBox.setText(tvText);

在检索代码中使用(int)Long.parseLong的原因是因为样式/颜色可以为负数。这会触发parseInt并导致溢出错误。但是,先执行parseLong然后将其强制转换为int会给出正确的(正数或负数)整数。

答案 5 :(得分:0)

这个问题很有趣,因为你必须从 SpannableString 或 SpannableStringBuilder 中保存你想要的所有信息,Gson 不会自动选择它们。对于我的实现,使用 HTML 无法正常工作,因此这是另一个可行的解决方案。这里所有的答案都不完整,你必须这样做:

class SpannableSerializer : JsonSerializer<SpannableStringBuilder?>, JsonDeserializer<SpannableStringBuilder?> {

    private val gson: Gson
        get() {
            val rtaf = RuntimeTypeAdapterFactory
                    .of(ParcelableSpan::class.java, ParcelableSpan::class.java.simpleName)
                    .registerSubtype(ForegroundColorSpan::class.java, ForegroundColorSpan::class.java.simpleName)
                    .registerSubtype(StyleSpan::class.java, StyleSpan::class.java.simpleName)
                    .registerSubtype(RelativeSizeSpan::class.java, RelativeSizeSpan::class.java.simpleName)
                    .registerSubtype(SuperscriptSpan::class.java, SuperscriptSpan::class.java.simpleName)
                    .registerSubtype(UnderlineSpan::class.java, UnderlineSpan::class.java.simpleName)
            return GsonBuilder()
                    .registerTypeAdapterFactory(rtaf)
                    .create()
        }

    override fun serialize(spannableStringBuilder: SpannableStringBuilder?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        val spanTypes = spannableStringBuilder?.getSpans(0, spannableStringBuilder.length, ParcelableSpan::class.java)
        val spanStart = IntArray(spanTypes?.size ?: 0)
        val spanEnd = IntArray(spanTypes?.size ?: 0)
        val spanFlags = IntArray(spanTypes?.size ?: 0)
        val spanInfo = DoubleArray(spanTypes?.size ?: 0)
        spanTypes?.forEachIndexed { i, span ->
            when (span) {
                is ForegroundColorSpan -> spanInfo[i] = span.foregroundColor.toDouble()
                is StyleSpan -> spanInfo[i] = span.style.toDouble()
                is RelativeSizeSpan -> spanInfo[i] = span.sizeChange.toDouble()
            }
            spanStart[i] = spannableStringBuilder.getSpanStart(span)
            spanEnd[i] = spannableStringBuilder.getSpanEnd(span)
            spanFlags[i] = spannableStringBuilder.getSpanFlags(span)
        }

        val jsonSpannable = JsonObject()
        jsonSpannable.addProperty(INPUT_STRING, spannableStringBuilder.toString())
        jsonSpannable.addProperty(SPAN_TYPES, gson.toJson(spanTypes))
        jsonSpannable.addProperty(SPAN_START, gson.toJson(spanStart))
        jsonSpannable.addProperty(SPAN_END, gson.toJson(spanEnd))
        jsonSpannable.addProperty(SPAN_FLAGS, gson.toJson(spanFlags))
        jsonSpannable.addProperty(SPAN_INFO, gson.toJson(spanInfo))
        return jsonSpannable
    }

    override fun deserialize(jsonElement: JsonElement, type: Type, jsonDeserializationContext: JsonDeserializationContext): SpannableStringBuilder {
        val jsonSpannable = jsonElement.asJsonObject
        val spannableString = jsonSpannable[INPUT_STRING].asString
        val spannableStringBuilder = SpannableStringBuilder(spannableString)
        val spanObjectJson = jsonSpannable[SPAN_TYPES].asString
        val spanTypes: Array<ParcelableSpan> = gson.fromJson(spanObjectJson, Array<ParcelableSpan>::class.java)
        val spanStartJson = jsonSpannable[SPAN_START].asString
        val spanStart: IntArray = gson.fromJson(spanStartJson, IntArray::class.java)
        val spanEndJson = jsonSpannable[SPAN_END].asString
        val spanEnd: IntArray = gson.fromJson(spanEndJson, IntArray::class.java)
        val spanFlagsJson = jsonSpannable[SPAN_FLAGS].asString
        val spanFlags: IntArray = gson.fromJson(spanFlagsJson, IntArray::class.java)
        val spanInfoJson = jsonSpannable[SPAN_INFO].asString
        val spanInfo: DoubleArray = gson.fromJson(spanInfoJson, DoubleArray::class.java)
        for (i in spanTypes.indices) {
            when (spanTypes[i]) {
                is ForegroundColorSpan -> spannableStringBuilder.setSpan(ForegroundColorSpan(spanInfo[i].toInt()), spanStart[i], spanEnd[i], spanFlags[i])
                is StyleSpan -> spannableStringBuilder.setSpan(StyleSpan(spanInfo[i].toInt()), spanStart[i], spanEnd[i], spanFlags[i])
                is RelativeSizeSpan -> spannableStringBuilder.setSpan(RelativeSizeSpan(spanInfo[i].toFloat()), spanStart[i], spanEnd[i], spanFlags[i])
                else -> spannableStringBuilder.setSpan(spanTypes[i], spanStart[i], spanEnd[i], spanFlags[i])
            }
        }
        return spannableStringBuilder
    }

    companion object {
        private const val PREFIX = "SSB:"
        private const val INPUT_STRING = PREFIX + "string"
        private const val SPAN_TYPES = PREFIX + "spanTypes"
        private const val SPAN_START = PREFIX + "spanStart"
        private const val SPAN_END = PREFIX + "spanEnd"
        private const val SPAN_FLAGS = PREFIX + "spanFlags"
        private const val SPAN_INFO = PREFIX + "spanInfo"
    }
}

如果还有其他类型的跨度,你必须在when部分添加它们并选择跨度的关联信息,很容易将它们全部添加。

RuntimeTypeAdapterFactory 在 gson 库中是私有的,您必须将其复制到您的项目中。 https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

现在使用它!

val gson by lazy {
    val type: Type = object : TypeToken<SpannableStringBuilder>() {}.type
    GsonBuilder()
            .registerTypeAdapter(type, SpannableSerializer())
            .create()
}
val ssb = gson.fromJson("your json here", SpannableStringBuilder::class.java)

答案 6 :(得分:-1)

我提出的解决方案是使用带有自定义序列化器/解串器的GSON。该解决方案结合了其他答案中提到的一些想法。

定义一些JSON密钥

library(dplyr)
df %>% mutate(VAR3=coalesce(VAR1,VAR2))

Gson Serializer

/* JSON Property Keys */
private static final String PREFIX = "SpannableStringBuilder:";
private static final String PROP_INPUT_STRING = PREFIX + "string";
private static final String PROP_SPAN_OBJECTS= PREFIX + "spanObjects";
private static final String PROP_SPAN_START= PREFIX + "spanStart";
private static final String PROP_SPAN_END = PREFIX + "spanEnd";
private static final String PROP_SPAN_FLAGS = PREFIX + "spanFlags";

Gson Deserializer

public static class SpannableSerializer implements JsonSerializer<SpannableStringBuilder> {
    @Override
    public JsonElement serialize(SpannableStringBuilder spannableStringBuilder, Type type, JsonSerializationContext context) {
        ParcelableSpan[] spanObjects = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ParcelableSpan.class);

        int[] spanStart = new int[spanObjects.length];
        int[] spanEnd= new int[spanObjects.length];
        int[] spanFlags = new int[spanObjects.length];
        for(int i = 0; i < spanObjects.length; ++i) {
            spanStart[i] = spannableStringBuilder.getSpanStart(spanObjects[i]);
            spanEnd[i] = spannableStringBuilder.getSpanEnd(spanObjects[i]);
            spanFlags[i] = spannableStringBuilder.getSpanFlags(spanObjects[i]);
        }
        JsonObject jsonSpannable = new JsonObject();
        jsonSpannable.addProperty(PROP_INPUT_STRING, spannableStringBuilder.toString());
        jsonSpannable.addProperty(PROP_SPAN_OBJECTS, gson.toJson(spanObjects));
        jsonSpannable.addProperty(PROP_SPAN_START, gson.toJson(spanStart));
        jsonSpannable.addProperty(PROP_SPAN_END, gson.toJson(spanEnd));
        jsonSpannable.addProperty(PROP_SPAN_FLAGS, gson.toJson(spanFlags));
        return jsonSpannable;
    }
}

对于public static class SpannableDeserializer implements JsonDeserializer<SpannableStringBuilder> { @Override public SpannableStringBuilder deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonSpannable = jsonElement.getAsJsonObject(); try { String spannableString = jsonSpannable.get(PROP_INPUT_STRING).getAsString(); SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(spannableString); String spanObjectJson = jsonSpannable.get(PROP_SPAN_OBJECTS).getAsString(); ParcelableSpan[] spanObjects = gson.fromJson(spanObjectJson, ParcelableSpan[].class); String spanStartJson = jsonSpannable.get(PROP_SPAN_START).getAsString(); int[] spanStart = gson.fromJson(spanStartJson, int[].class); String spanEndJson = jsonSpannable.get(PROP_SPAN_END).getAsString(); int[] spanEnd = gson.fromJson(spanEndJson, int[].class); String spanFlagsJson = jsonSpannable.get(PROP_SPAN_FLAGS).getAsString(); int[] spanFlags = gson.fromJson(spanFlagsJson, int[].class); for (int i = 0; i <spanObjects.length; ++i) { spannableStringBuilder.setSpan(spanObjects[i], spanStart[i], spanEnd[i], spanFlags[i]); } return spannableStringBuilder; } catch (Exception ex) { Log.e(TAG, Log.getStackTraceString(ex)); } return null; } } ,您可能需要将类型注册到GSON,如下所示:

ParcelableSpan