Parcelabe - co to dokładnie jest i jak tego używać

0

Witajcie,

czy mógłby ktoś po polsku wyjaśnić mi co to jest Parcelable i jak dokładnie się tego używa? Wiem tylko tyle, że Parcelable jest o wiele szybsze niż Seriazable.

Z góry dziękuje.

4

W dużym skrócie - komponenty specyficzne dla Androida przesyłają między sobą dane za pomocą klasy Parcel w przypadku różnych procesów lub Bundle w przypadku tego samego procesu. Niezależnie od tego, który z mechanizmów zostanie użyty, musisz tam wrzucić jakoś swoje dane. Załóżmy, że masz klasę Pokemon i chcesz przesłać obiekt stworzony w jednym Activity do drugiego. Aby to zrobić musisz utworzyć Intent dla nowego Activity i zapisać w nim swojego pokemona (Intent pod spodem korzysta z Bundle).

class Pokemon {
    final int level;
    final String name;

    Pokemon(int level, String name) {
        this.level = level;
        this.name = name;
    }
}

Tak zdefiniowanej klasy niestety nie możesz przesłać w łatwy sposób. Możesz skorzystać z Javowego interfejsu Serializable i zaimplementować go w swojej klasie.

class Pokemon implements Serializable {
    private static final long serialVersionUID = 1L;

    final int level;
    final String name;

    Pokemon(int level, String name) {
        this.level = level;
        this.name = name;
    }
}

Dzięki temu, możesz teraz wrzucić swoją klasę w Intent (czyli w Bundle).

Intent intent = new Intent();
Pokemon pokemon = new Pokemon(1, "Pikachu");
intent.putExtra("Pokemon", pokemon);

Niby wszystko ok, ale serializacja obiektu jest jednak powolna (relatywnie powolna, co ma znaczenie na urządzeniach mobilnych). Z tego powodu powstał interfejs Parcelable, który definiuje dwie metody - writeToParcel() i describeContents(). Dodatkowo, aby zapewnić poprawność zapisu/odczytu danych w swojej klasie musisz mieć publiczne statyczne pole typu Creator<MojaKlasa>, które musi dodatkowo zaimplementować dwie metody - createFromParcel() i newArray(). Przykładowo, klasa Pokemon wyglądałaby teraz w ten sposób.

class Pokemon implements Parcelable {
    final int level;
    final String name;

    Pokemon(int level, String name) {
        this.level = level;
        this.name = name;
    }

    // Kod poniżej jest potrzebny ze względu na implementację Parcelable.

    // Ta metoda opisuje czy w naszej klasie jest jakiś specjalny obiekt. Z reguły powinieneś zwracać tutaj 0.
    // Inne wartości są używane w nietypowych przypadkach.
    @Override
    public int describeContents() {
        return 0;
    }

    // Tutaj definiujemy, jak nasza klasa jest zapisywana do Parcel.
    // Najpierw zapisujemy poziom pokemona a następnie jego imię.
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(level);
        dest.writeString(name);
    }

    public static final Creator<Pokemon> CREATOR = new Creator<Pokemon>() {

        // Ta metoda odczytuje naszą klasę z obiektu typu Parcel.
        // Najpierw odczytujemy poziom pokemona, potem imię a na końcu tworzymy go z powrotem z tych danych.
        @Override
        public Pokemon createFromParcel(Parcel in) {
            int level = in.readInt();
            String name = in.readString();
            return new Pokemon(level, name);
        }

        // Ta metoda prealokuje tablicę dla elementów naszej klasy w przypadku, gdybyśmy wysyłali więcej niż jeden obiekt.
        @Override
        public Pokemon[] newArray(int size) {
            return new Pokemon[size];
        }
    };
}

