OK
Parcelable vs Serializable – Podstawy Androida

Parcelable vs Serializable – Podstawy Androida

Parcelable vs Serializable – Podstawy Androida

Podczas tworzenia aplikacji w androidzie na pewno każdy z nas spotkał się z problemem przekazywania danych pomiędzy aktywnościami czy fragmentami aplikacji. Zwykle wykorzystujemy wtedy narzędzie które daje nam android czyli Bundle. W przypadku gdy mamy do przekazania proste obiekty takie jak String czy Integer nie napotykamy żadnych problemów. Pojawiają się one w momencie gdy mamy do przesłania większy obiekt, składający się z prostych obiektów. W takim przypadku mamy kilka opcji na przesłanie takiego obiektu poprzez Bundle. Jednymi z opcji jest wykorzystanie implementacji interfejsów Parcelable oraz Serializable. Z pozoru dają nam one takie same możliwości i efekt jest taki sam- przesłanie obiektu, jednak są to tylko pozory, a w tym artykule chciałbym przyjrzeć się różnicom pomiędzy nimi.

Serializable

Serializable to prosty javowy interfejs. wykorzystujący mechanizm refleksji, który jak wszyscy dobrze wiemy potrafi przysporzyć wiele problemów. Standardowa implementacja Serializable nie wymaga żadnych dodatkowych metod. Obok deklaracji naszej klasy dodajemy proste „implements Serializable” lub „: Serializable” w przypadku Kotlina i możemy wykorzystywać ten interfejs. Największym plusem Serializable jest właśnie jego prostota oraz brak potrzeby pisania dodatkowego kodu do implementacji tego interfejsu.

data class SerializableClass(val id: Int,val name: String,
val person:SerializablePerson,val city:  SerializableCity ): Serializable

class SerializableCity(val cityName: String, val population: Int)

data class SerializablePerson (val name: String, val age: Int)

Jednak ta prostota przychodzi z pewnym kosztem. Standardowa serializacja oparta o Serializable działa bardzo wolno. Podczas pisania zwykłych aplikacji możemy tego nie zauważyć, jednak w Androidzie gdzie obraz dla zachowania standardowych 60 klatek na sekundę odświeża się co 16 ms możemy zauważyć spowolnienia przy przesyłaniu dużych obiektów. (Podczas obracania ekranu czy szybkiego przechodzenie pomiędzy aktywnościami.) Swoją drogą… zauważyliście jakiś błąd w klasach SerializableCity oraz SerializablePerson? Żadna z nich nie implementuje interfejsu Serializable co spowoduje wyjątek podczas działania aplikacji. Niestety, podczas kompilacji nie otrzymamy żadnego ostrzeżenia przed naszym błędem. Jest to kolejny minus przy korzystaniu z Serializable. W przypadku gdybyśmy chcieli zrobić coś podobnego z wykorzystaniem Parcelable kompilator wyrzuciłby wyjątek. Na pomoc przychodzi inny interfejs o którym wspominałem wcześniej czyli Parcelable.A skoro Parcelable zostało już wywołane do tablicy, to przyjrzyjmy się mu trochę bliżej…

Parcelable

Parcelable jest interfejsem stworzonym przez Google na potrzeby Androida. Umożliwia nam przesyłanie obiektów pomiędzy aktywnościami oraz fragmentami. Według inżynierów Google jest on nawet 10 razy szybszy od serializable, co czyni go preferowanym sposobem do przesyłania obiektów pomiędzy fragmentami oraz aktywnościami.

import android.os.Parcel 
import android.os.Parcelable 

