SpannableString 可以序列化吗?

Posted

技术标签:

【中文标题】SpannableString 可以序列化吗?【英文标题】:Can SpannableString be Serializable? 【发布时间】:2017-08-23 18:26:36 【问题描述】:

我想通过 Intent 发布一个带有 SpannableString 的对象。我让对象实现Serializable,但它会抛出

java.io.NotSerializableException: android.text.SpannableString

所以我让 SpannableString 实现 Serializable。

private class MySpannableString extends SpannableString implements Serializable
    public MySpannableString(CharSequence source) 
        super(source);
    

但是我在下一个Activity中获取不到对象。

【问题讨论】:

【参考方案1】:

我已经测试通过SpannableStringSerializable,但它会在第二个活动中抛出错误,如Can not cast Serializable to SpannableString, null value will return as default。所以我认为这可能是不可能的。

但是我们可以通过下面的简单方式传递SpannableString

第一个活动

SpannableString text == ...;
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("KEY", text);
startActivity(intent);

第二个活动

SpannableString a = (SpannableString). getIntent().getCharSequenceExtra("KEY");
//we can get value because SpannableString implements CharSequence

DEMO

【讨论】:

谢谢,你的回答很好,但我不得不采纳另一个。因为他告诉了我原因。【参考方案2】:

根据 SpannableString 的文档,它没有实现 Serializable 接口,因此出现“java.io.NotSerializableException”异常。

文档 - https://developer.android.com/reference/android/text/SpannableString.html

您通过扩展 SpannableString 和实现 Serializable 接口来解决此问题的方法将不起作用。这样做的原因是,为了通过扩展一个不可序列化的类来使其可序列化,它需要有一个可访问的无参数构造函数。 SpannableString 类没有这个,唯一可访问的构造函数将 CharacterSequence 作为参数。

根据 Serializable 接口的文档 -

“为了允许不可序列化类的子类型被序列化,子类型可以负责保存和恢复超类型的公共、受保护和(如果可访问)包字段的状态。子类型可以假设这一点仅当它扩展的类具有可访问的无参数构造函数来初始化类的状态时才负责。如果不是这种情况,则声明类 Serializable 是错误的。将在运行时检测到错误。"

文档 - https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html

【讨论】:

谢谢,从你的回答我知道原因了。 我知道原因但知道解决办法【参考方案3】:

你不能直接序列化它,但你可以制作包装器,它将spannable转换为HTML并序列化字符串。

class SpannableWp(var spannable: SpannableString? = null) : Serializable 


  @Throws(IOException::class)
  private fun writeObject(stream: ObjectOutputStream) 
    stream.writeObject(spannable?.toHtml())
  

  @Throws(IOException::class, ClassNotFoundException::class)
  private fun readObject(stream: ObjectInputStream) 
    val html = stream.readObject() as String
    spannable = SpannableString(Html.fromHtml(html))
  

【讨论】:

我可以问这个问题吗?我有任何其他属性,然后从其他活动获取时为空,您能帮忙吗? @famfamfam 如果您有 HTML 未涵盖的属性,那么您需要手动写入/读取它们我写入/读取对象【参考方案4】:

我结合了 @Yarh@kotoMJ 答案,通过打包将序列化值保持在最小尺寸:

class SerializedSpannableString(internal var value: SpannableString) : Serializable 

    @Throws(IOException::class)
    private fun writeObject(stream: ObjectOutputStream) 
        val parcel = Parcel.obtain()
        TextUtils.writeToParcel(value, parcel, 0)
        stream.writeObject(parcel.marshall())
        parcel.recycle()
    

    @Throws(IOException::class, ClassNotFoundException::class)
    private fun readObject(stream: ObjectInputStream) 
        val byteArray: ByteArray = stream.readObject() as ByteArray
        val parcel = Parcel.obtain()
        parcel.unmarshall(byteArray, 0, byteArray.size)
        parcel.setDataPosition(0)
        value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel) as SpannableString
        parcel.recycle()
    

    override fun equals(other: Any?): Boolean 
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as SerializedSpannableString

        val thisSpans = value.getSpans(0, value.length, Any::class.java)
        val otherSpans = other.value.getSpans(0, other.value.length, Any::class.java)
        if (thisSpans.size != otherSpans.size) return false
        return thisSpans.asSequence().zip(otherSpans.asSequence()).all  (thisSpan, otherSpan) ->
            value.getSpanStart(thisSpan) == other.value.getSpanStart(otherSpan) &&
                value.getSpanEnd(thisSpan) == other.value.getSpanEnd(otherSpan) &&
                value.getSpanFlags(thisSpan) == other.value.getSpanFlags(otherSpan)
        
    

    override fun hashCode(): Int 
        var hash = value.toString().hashCode()
        val spanCount = value.length
        hash = hash * 31 + spanCount
        value.getSpans(0, value.length, Any::class.java).forEach  span ->
            hash = hash * 31 + value.getSpanStart(span)
            hash = hash * 31 + value.getSpanEnd(span)
            hash = hash * 31 + value.getSpanFlags(span)
        
        return hash
    


equalshashCode 实现来自SpannableStringInternal

【讨论】:

【参考方案5】:

我知道这不是对您的问题的直接回应。但是我正在解决类似的问题(在使用导航组件时通过 safeargs 传递 SpannableString),并且有一个技巧如何为它制作 parcelable 包装器:

@Keep
class ParcelableSpanned2(
    val spannableString: SpannableString?
) : Parcelable 
    constructor(parcel: Parcel) : this(parcel.readSpannableString())

    override fun writeToParcel(parcel: Parcel, flags: Int) 
        parcel.writeSpannableString(spannableString, flags)
    

    override fun describeContents(): Int 
        return 0
    

    companion object CREATOR : Parcelable.Creator<ParcelableSpanned2> 
        override fun createFromParcel(parcel: Parcel): ParcelableSpanned2 
            return ParcelableSpanned2(parcel)
        

        override fun newArray(size: Int): Array<ParcelableSpanned2?> 
            return arrayOfNulls(size)
        
    


fun Parcel.writeSpannableString(spannableString: SpannableString?, flags: Int) 
    TextUtils.writeToParcel(spannableString, this, flags)


fun Parcel.readSpannableString(): SpannableString? 
    return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this) as SpannableString?

【讨论】:

我可以问这个问题吗?我有任何其他属性,然后从其他活动获取时为空,您能帮忙吗?

以上是关于SpannableString 可以序列化吗?的主要内容,如果未能解决你的问题,请参考以下文章

Android SpannableString 在部分文本后面设置背景

使用 SpannableString 调整文本对齐方式

SpannableString:是不是可以应用两个或多个RelativeSizeSpans?

android中SpannableString之富文本显示效果

SpannableString 打造绚丽多彩的文本显示效果

spannablestring 不适用于以编程方式创建的按钮