Zwróć uwagę, że kolejność zapisywania i odczytywania z paczki musi być zachowana, aby móc poprawnie przesłać obiekt. Teraz, żeby obiekt tego typu wrzucić w Intent wykonasz dokładnie te same oparcje, co w przypadku Serializable. Różnia będzie jednak taka, że do rozłożenia pokemona na części pierwsze i potem złożenie go z powrotem zostanie użyty interfejs Parcelable a nie Serializable, co w efekcie będzie szybsze.

Intent intent = new Intent();
Pokemon pokemon = new Pokemon(1, "Pikachu");
intent.putExtra("Pokemon", pokemon);
0

ważna uwaga do postu wyżej, sa gotowe biblioteki które generują ten kod i oszczędzają sporo czasy i pozwalają unikać błędów.

0

Owszem. A jeszcze lepiej Kotlin + @Parcelize. Niemniej, ważne jest rozumieć co się dzieje po spodem.

0

true.

0

Dziękuje Wam serdecznie.

Dziękuję Michał. Super odpowiedź. Pozwól, że dopytam Cię jeszcze o szczegóły gdybym miał jakieś pytania.. Oczywiście łapka w górę.

0

Niby wszystko ok, ale serializacja obiektu jest jednak powolna (relatywnie powolna, co ma znaczenie na urządzeniach mobilnych). Z tego powodu powstał interfejs Parcelable, który definiuje dwie metody - writeToParcel() i describeContents(). Dodatkowo, aby zapewnić poprawność zapisu/odczytu danych w swojej klasie musisz mieć publiczne statyczne pole typu Creator<MojaKlasa>, które musi dodatkowo zaimplementować dwie metody - createFromParcel() i newArray(). Przykładowo, klasa Pokemon wyglądałaby teraz w ten sposób.

Tutaj writeToParcel rozumiem, bo to zapisywanie danych w tej 'paczce' .A po co jest:

  • ta druga metoda describeContents (piszesz o 'specjalnym obiekcie'.. a możesz rozwinąć co to..?) oraz

  • to pole statyczne.

Dlaczego w tej metodzie parametry to Parcel dest oraz int flags?

 public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(level);
        dest.writeString(name);
    }

I co oznacza dokładnie że 'metoda prelokuje tablice dla więcej niż jednego obiektu'?

public Pokemon[] newArray(int size) {
            return new Pokemon[size];
        }


1
  • ta druga metoda describeContents() (piszesz o 'specjalnym obiekcie'.. a możesz rozwinąć co to..?)

describeContents() powinno zwracać na chwilę obecną 0 albo Parcelable.CONTENTS_FILE_DESCRIPTOR, którego wartość jest równa 1. Obecnie, jedynym specjalnym obiektem jest FileDescriptor. Czyli, gdybyś w swojej klasie za pomocą Parcelable chciał przekazać FileDescriptor musiałbyś zwrócić odpowiednią flagę z describeContents(), ale naprawdę nie powinieneś się tym przejmować, bo nie sądzę, że spotkasz się z taką potrzebą. W skrócie — zawsze zwracaj 0.

  • to pole statyczne.

Pole statyczne o nazwie CREATOR jest potrzebne ze względu na to, jak tworzone są obiekty implementujące Parcelable z Parcel i ze względu na ograniczenia, jakie narzuca Java. Pole CREATOR jest odczytywane za pomocą refleksji. Następnie, tworzenie obiektu jest robione za pomocą metody createFromParcel(). Gdyby tego pola nie było, nie miałbyś żadnego mechanizmu, który pozwoliłby na stworzenie Twojego obiektu z Parcel.

Dlaczego w tej metodzie parametry to Parcel dest oraz int flags?

Parcel dest to obiekt, do którego zapisujesz obiekty swojej klasy pole po polu. int flags służy do poinformowania osoby, która implementuje Parcelable o specjalnych sytuacjach. Obecnie, jedyna specjalna okazja ma miejsce, gdy flaga ma wartość Parcelable.PARCELABLE_WRITE_RETURN_VALUE równą 1. Niektóre implementacje mogą dzięki tej informacji zwolić jakieś zasoby. Z tego co wiem, jest to wykorzystywane tylko w sytuacji, gdy masz komunikację między różnymi procesami np. za pomocą ContentProvider, ale głowy sobie nie dam uciąć, że to jedyny przypadek. Tak czy inaczej — wątpię, że będziesz musiał się tym przejmować kiedykolwiek. Musiałbyś robić naprawdę zaawansowane rzeczy, żeby trafić na taki przypadek.