class ParcelableClass(
    private val id: Int, 
    private val name: String, 
    private val parcelablePerson: ParcelablePerson, 
    private val parcelableCity: ParcelableCity ): Parcelable {

     constructor(parcel: Parcel) : this( 
      parcel.readInt(), 
      parcel.readString() ?: "", 
      parcel.readParcelable(ParcelablePerson::class.java.classLoader)!!, 
      parcel.readParcelable(ParcelableCity::class.java.classLoader)!! ) 


      override fun writeToParcel(parcel: Parcel, flags: Int) { 
           parcel.writeInt(id) 
           parcel.writeString(name) 
           parcel.writeParcelable(parcelablePerson, flags) 
           parcel.writeParcelable(parcelableCity, flags) 
      } 

      override fun describeContents(): Int = 0

      companion object CREATOR : Parcelable.Creator<ParcelableClass> { 
          override fun createFromParcel(parcel: Parcel): ParcelableClass =  
                 ParcelableClass(parcel)
 
          override fun newArray(size: Int): Array<ParcelableClass?> = 
                 arrayOfNulls(size)  
       } 
} 

data class ParcelablePerson(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<ParcelablePerson> {

        override fun createFromParcel(parcel: Parcel): ParcelablePerson =
            ParcelablePerson(parcel)
        
        override fun newArray(size: Int): Array<ParcelablePerson?> =
            arrayOfNulls(size)
        
    }
}

data class ParcelableCity(val cityName: String, val population: Int) : Parcelable {
    constructor(parcel: Parcel) : this(parcel.readString() ?: "", parcel.readInt())

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

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<ParcelableCity> {
        override fun createFromParcel(parcel: Parcel): ParcelableCity =
            ParcelableCity(parcel)
        

        override fun newArray(size: Int): Array<ParcelableCity?> =
            arrayOfNulls(size)
    }
}

Jak można zauważyć na powyższym przykładzie, przyśpieszenie działania jest jednak osiągane poprzez dodatkowy kod jaki trzeba dodać do klasy, aby zaimplementować Parcelable. Z prostej klasy zajmującej kilka linijek musimy dodać kolejne 50 lub więcej jeśli nasza klasa jest rozbudowana. Dodatkowym problemem jest utrzymanie projektu. Każda zmiana w klasie taka jak dodanie nowej zmiennej lub zmiana nazwy jednej z istniejących, powoduje potrzebę dodatkowych zmian w konstruktorze oraz w metodzie writeToParcel.

Testy

Czy testy potwierdzają informacje podane przez Google? Jak się okazuję i tak… i nie. Porównanie standardowej implementacji Serializable oraz Parcelable daje oczekiwane rezultaty. Parcelable jest do 10 razy szybsze niż interfejs dostarczony przez Javę.

Okazuje się jednak, że dzieje się tak tylko i wyłącznie w przypadku, gdy używany standardowej implementacji Serializable wykorzystującej refleksje. Po ręcznym zaimplementowaniu tego interfejsu okazuje się, że jest ono szybsze od Parcelable.

A więc… co wybrać?

Zagmatwane prawda? Każdy interfejs zdaje się mieć swoje plusy i minusy. Standardowa implementacja Serializable nie wymaga dodatkowego kodu i jest prosta w utrzymaniu. Jest to jednak osiągane kosztem prędkości, tak potrzebnej w aplikacjach na system dostarczony przez Google. Parcelable z kolei działa szybciej, lecz jest trudniejsze w utrzymaniu. Ma jednak jedną przewagę ponad ręczną implementacją Serializable. Kod, który jest nam potrzebny do implementacji Parcelable może zostać automatycznie wygenerowany przez nasze IDE, nie musimy myśleć, jak to powinno wyglądać.W zależności od naszych potrzeb możemy wybrać jedną z tych opcji. Gdy zależy nam na prostocie – standardowa implementacja interfejsu Serializable wydaje się być idealna. Gdy potrzebujemy szybszego rozwiązania przychodzi nam na pomoc Parcelable z automatycznie generowanym kodem. W przypadku, gdy potrzebujemy najszybciej działającego rozwiązania możemy również ręcznie zaimplementować Serializable, co da nam najlepsze efekty.

Źródła:

ul. Jaracza 62
90-251 Łódź
Bitnami