นักพัฒนาแอนดรอยด์ส่วนใหญ่มักจะรู้จักกับ Parcelable มากกว่า Serializable เนอะ ซึ่งบางคนก็รู้แค่ว่าต้องใช้ Parcelable แต่ไม่รู้ว่าทำไม เพราะอะไร ดังนั้นจึงขอหยิบเรื่องนี้มาเขียนเป็นบทความต้อนรับปีใหม่ให้กับนักพัฒนาแอนดรอยด์อ่านเล่นดีกว่า~

รู้จักกับ Serializable กันแบบคร่าวๆ

Serializable นั้นมีมาตั้งแต่สมัย Java ดั้งเดิมอยู่แล้ว มีจุดประสงค์เพื่อใช้ในการแปลง Model Class ให้อยู่ในรูปของ Byte Stream เพื่อให้สามารถรับ/ส่งข้อมูลระหว่างอุปกรณ์ได้ โดยมีเงื่อนไขว่า Model Class นั้นๆต้องเก็บข้อมูลข้างในเป็น Primitive Data Type ทั้งหมด

รู้จักกับ Parcelable กันแบบคร่าวๆ

Parcelable ถูกเพิ่มเข้ามาเพื่อใช้งานในแอนดรอยด์ มีหน้าที่คล้ายๆกับ Serializable นั่นแหละ แต่บนแอนดรอยด์ใช้ใน Inter-process communication (IPC) ซึ่งจะคุ้นเคยกันดีเมื่อจะส่งข้อมูลที่เป็น Model Class จาก Activity ตัวหนึ่งไปให้อีกตัวหนึ่งจะต้องทำเป็น Parcelable ทุกครั้ง

วิธีสร้าง Model Class ให้เป็น Parcelable และ Serializable

เจ้าของบล็อกขอยกตัวอย่าง Model Class เป็นคลาสที่ชื่อว่า Post ซึ่งข้างในจะเก็บข้อมูลต่างๆในรูปแบบดังนี้

data class Post(
    var title: String?,
    var content: String?,
    var id: String?,
    var date: String?,
    var author: String?,
    var url: String?,
    var readCount: Int,
    var isDraft: Boolean,
    var tagist: List<String>?,
    var commentList: List<Comment>?
) {
    data class Comment(
        var comment: String?,
        var user: String?,
        var date: String?,
    )
}

ทำคลาสให้เป็น Serializable

ให้คลาสทำการ Implement จาก Serializable ได้เลย รวมไปถึง Inner Class ด้วย

data class Post(
    var title: String?,
    var content: String?,
    var id: String?,
    var date: String?,
    var author: String?,
    var url: String?,
    var readCount: Int,
    var isDraft: Boolean,
    var tagist: List<String>?,
    var commentList: List<Comment>?
) : Serializable {
    data class Comment(
        var comment: String?,
        var user: String?,
        var date: String?
    ) : Serializable
}

จริงๆแล้ว Model Class ตัวไหนที่เป็น Serializable ควรจะประกาศตัวแปร String ที่ชื่อว่า serialVersionUID ไว้ด้วย แต่เนื่องจากเจ้าของบล็อกแค่สร้างขึ้นมาเพื่อทดสอบการทำงานเฉยๆ ดังนั้นจึงไม่ได้ประกาศไว้

ทำคลาสให้เป็น Parcelable

ให้คลาสทำการ Implement จาก Parcelable ได้เลย รวมไปถึง Inner Class ด้วย

@Parcelize
data class Post(
    var title: String?,
    var content: String?,
    var id: String?,
    var date: String?,
    var author: String?,
    var url: String?,
    var readCount: Int,
    var isDraft: Boolean,
    var tagist: List<String>?,
    var commentList: List<Comment>?
) : Parcelable {
    @Parcelize
    data class Comment(
        var comment: String?,
        var user: String?,
        var date: String?
    ) : Parcelable
}

เนื่องจากเป็น Kotlin จึงสามารถใช้ @Parcelize ซึ่งเป็น Annotation สำหรับ Parcelable โดยเฉพาะ ถ้าเป็นสมัย Java จะต้องประกาศ Boilerplate Code สำหรับ Parcelable แทน

แล้ว Parcelable มันดีกว่า Serializable ยังไง?

