Android Parcelize

Parcel是针对Android的优化序列化格式,旨在使咱们可以在进程之间传输数据。 这是大多数Android开发人员偶尔须要作的事情,但不是常常这样作。 制做课程Pareclizable实际上须要一点点努力,可是有一个Kotlin扩展能够极大地简化事情。 在这篇文章中,咱们将看看@Parcelize以及它如何让咱们的生活更轻松。原文java

我确信每次我须要实现Parcelable时我并不孤单我有点绝望,由于我知道我须要写一些样板代码,我须要查阅文档以提醒我如何去作 由于这是我不常常作的事情。 Kotlin来救援!android

一般,咱们须要在进程之间传递的数据类型能够封装在数据类中。 若是数据类实现了Parcelable,咱们一般须要包含一些方法:git

SimpleDataClassgithub

data class SimpleDataClass( val name: String, val age: Int ) : Parcelable {
    
    constructor(parcel: Parcel) : this(
        parcel.readString()!!,
        parcel.readInt()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeInt(age)
    }

    override fun describeContents(): Int {
        return 0
    }

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

        override fun newArray(size: Int): Array<SimpleDataClass?> {
            return arrayOfNulls(size)
        }
    }
}
复制代码

值得庆幸的是,IntelliJ IDEA和Android Studio包含帮助程序以自动生成此代码,但它仍然增长了咱们的数据类。 @Parcelize注释是咱们的朋友! 它仍然处于实验状态,但已经存在了一段时间,因此咱们能够但愿它很快就能稳定下来。 很是感谢Olivier Genez指出@Parcelize是在Kotlin 1.3.40中进行实验的(本文最初是在发布以前编写的)。 要在1.3.40以前的Kotlin版本中使用@Parcelize,咱们须要启用实验性Android扩展:swift

应用程序/的build.gradleapp

.
.
.
androidExtensions {
    experimental = true
}
.
.
.
复制代码

有了@Parcelize,如今能够很是简化咱们的数据类:框架

SimpleDataClass.ktide

@Parcelize
data class SimpleDataClass( val name: String, val age: Int ) : Parcelable 复制代码

虽然这彷佛是Styling Android历史中最短的帖子,但它并不那么简单。 让咱们看一个稍微复杂的例子:函数

CompoundDataClass.ktgradle

@Parcelize
data class CompoundDataClass @JvmOverloads constructor( val name: String, val simpleDataClass: SimpleDataClass, @Transient val transientString: String = "" ) : Parcelable 复制代码

若是你喜欢一个谜题,那么请研究那个片断,看看你是否能够找出不能按预期工做的东西。

乍一看,人们可能会认为包括SimpleDataClass类型的字段可能会致使问题,但这实际上很好,由于它已是Parcelable,咱们可使用任何Parcelable类做为字段。 问题其实是transientString字段,它看起来并不简单。 读取代码,能够理解的是,假设这个接收器在transientString字段中获得一个空字符串,但这不会发生。 这个问题其实是双重的:首先,Android框架只有一个Parcelable,当它有to;时,其次,@Parcelize不尊重@Transient注释。

要演示第一个,请查看如下Fragment

MainFragment.kt

class MainFragment : Fragment() {

    private var simple: SimpleDataClass? = null
    private var compound: CompoundDataClass? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            simple = it.getParcelable(ARG_SIMPLE)
            compound = it.getParcelable(ARG_COMPOUND)
        }
        Timber.d("Simple: \"%s\"; Compound: \"%s\"", simple, compound)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.fragment_main, container, false)

    companion object {
        private const val ARG_SIMPLE = "simple"
        private const val ARG_COMPOUND = "compound"

        @JvmStatic
        fun newInstance(simpleDataClass: SimpleDataClass, compound: CompoundDataClass) =
            MainFragment().apply {
                arguments = Bundle().apply {
                    putParcelable(ARG_SIMPLE, simpleDataClass)
                    putParcelable(ARG_COMPOUND, compound)
                }
            }
    }
}
复制代码

若是咱们称之为以下,咱们可能指望不一样的行为而不是实际发生:

class MainActivity : AppCompatActivity() {

    private val simple = SimpleDataClass("Simple", 1)
    private val compound = CompoundDataClass("Compound", simple, "Transient")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager.beginTransaction().apply {
            replace(R.id.fragment_main, MainFragment.newInstance(simple, compound))
            commit()
        }
    }
}
复制代码

尽管MainFragmentnewInstance()方法中突出显示的行显示咱们正在向Bundle添加Parcelable,但若是Android框架须要在整个流程边界中展平它,它只会变得扁平化。 在这种状况下,它在同一个Activity中传递,没有理由将其展平,所以在onCreate()方法的突出显示行中检索CompoundDataClass的相同实例。

咱们能够经过添加一些扩展函数来模拟Parcel的扁平化,这些扩展函数模仿Android框架将如何展平和扩展Parcels

inline fun <reified T : Parcelable> T.collapse(): ByteArray {
    val parcel = Parcel.obtain()
    parcel.writeParcelable(this, 0)
    val byteArray = parcel.marshall()
    parcel.recycle()
    return byteArray
}

