美文网首页
什么?还在傻傻地手写Parcelable实现?

什么?还在傻傻地手写Parcelable实现?

作者: 王可大虾 | 来源:发表于2022-05-18 15:16 被阅读0次

    缘起

    序列化已经是Android司空见惯的东西了,场景太多了。就拿Intent来说吧,extra能放的数据,除了基本类型外,就是序列化的数据了,有两种:

    • Serializable:Java世界自带的序列化工具,大道至简,是一个无方法接口
    • Parcelable:Android的官配序列化工具

    这二者在性能、用法乃至适用场景上均有不同,网上的讨论已经很多了,这里不再赘述。

    下面来看看官配正品怎么用的。

    Android的Parcelable

    首先看看官方示例:

    public class MyParcelable implements Parcelable {
        private int mData;
    
        public int describeContents() {
            return 0;
        }
    
        public void writeToParcel(Parcel out, int flags) {
            out.writeInt(mData);
        }
    
        public static final Parcelable.Creator<MyParcelable> CREATOR
                = new Parcelable.Creator<MyParcelable>() {
            public MyParcelable createFromParcel(Parcel in) {
                return new MyParcelable(in);
            }
    
            public MyParcelable[] newArray(int size) {
                return new MyParcelable[size];
            }
        };
    
        private MyParcelable(Parcel in) {
            mData = in.readInt();
        }
    }
    

    可以总结,实现Parcelable的数据类,有两个要点:

    1. 必须有一个 非空的、静态的且名为"CREATOR" 的对象,该对象实现 Parcelable.Creator 接口
    2. 实现方法 describeContents ,描述内容;
      实现方法 writeToParcel ,将类数据打入parcel内

    示例中,实际的数据只有一个简单的整型。

    实验:Intent中的Parcelable传递

    这里通过一个案例来说明一下Parcelable的使用。

    首先,定义一个数据类User,它包含一个String和一个Int:

    class User() : Parcelable {
    
        var name: String? = ""
        var updatedTime: Long = 0L
    
        constructor(parcel: Parcel) : this() {
            name = parcel.readString()
            updatedTime = parcel.readLong()
        }
    
        constructor(name: String?, time: Long) : this() {
            this.name = name
            updatedTime = time
        }
    
        override fun writeToParcel(parcel: Parcel, flags: Int) {
            Log.d("p-test", "write to")
            parcel.writeString(name)
            parcel.writeLong(updatedTime)
        }
    
        override fun describeContents(): Int {
            return 0
        }
    
        companion object CREATOR : Parcelable.Creator<User> {
            override fun createFromParcel(parcel: Parcel): User {
                Log.d("p-test", "createFromParcel")
                return User(parcel)
            }
    
            override fun newArray(size: Int): Array<User?> {
                return arrayOfNulls(size)
            }
        }
    
        override fun toString(): String = "$name - [${
            DateFormat.getInstance().format(Date(updatedTime))
        }]"
    
    }
    

    启动方带上User数据:

    Log.d("p-test", "navigate to receiver")
    context.startActivity(Intent(context, ReceiverActivity::class.java).apply {
        putExtra("user", User("Dale", System.currentTimeMillis())) // 调用Intent.putExtra(String name, @Nullable Parcelable value)
    })
    

    接收方读取并显示User数据:

    Log.d("p-test", "onCreate")
    val desc: User? = intent?.getParcelableExtra("user")
    // 省略展示:desc?.toString()
    

    来看看日志:

    2022-05-18 11:45:28.280 26148-26148 p-test  com.jacee.example.parcelabletest     D  navigate to receiver
    2022-05-18 11:45:28.282 26148-26148 p-test  com.jacee.example.parcelabletest     D  write to
    2022-05-18 11:45:28.342 26148-26148 p-test  com.jacee.example.parcelabletest     D  onCreate
    2022-05-18 11:45:28.343 26148-26148 p-test  com.jacee.example.parcelabletest     D  createFromParcel
    

    其过程为:

    1. 启动
    2. User类调用writeToParcel,将数据写入Parcel
    3. 接收
    4. CREATOR调用createFromParcel,从Parcel中读取数据,并构造相应的User数据类对象

    界面上,User正确展示:

    image.png

    由此,Parcelable的数据类算是正确实现了。

    看起来,虽然没有很难,但是,是真心有点儿烦啊,尤其是相较于Java的Serializable来说。有没有简化之法呢?当然有啊,要知道,现在可是Kotlin时代了!

    kotlin-parcelize插件

    隆重介绍kotlin-parcelize插件:它提供了一个 Parcelable 的实现生成器。有了此生成器,就不必再写如前的复杂代码了。

    怎么使用呢?

    首先,需要在gradle里面添加此插件:

    plugins {
        id 'kotlin-parcelize'
    }
    

    然后,在需要 Parcelable 的数据类上添加 @kotlinx.parcelize.Parcelize 注解就行了。

    来吧,改造前面的例子:

    import kotlinx.parcelize.Parcelize
    
    @Parcelize
    data class User(
        val name: String?,
        val updatedTime: Long
    ): Parcelable {
        override fun toString(): String = "new: $name - [${
            DateFormat.getInstance().format(Date(updatedTime))
        }]"
    }
    

    哇,简化如斯,真能实现?还是来看看上述代码对应的字节码吧:

    @Metadata(
       mv = {1, 6, 0},
       k = 1,
       d1 = {"\u0000:\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\t\n\u0002\b\t\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u000b\n\u0000\n\u0002\u0010\u0000\n\u0002\b\u0003\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\b\u0087\b\u0018\u00002\u00020\u0001B\u0017\u0012\b\u0010\u0002\u001a\u0004\u0018\u00010\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\u000b\u0010\u000b\u001a\u0004\u0018\u00010\u0003HÆ\u0003J\t\u0010\f\u001a\u00020\u0005HÆ\u0003J\u001f\u0010\r\u001a\u00020\u00002\n\b\u0002\u0010\u0002\u001a\u0004\u0018\u00010\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\t\u0010\u000e\u001a\u00020\u000fHÖ\u0001J\u0013\u0010\u0010\u001a\u00020\u00112\b\u0010\u0012\u001a\u0004\u0018\u00010\u0013HÖ\u0003J\t\u0010\u0014\u001a\u00020\u000fHÖ\u0001J\b\u0010\u0015\u001a\u00020\u0003H\u0016J\u0019\u0010\u0016\u001a\u00020\u00172\u0006\u0010\u0018\u001a\u00020\u00192\u0006\u0010\u001a\u001a\u00020\u000fHÖ\u0001R\u0013\u0010\u0002\u001a\u0004\u0018\u00010\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\u001b"},
       d2 = {"Lcom/jacee/example/parcelabletest/data/User;", "Landroid/os/Parcelable;", "name", "", "updatedTime", "", "(Ljava/lang/String;J)V", "getName", "()Ljava/lang/String;", "getUpdatedTime", "()J", "component1", "component2", "copy", "describeContents", "", "equals", "", "other", "", "hashCode", "toString", "writeToParcel", "", "parcel", "Landroid/os/Parcel;", "flags", "parcelable-test_debug"}
    )
    @Parcelize
    public final class User implements Parcelable {
       @Nullable
       private final String name;
       private final long updatedTime;
       public static final android.os.Parcelable.Creator CREATOR = new User.Creator();
    
       @NotNull
       public String toString() {
          return "new: " + this.name + " - [" + DateFormat.getInstance().format(new Date(this.updatedTime)) + ']';
       }
    
       @Nullable
       public final String getName() {
          return this.name;
       }
    
       public final long getUpdatedTime() {
          return this.updatedTime;
       }
    
       public User(@Nullable String name, long updatedTime) {
          this.name = name;
          this.updatedTime = updatedTime;
       }
    
       @Nullable
       public final String component1() {
          return this.name;
       }
    
       public final long component2() {
          return this.updatedTime;
       }
    
       @NotNull
       public final User copy(@Nullable String name, long updatedTime) {
          return new User(name, updatedTime);
       }
    
       // $FF: synthetic method
       public static User copy$default(User var0, String var1, long var2, int var4, Object var5) {
          if ((var4 & 1) != 0) {
             var1 = var0.name;
          }
    
          if ((var4 & 2) != 0) {
             var2 = var0.updatedTime;
          }
    
          return var0.copy(var1, var2);
       }
    
       public int hashCode() {
          String var10000 = this.name;
          return (var10000 != null ? var10000.hashCode() : 0) * 31 + Long.hashCode(this.updatedTime);
       }
    
       public boolean equals(@Nullable Object var1) {
          if (this != var1) {
             if (var1 instanceof User) {
                User var2 = (User)var1;
                if (Intrinsics.areEqual(this.name, var2.name) && this.updatedTime == var2.updatedTime) {
                   return true;
                }
             }
    
             return false;
          } else {
             return true;
          }
       }
    
       public int describeContents() {
          return 0;
       }
    
       public void writeToParcel(@NotNull Parcel parcel, int flags) {
          Intrinsics.checkNotNullParameter(parcel, "parcel");
          parcel.writeString(this.name);
          parcel.writeLong(this.updatedTime);
       }
    
       @Metadata(
          mv = {1, 6, 0},
          k = 3
       )
       public static class Creator implements android.os.Parcelable.Creator {
          @NotNull
          public final User[] newArray(int size) {
             return new User[size];
          }
    
          // $FF: synthetic method
          // $FF: bridge method
          public Object[] newArray(int var1) {
             return this.newArray(var1);
          }
    
          @NotNull
          public final User createFromParcel(@NotNull Parcel in) {
             Intrinsics.checkNotNullParameter(in, "in");
             return new User(in.readString(), in.readLong());
          }
    
          // $FF: synthetic method
          // $FF: bridge method
          public Object createFromParcel(Parcel var1) {
             return this.createFromParcel(var1);
          }
       }
    }
    

    嗯,十分眼熟 —— 这不就是 完美且完整地实现了Parcelable 吗?当然是能正确工作的!

    2022-05-18 13:13:30.197 27258-27258 p-test   com.jacee.example.parcelabletest     D  navigate to receiver
    2022-05-18 13:13:30.237 27258-27258 p-test   com.jacee.example.parcelabletest     D  onCreate
    
    image.png

    复杂的序列化逻辑

    如果需要添加更复杂的序列化逻辑,就需要额外通过伴随对象实现,该对象需要实现接口 Parceler

    interface Parceler<T> {
        /**
         * Writes the [T] instance state to the [parcel].
         */
        fun T.write(parcel: Parcel, flags: Int)
    
        /**
         * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it.
         */
        fun create(parcel: Parcel): T
    
        /**
         * Returns a new [Array]<T> with the given array [size].
         */
        fun newArray(size: Int): Array<T> {
            throw NotImplementedError("Generated by Android Extensions automatically")
        }
    }
    

    看样子,Parceler 和原生 Parcelable.Creator 十分像啊,不过多了一个 write 函数 —— 其实就是对应了Parcelable.writeToParcel方法。

    简单打印点日志模拟所谓的“复杂的序列化逻辑”:

    @Parcelize
    data class User(
        val name: String?,
        val updatedTime: Long
    ): Parcelable {
        override fun toString(): String = "new: $name - [${
            DateFormat.getInstance().format(Date(updatedTime))
        }]"
    
        private companion object : Parceler<User> {
            override fun create(parcel: Parcel): User {
                Log.d("p-test", "new: create")
                return User(parcel.readString(), parcel.readLong())
            }
    
            override fun User.write(parcel: Parcel, flags: Int) {
                Log.d("p-test", "new: write to")
                parcel.writeString("【${name}】")
                parcel.writeLong(updatedTime)
            }
    
        }
    }
    

    来看看:

    2022-05-18 13:24:49.365 29603-29603 p-test  com.jacee.example.parcelabletest     D  navigate to receiver
    2022-05-18 13:24:49.366 29603-29603 p-test  com.jacee.example.parcelabletest     D  new: write to
    2022-05-18 13:24:49.450 29603-29603 p-test  com.jacee.example.parcelabletest     D  onCreate
    2022-05-18 13:24:49.450 29603-29603 p-test  com.jacee.example.parcelabletest     D  new: create
    

    果然调用了,其中,接收方拿到的name,确实就是write函数改造过的(加了“【】”):

    image.png

    映射序列化

    假如数据类不能直接支持序列化,那就可以通过自定义一个Parceler实现映射序列化

    怎么理解呢?假如有一个数据类A,是一个普通实现,不支持序列化(或者有其他原因,总之是不支持),但是呢,我们又有需求是将它序列化后使用,这时候就可以实现 Parceler<A> 类,然后用包裹A的类B来实现序列化 —— 即,通过Parceler,将普通的A包裹成了序列化的B

    // 目标数据类A
    data class User(
        val name: String?,
        val updatedTime: Long
    ) {
        override fun toString(): String = "new: $name - [${
            DateFormat.getInstance().format(Date(updatedTime))
        }]"
    }
    
    // 实现的Parceler<A>
    object UserParceler: Parceler<User> {
        override fun create(parcel: Parcel): User {
            Log.d("djx_test", "1 new: create")
            return User(parcel.readString(), parcel.readLong())
        }
    
        override fun User.write(parcel: Parcel, flags: Int) {
            Log.d("djx_test", "1 new: write to")
            parcel.writeString("【${name}】")
            parcel.writeLong(updatedTime)
        }
    }
    
    // 映射类B
    @Parcelize
    @TypeParceler<User, UserParceler>
    class Target(val value: User): Parcelable // 这个类来实现Parcelable
    

    如上就是 A -> B 的序列化映射,同样没问题:

    2022-05-18 14:08:26.091 30639-30639 p-test   com.jacee.example.parcelabletest     D  navigate to receiver
    2022-05-18 14:08:26.094 30639-30639 p-test   com.jacee.example.parcelabletest     D  1 new: write to
    2022-05-18 14:08:26.148 30639-30639 p-test   com.jacee.example.parcelabletest     D  onCreate
    2022-05-18 14:08:26.148 30639-30639 p-test   com.jacee.example.parcelabletest     D  1 new: create
    
    image.png

    上面的映射类B,还可以这么写:

    @Parcelize
    class Target(@TypeParceler<User, UserParceler> val value: User): Parcelable
    
    // 或
    
    @Parcelize
    class Target(val value: @WriteWith<UserParceler> User): Parcelable
    

    总结

    说了这么多,其实总结一下就是:

    插件kotlin-parcelize接管了套路化、模版化的工作,帮我们自动生成了序列化的实现,它并没有改变 Parcelable 的实现方式

    用它就对了!

    相关文章

      网友评论

          本文标题:什么?还在傻傻地手写Parcelable实现?

          本文链接:https://www.haomeiwen.com/subject/updaurtx.html