SpannableString 可以序列化吗?

Can SpannableString be Serializable?

我想要 post 一个带有 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中获取对象。

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

不过我们可以通过下面的简单方法SpannableString

第一个Activity

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

第二个Activity

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

DEMO

根据 SpannableString 的文档,它没有实现 Serializable 接口,因此您遇到了 "java.io.NotSerializableException" 异常。

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

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

根据 Serializable 接口的文档 -

"To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime."

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

你不能直接序列化它,但你可以制作包装器,它将 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))
  }
}

我知道这不是对您问题的直接回应。但是我正在解决类似的问题(在使用导航组件时通过 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?
}

我结合了 @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.