在保存实例状态时在bundle中传递kotlin函数时出现NotSerializableException

时间:2018-02-19 08:54:20

标签: kotlin

在我将kotlin函数传递到Bundle中的onSaveInstanceState后,我得到了NotSerializableException:

java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = MyActivity$showFragmentA$1)
                                                                  at android.os.Parcel.writeSerializable(Parcel.java:1447)
                                                                  at android.os.Parcel.writeValue(Parcel.java:1395)
                                                                  at android.os.Parcel.writeArrayMapInternal(Parcel.java:665)
                                                                  at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1330)
                                                                  at android.os.Bundle.writeToParcel(Bundle.java:1079)
                                                                  at android.os.Parcel.writeBundle(Parcel.java:690)
                                                                  at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3269)
                                                                  at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3632)
                                                                  at android.os.Handler.handleCallback(Handler.java:815)
                                                                  at android.os.Handler.dispatchMessage(Handler.java:104)
                                                                  at android.os.Looper.loop(Looper.java:207)
                                                                  at android.app.ActivityThread.main(ActivityThread.java:5728)
                                                                  at java.lang.reflect.Method.invoke(Native Method)
                                                                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
                                                               Caused by: java.io.NotSerializableException: MyActivity
                                                                  at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1344)
                                                                  at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1651)
                                                                  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1497)
                                                                  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1461)
                                                                  at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:959)
                                                                  at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:360)
                                                                  at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1054)
                                                                  at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1384)
                                                                  at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1651)
                                                                  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1497)
                                                                  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1461)
                                                                  at android.os.Parcel.writeSerializable(Parcel.java:1442)
                                                                  at android.os.Parcel.writeValue(Parcel.java:1395) 
                                                                  at android.os.Parcel.writeArrayMapInternal(Parcel.java:665) 
                                                                  at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1330) 
                                                                  at android.os.Bundle.writeToParcel(Bundle.java:1079) 
                                                                  at android.os.Parcel.writeBundle(Parcel.java:690) 
                                                                  at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3269) 
                                                                  at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3632) 
                                                                  at android.os.Handler.handleCallback(Handler.java:815) 
                                                                  at android.os.Handler.dispatchMessage(Handler.java:104) 
                                                                  at android.os.Looper.loop(Looper.java:207) 
                                                                  at android.app.ActivityThread.main(ActivityThread.java:5728) 
                                                                  at java.lang.reflect.Method.invoke(Native Method) 
                                                                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789) 
                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)

我的课程:

class MyActivity : Activity {
    private var lastFragment: (() -> Fragment)? = null

    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
         outState?.putSerializable("lastFragment", lastFragment as Serializable)
    }

    fun showFragmentA() {
        lastFragment = { FragmentA() }
        // show fragment lastFragment()
    }

    fun showFragmentB() {
        ...
    }
}

1 个答案:

答案 0 :(得分:3)

问题是这个。您创建一个lambda并将其设置为函数lastFragment的值。但是什么是lambda?它是由MyActivity类中的编译器创建的自定义类。它是一个内部类,因此它有一个指向MyActivity实例的指针,该实例不可序列化。因此,您的函数实例lambda类具有对其进行序列化的引用。看看这个:

class MyClass {
    var foo: (()->Unit)? = null

    fun makeProblem() {
        foo = { println("hi") }
    }
}

这创建了MyClass$makeProblem$1内部类MyClass来保存我的lambda { println("hi") }的主体......并且所有内部类都有指向其包含类的指针,因此MyClass$makeProblem$1有一个类型MyClass的变量,你看不到但显然是因为它允许lambda中的代码访问包含类的成员。然后繁荣,这打破了序列化。

计划序列化lambdas的库知道这个和特殊情况切割这个链接,假设没有使用这个内部类引用。 Apache Spark通过基本上使用内省来查找特定的隐藏字段并将其设置为null来实现此目的。我在某个地方有一个Kotlin的例子,但如果内部有变化,它就很脆弱。

你也可以在任何类之外声明你的lambda,以避免它成为一个内部类。或者确保包含的类也是可序列化的。或者使用可序列化的静态类包装它。其中一个可能有效,或者不可行,这取决于您稍后对lambda(和类)进行反序列化时想要发生的事情。

如果你看一下生成的字节码,你会发现这个lambda显然是一个内部类:

// ================uy/sotest/MyClass.class =================
// class version 50.0 (50)
// access flags 0x31
public final class uy/sotest/MyClass {


  // access flags 0x2
  // signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
  private Lkotlin/jvm/functions/Function0; foo
  @Lorg/jetbrains/annotations/Nullable;() // invisible

  // access flags 0x11
  // signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
  // declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getFoo()
  public final getFoo()Lkotlin/jvm/functions/Function0;
  @Lorg/jetbrains/annotations/Nullable;() // invisible
   ...

  // access flags 0x11
  // signature (Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V
  // declaration: void setFoo(kotlin.jvm.functions.Function0<kotlin.Unit>)
  public final setFoo(Lkotlin/jvm/functions/Function0;)V
    @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
   ...

  // access flags 0x11
  public final makeProblem()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    GETSTATIC uy/sotest/MyClass$makeProblem$1.INSTANCE : Luy/sotest/MyClass$makeProblem$1;
    CHECKCAST kotlin/jvm/functions/Function0
    PUTFIELD uy/sotest/MyClass.foo : Lkotlin/jvm/functions/Function0;
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE this Luy/sotest/MyClass; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public <init>()V
   ...

  @Lkotlin/Metadata;( ... )
  // access flags 0x18
  final static INNERCLASS uy/sotest/MyClass$makeProblem$1 null null
  // compiled from: ShowThing.kt
}


// ================uy/sotest/MyClass$makeProblem$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: uy/sotest/MyClass$makeProblem$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class uy/sotest/MyClass$makeProblem$1 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {


  // access flags 0x1041
  public synthetic bridge invoke()Ljava/lang/Object;
    ...

  // access flags 0x11
  public final invoke()V
    ...

  // access flags 0x0
  <init>()V
    ...

  // access flags 0x19
  public final static Luy/sotest/MyClass$makeProblem$1; INSTANCE

  // access flags 0x8
  static <clinit>()V
    ...

  @Lkotlin/Metadata;( ... )
  OUTERCLASS uy/sotest/MyClass makeProblem ()V
  // access flags 0x18
  final static INNERCLASS uy/sotest/MyClass$makeProblem$1 null null
     ...

}