I co oznacza dokładnie że 'metoda prelokuje tablice dla więcej niż jednego obiektu'?

Czasami Android musi wydzielić zasoby pamięci dla tablicy obiektów implementujących Parcelable. Po pierwsze dzięki tej metodzie nie trzeba się bawić z generycznymi parametrami. Po drugie, gdyby domyślnie zostało zaalokowane za mało pamięci to podczas wrzucania obiektów do tablicy w pewnym momencie musiałaby być ona powiększona, co wydłużyłoby proces serializacji, a Parcelable zostało stworzone z myślą o byciu jak najszybszym. Podobnie jak w pozostałych przypadkach sam tej metody raczej nigdy nie wywołasz (w typowych zastosowaniach Android też z niej nie będzie korzystał), więc nie ma się czym zbytnio przejmować. Dla świętego spokoju jest jednak lepiej zwracać tablicę o odpowiednim rozmiarze.

0

Serdecznie dziękuję.

Muszę Ci powiedzieć, że w mojej dopiero początkującej przygodzie z programowaniem (1 rok) nie spotkałem takiej osoby jak Ty, która umie wyjaśnić temat w sposób zrozumiały dla laika.. Mam tylko problem ze zrozumieniem co to ten FileDescriptor nawet z linka który podałeś. Ale mniejsza o to.

Dziękuję Ci przede wszystkim za chęć. Ale także za sposób i przekazaną wiedze. Zapewne nieraz będę wracał do tego postu.

0

Warto też wiedzieć że można zaimplementować własną logikę zapisywania i odczytywania danych korzystając z interfejsu Serializable, w takim przypadku możemy uzyskać nawet lepszą wydajność niż przy Parcelable

https://android.jlelse.eu/parcelable-vs-serializable-6a2556d51538

0

Matthi, dziękuję. To też ważna wiadomość. Chciałbym się wypowiedzieć mądrzej ale ze względu na brak wiedzy po prostu zapisze ten link żeby go postudiować w wolnym czasie.

0
Matthi napisał(a):

Warto też wiedzieć że można zaimplementować własną logikę zapisywania i odczytywania danych korzystając z interfejsu Serializable, w takim przypadku możemy uzyskać nawet lepszą wydajność niż przy Parcelable

https://android.jlelse.eu/parcelable-vs-serializable-6a2556d51538

Niby prawda, ale ta trzecia prawda.

  • W przypadku tak zagnieżdżonej struktury jak tej z artykułu własna implementacja Serializable faktycznie będzie szybsza. Nie jest to jednak typowe zastosowanie mechanizmu. Parcelable wygrywa pod względem szybkości w przypadku prostych klas, które są de facto ValueObjectami. Poza tym, jeżeli ktoś przekazuje tyle danych, że ich serializacja/deserializacja zajmuje ponad 100 ms i robi to na głównym wątku, to musi pomyśleć nad innym rozwiązaniem.
  • Serializable nie może być przekazane z jednego procesu do drugiego. Taka możliwość istnieje tylko dla Parcelable. Jest w sumie do przeżycia, jeżeli piszemy prostą aplikację, która niczego nie udostępnia na zewnątrz.
  • Nasza klasa musi być w takim wypadku mutowalna. Można niby wszystko na private poustawiać i dać gettery dla każdego pola, ale wolę dbać o swoje zdrowie psychiczne i takich rzeczy nie robić. Dla mnie osobiście skreśla to całkowicie własną implementację Serializable.

1 użytkowników online, w tym zalogowanych: 0, gości: 1