Trình bổ trợ kotlin-parcelize
cung cấp một trình tạo lượt triển khai Parcelable
.
Để hỗ trợ Parcelable
, hãy thêm trình bổ trợ Gradle vào tệp build.gradle
của ứng dụng:
Groovy
plugins { id 'kotlin-parcelize' }
Kotlin
plugins { id("kotlin-parcelize") }
Khi bạn chú thích một lớp (class) bằng @Parcelize
, phương thức triển khai Parcelable
sẽ tự động được tạo, như trong ví dụ sau:
import kotlinx.parcelize.Parcelize
@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable
@Parcelize
đòi hỏi khai báo tất cả thuộc tính tuần tự trong hàm khởi tạo chính. Trình bổ trợ này cảnh báo về từng thuộc tính có một trường sao lưu được khai báo trong nội dung lớp. Ngoài ra, bạn không thể áp dụng @Parcelize
nếu một số tham số của hàm khởi tạo chính không phải là thuộc tính.
Nếu lớp của bạn yêu cầu logic tuần tự hoá nâng cao hơn, hãy ghi lớp này vào trong lớp đồng hành (companion class):
@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
private companion object : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
// Custom write implementation
}
override fun create(parcel: Parcel): User {
// Custom read implementation
}
}
}
Các kiểu được hỗ trợ
@Parcelize
hỗ trợ nhiều kiểu:
- Kiểu nguyên bản (và các phiên bản đóng hộp)
- Đối tượng và enum
String
,CharSequence
Duration
Exception
Size
,SizeF
,Bundle
,IBinder
,IInterface
,FileDescriptor
SparseArray
,SparseIntArray
,SparseLongArray
,SparseBooleanArray
- Tất cả cách triển khai
Serializable
(bao gồm cảDate
) vàParcelable
- Bộ sưu tập toàn bộ kiểu được hỗ trợ:
List
(được ánh xạ tớiArrayList
),Set
(được ánh xạ tớiLinkedHashSet
),Map
(được ánh xạ tớiLinkedHashMap
)- Ngoài ra, có một số cách triển khai cụ thể:
ArrayList
,LinkedList
,SortedSet
,NavigableSet
,HashSet
,LinkedHashSet
,TreeSet
,SortedMap
,NavigableMap
,HashMap
,LinkedHashMap
,TreeMap
,ConcurrentHashMap
- Ngoài ra, có một số cách triển khai cụ thể:
- Mảng (array) thuộc mọi kiểu được hỗ trợ
- Phiên bản có tính chất rỗng của mọi kiểu được hỗ trợ
Parceler
tuỳ chỉnh
Nếu kiểu của bạn không được hỗ trợ trực tiếp, bạn có thể viết một đối tượng ánh xạ Parceler
cho kiểu đó.
class ExternalClass(val value: Int)
object ExternalClassParceler : Parceler<ExternalClass> {
override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
override fun ExternalClass.write(parcel: Parcel, flags: Int) {
parcel.writeInt(value)
}
}
Bạn có thể áp dụng các trình đóng gói (parceler) bên ngoài bằng chú thích @TypeParceler
hoặc @WriteWith
:
// Class-local parceler
@Parcelize
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass) : Parcelable
// Property-local parceler
@Parcelize
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass) : Parcelable
// Type-local parceler
@Parcelize
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass) : Parcelable
Tạo dữ liệu trong Parcel
Trong mã Java, bạn có thể truy cập trực tiếp vào trường CREATOR
.
class UserCreator {
static User fromParcel(Parcel parcel) {
return User.CREATOR.createFromParcel(parcel);
}
}
Trong Kotlin, bạn không thể trực tiếp sử dụng trường CREATOR
. Thay vào đó, hãy sử dụng kotlinx.parcelize.parcelableCreator
.
import kotlinx.parcelize.parcelableCreator
fun userFromParcel(parcel: Parcel): User {
return parcelableCreator<User>().createFromParcel(parcel)
}
Bỏ qua các thuộc tính trong quá trình chuyển đổi tuần tự
Nếu bạn muốn bỏ qua một số thuộc tính không được phân đoạn, hãy sử dụng chú thích @IgnoredOnParcel
. Bạn cũng có thể dùng thuộc tính này trên các thuộc tính trong phần nội dung của một lớp để tắt tiếng các cảnh báo về việc thuộc tính không được chuyển đổi tuần tự.
Các thuộc tính hàm khởi tạo được chú thích bằng @IgnoredOnParcel
phải có giá trị mặc định.
@Parcelize
class MyClass(
val include: String,
// Don't serialize this property
@IgnoredOnParcel val ignore: String = "default"
): Parcelable {
// Silence a warning
@IgnoredOnParcel
val computed: String = include + ignore
}
Sử dụng android.os.Parcel.writeValue để chuyển đổi tuần tự một thuộc tính
Bạn có thể chú thích một loại bằng @RawValue
để Parcelize sử dụng Parcel.writeValue
cho thuộc tính đó.
@Parcelize
class MyClass(val external: @RawValue ExternalClass): Parcelable
Thao tác này có thể không thành công trong thời gian chạy nếu giá trị của thuộc tính không được Android hỗ trợ sẵn.
Parcelize cũng có thể yêu cầu bạn sử dụng chú thích này khi không có cách nào khác để chuyển đổi tuần tự thuộc tính.
Đóng gói bằng các lớp kín và giao diện kín
Việc phân vùng yêu cầu phải phân vùng một lớp sao cho không phải là lớp trừu tượng. Hạn chế này không áp dụng cho các lớp kín. Khi chú thích @Parcelize
được sử dụng trên một lớp kín, bạn không cần lặp lại chú thích này cho các lớp phái sinh.
@Parcelize
sealed class SealedClass: Parcelable {
class A(val a: String): SealedClass()
class B(val b: Int): SealedClass()
}
@Parcelize
class MyClass(val a: SealedClass.A, val b: SealedClass.B, val c: SealedClass): Parcelable
Thiết lập Parcelize cho Kotlin đa nền tảng
Trước Kotlin 2.0, bạn có thể sử dụng Parcelize bằng cách đặt bí danh cho các chú giải Parcelize
bằng expect
và actual
:
// Common code
package example
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
expect annotation class MyParcelize()
expect interface MyParcelable
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()
@MyParcelize
class MyClass(
val x: String,
@MyIgnoredOnParcel val y: String = ""
): MyParcelable
// Platform code
package example
actual typealias MyParcelize = kotlinx.parcelize.Parcelize
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel
Trong Kotlin 2.0 trở lên, các chú giải đại diện kích hoạt trình bổ trợ sẽ không được hỗ trợ. Để khắc phục vấn đề này, hãy cung cấp chú thích Parcelize
mới làm tham số additionalAnnotation
cho trình bổ trợ.
// Gradle build configuration
kotlin {
androidTarget {
compilerOptions {
// ...
freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:additionalAnnotation=example.MyParcelize")
}
}
}
// Common code
package example
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
// No `expect` keyword here
annotation class MyParcelize()
expect interface MyParcelable
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()
@MyParcelize
class MyClass(
val x: String,
@MyIgnoredOnParcel val y: String = ""
): MyParcelable
// Platform code
package example
// No typealias for MyParcelize here
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel
Vì giao diện Parcel
chỉ có trên Android, nên Parcelize sẽ không tạo bất kỳ mã nào trên các nền tảng khác, vì vậy, mọi hoạt động triển khai actual
có thể bị trống. Bạn cũng không thể sử dụng bất kỳ chú giải nào yêu cầu tham chiếu đến lớp Parcel
, chẳng hạn như @WriteWith
, trong mã chung.
Các tính năng thử nghiệm
Trình chuyển đổi tuần tự lớp dữ liệu
Được cung cấp kể từ phiên bản Kotlin 2.1.0.
Chú giải DataClass
cho phép chuyển đổi tuần tự các lớp dữ liệu như thể các lớp đó được chú thích bằng Parcelize
. Chú thích này yêu cầu bạn chọn sử dụng kotlinx.parcelize.Experimental
.
@file:OptIn(kotlinx.parcelize.Experimental::class)
data class C(val a: Int, val b: String)
@Parcelize
class P(val c: @DataClass C) : Parcelable
Bạn phải truy cập được hàm khởi tạo chính và tất cả thuộc tính của hàm đó từ lớp Parcelable
. Ngoài ra, Parcelize
phải hỗ trợ tất cả thuộc tính hàm khởi tạo chính của lớp dữ liệu.
Bạn nên chỉ định Parceler tuỳ chỉnh (nếu được chọn) trên lớp Parcelable
, chứ không phải trên lớp dữ liệu.
Nếu lớp dữ liệu triển khai Serializable
cùng một lúc, thì chú thích @DataClass
sẽ được ưu tiên: android.os.Parcel.writeSerializable
sẽ không được sử dụng.
Một trường hợp sử dụng thực tế cho việc này là chuyển đổi tuần tự kotlin.Pair
.
Một ví dụ hữu ích khác là đơn giản hoá mã đa nền tảng: mã chung có thể khai báo lớp dữ liệu dưới dạng lớp dữ liệu, sau đó mã Android có thể bổ sung logic chuyển đổi tuần tự, không cần chú thích dành riêng cho Android và bí danh loại trong mã chung.
// Common code:
data class MyData(val x: String, val y: MoreData)
data class MoreData(val a: String, val b: Int)
// Platform code:
@OptIn(kotlinx.parcelize.Experimental::class)
@Parcelize
class DataWrapper(val wrapped: @DataClass MyData): Parcelable
Tham số không phải val hoặc var trong hàm khởi tạo chính
Được cung cấp kể từ phiên bản Kotlin 2.1.0.
Để bật tính năng này, hãy thêm experimentalCodeGeneration=true
vào các đối số của trình bổ trợ phân vùng.
kotlin {
compilerOptions {
// ...
freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:experimentalCodeGeneration=true")
}
}
Tính năng này sẽ gỡ bỏ quy định hạn chế đối với đối số hàm khởi tạo chính phải là val
hoặc var
. Điều này giải quyết một vấn đề khó khăn khi sử dụng tính năng phân phối gói với tính năng kế thừa, trước đây yêu cầu sử dụng các thuộc tính open
.
// base parcelize
@Parcelize
open class Base(open val s: String): Parcelable
@Parcelize
class Derived(
val x: Int,
// all arguments have to be `val` or `var` so we need to override
// to not introduce new property name
override val s: String
): Base(s)
// experimental code generation enabled
@Parcelize
open class Base(val s: String): Parcelable
@Parcelize
class Derived(val x: Int, s: String): Base(s)
Bạn chỉ được phép sử dụng các tham số như vậy trong đối số cho hàm khởi tạo lớp cơ sở. Bạn không được tham chiếu các thuộc tính này trong phần nội dung của lớp.
@Parcelize
class Derived(s: String): Base(s) { // allowed
@IgnoredOnParcel
val x: String = s // ERROR: not allowed.
init {
println(s) // ERROR: not allowed
}
}
Phản hồi
Nếu gặp vấn đề với trình bổ trợ kotlin-parcelize
cho Gradle, bạn có thể báo cáo lỗi.