inline fun <reified T : Parcelable> Class<T>.expand(byteArray: ByteArray): T {
    val parcel = Parcel.obtain()
    parcel.apply {
        unmarshall(byteArray, 0, byteArray.size)
        setDataPosition(0)
    }
    val parcelable =
        parcel.readParcelable<T>(this@expand.classLoader)
            ?: throw InstantiationException("Unable to expand $name")
    parcel.recycle()
    return parcelable
}
复制代码

而后咱们能够调用它们来强制扁平化数据类,而后再次展开它(这真的不是任何人在真实场景中须要作的事情,因此请注意代码的注释):

@JvmStatic
fun newInstance(simpleDataClass: SimpleDataClass, compound: CompoundDataClass) =
    MainFragment().apply {
        arguments = Bundle().apply {
            putParcelable(ARG_SIMPLE, simpleDataClass)
            putParcelable(
                ARG_COMPOUND,
                /* * Don't do this. * * I've done this here in some sample code to demonstrate * that things don't always get serialised. There is no * reason than you'd actually want to do this in this context. * * So...really...don't do this. * * Look, I'm not joking, you really shouldn't do this. * * Even if you're being attacked by a pack of wild dogs and * think that collapsing then immediately re-expanding a * Parcelable will save your life, then I'm sorry, but * it won't. Rest In Peace. * * Perhaps I forgot to mention: you really shouldn't do this. */
                CompoundDataClass::class.java.expand(compound.collapse()) ) } } 复制代码

即便咱们这样作,咱们仍然获得ARG_COMPOUND值中的“瞬态"值,咱们从MainFragmentonCreate()方法中的Fragment参数获得。 正如我以前提到的,这是由于@Parcelize生成的字节码将保留全部字段,包括那些标记为@Transient的字段,这与在持久性期间一般处理瞬态字段的方式略有不一致,但咱们能够经过查看Kotlin字节码的反编译来证实这一点:

CompoundDataClass.decompiled.java

@Parcelize
public final class CompoundDataClass implements Parcelable {
   @NotNull
   private final String name;
   @NotNull
   private final SimpleDataClass simpleDataClass;
   @NotNull
   private final transient String transientString;
   public static final android.os.Parcelable.Creator CREATOR = new CompoundDataClass.Creator();
    .
    .
    .
   public void writeToParcel(@NotNull Parcel parcel, int flags) {
      Intrinsics.checkParameterIsNotNull(parcel, "parcel");
      parcel.writeString(this.name);
      this.simpleDataClass.writeToParcel(parcel, 0);
      parcel.writeString(this.transientString);
   }

   @Metadata(
      mv = {1, 1, 15},
      bv = {1, 0, 3},
      k = 3
   )
   public static class Creator implements android.os.Parcelable.Creator {
      @NotNull
      public final Object[] newArray(int size) {
         return new CompoundDataClass[size];
      }

      @NotNull
      public final Object createFromParcel(@NotNull Parcel in) {
         Intrinsics.checkParameterIsNotNull(in, "in");
         return new CompoundDataClass(in.readString(), (SimpleDataClass)SimpleDataClass.CREATOR.createFromParcel(in), in.readString());
      }
   }
}
复制代码

名为transientString的字段与Parcel进行序列化,尽管使用Java transient关键字进行声明。 若是咱们想要为这个领域提供短暂性,咱们必须手动完成。 实际上值得覆盖这个由于咱们可能还须要这样作,若是咱们的字段是third-party对象而不是Parcelable而且咱们须要实现一个自定义机制来存储足够的数据到Parcel以容许咱们re-实例化特定对象时 Parcel扩展回对象实例:

CompoundDataClass.kt

@Parcelize
data class CompoundDataClass @JvmOverloads constructor( val name: String, val simpleDataClass: SimpleDataClass, @Transient val transientString: String = "" ) : Parcelable {

    companion object : Parceler<CompoundDataClass> {

        override fun create(parcel: Parcel): CompoundDataClass {
            val name: String = parcel.readString()!!
            val simple: SimpleDataClass =
                parcel.readParcelable(SimpleDataClass::class.java.classLoader)!! return CompoundDataClass(name, simple) } override fun CompoundDataClass.write(parcel: Parcel, flags: Int) {
            parcel.writeString(name)
            parcel.writeParcelable(simpleDataClass, flags)
        }
    }
}
复制代码

拥有一个实现create()CompoundDataClass.write()方法的Parceler伴侣对象使咱们可以自定义Parcel的持久性,在这种状况下,容许咱们省略transientString字段。 虽然@Transient注释在这里没有达到直接目的,但我认为将它留在这里是好的,由于它使得阅读代码的任何人都更清楚,该字段将不会被持久化。 所以,咱们的代码更易于维护和理解。

虽然可能有人认为必须实施这些方法来执行包裹化,但这又回到了咱们刚开始的地方(即必须手动完成)。 可是,若是将其与第一个代码片断进行比较,仍然会为咱们生成不少样板代码,而且使用Parcelize的结果仍然少得多。

here提供了本文的源代码。

相关文章
相关标签/搜索