Serializable นั้นใช้วิธี Reflection ซึ่งรู้กันอยู่แล้วว่า Performance มันไม่ค่อยดีซักเท่าไร จึงทำให้ทีมแอนดรอยด์สร้าง Parcelable ขึ้นมาเพื่อใช้วิธีอื่นแทน แต่ก็ต้องแลกด้วย Boilerplate Code อย่างที่เห็นในตัวอย่างนั่นแหละ

แล้วมันดีกว่าแค่ไหนล่ะ?

นั่นสิ Performance มันดีมากถึงขนาดที่ต้องแลกกับ Boilerplate Code เลยหรอ? ดังนั้นเจ้าของบล็อกจึงลองทดสอบแบบง่ายๆดูเพื่อเทียบว่า Parcelable มันเร็วกว่า Serializable มากแค่ไหน

เพื่อไม่ให้สับสนระหว่างคลาสที่เป็น Parcelable กับ Serializable ดังนั้นเจ้าของบล็อกจึงแยกคลาส Post ออกเป็น 2 ตัวคือ PostParcelable กับ PostSerializable โดยทั้ง 2 คลาสเก็บข้อมูลเหมือนกันทั้งหมด

และในการทดสอบจะมีขั้นตอนดังนี้

  • สร้าง Post ขึ้นมาเพื่อทดสอบ
  • สร้าง Bundle ขึ้นมาแล้วเก็บ Post ลงใน Bundle
  • ให้ Bundle เรียกคำสั่ง writeToParcel(parcel: Parcel, i: Int)
  • ดึงข้อมูล Post จาก Bundle ออกมา
// Parcelable
val bundle = Bundle()
bundle.putParcelable(KEY_POST, createPostParcelable())
bundle.writeToParcel(Parcel.obtain(), 0)
val post = bundle.getParcelable(KEY_POST) as PostParcelable?

// Serializable
val bundle = Bundle()
bundle.putSerializable(KEY_POST, createPostSerializable())
bundle.writeToParcel(Parcel.obtain(), 0)
val post = bundle.getSerializable(KEY_POST) as PostSerializable?

สามารถดูโค้ดที่ใช้ทดสอบได้ที่ Parcelable Serializable Test [GitHub]

akexorcist/ParcelableSerializableTest
[Android] Performance testing for Parcelable and Serializable - akexorcist/ParcelableSerializableTest

เจ้าของบล็อกจะทดสอบทั้งหมด 10,000 ครั้ง เพื่อดูว่าแต่ละวิธีนั้นใช้เวลาในการทำงานเฉลี่ยเท่าไร โดยทดสอบบน 2 เครื่องดังนี้

  • Samsung Galaxy Note 5 (Android 6.0)
  • Moto X 1st Generation (Android 5.1)

ได้ผลลัพธ์ดังนี้

Samsung Galaxy Note 5

  • Parcelable — 0.0640 ms
  • Serializable — 0.1859 ms

เร็วกว่า 2.90 เท่า

Moto X 1st Generation

  • Parcelable — 0.2613 ms
  • Serializable — 0.8283 ms

เร็วกว่า 3.16 เท่า

ต่างกันเยอะพอสมควรเลยนะเนี่ย

สรุป

เดิมทีนั้น Serializable ถูกสร้างขึ้นมาเพื่อใช้งานบน Java มาตั้งแต่แรกอยู่แล้ว แต่เนื่องจากใช้ Reflection เป็นเบื้องหลังการทำงานจึงทำให้มี Performance ไม่ค่อยปลื้มมากนัก ทีมแอนดรอยด์จึงพัฒนา Parcelable ขึ้นมาเพื่อใช้งานทดแทน โดยแลกกับการเขียน Boilerplate Code ใน Model Class ทุกครั้ง (แก้ปัญหาด้วยไลบรารี Parceler ได้)

เมื่อทดสอบความเร็วในการทำงานของ Parcelable กับ Serializable ก็พบว่าการใช้ Parcelable มีประสิทธิภาพดีกว่า Serializable อย่างเห็นได้ชัด จึงไม่แปลกใจว่าทำไมการใช้ Parcelable เพื่อส่งข้อมูลผ่าน Bundle จึงเป็น Best Practice สำหรับนักพัฒนาแอนดรอยด์

ถึงแม้ว่าจะใช้ Serializable ได้อยู่ก็จริง แต่ก็อย่าลืมว่ามันมี Cost ที่แพงกว่า Parcelable นะ

แหล่งข้อมูลอ้างอิง​