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.
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.
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);
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.
Owszem. A jeszcze lepiej Kotlin + @Parcelize
. Niemniej, ważne jest rozumieć co się dzieje po spodem.
true.
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ę.
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()
idescribeContents()
. Dodatkowo, aby zapewnić poprawność zapisu/odczytu danych w swojej klasie musisz mieć publiczne statyczne pole typuCreator<MojaKlasa>
, które musi dodatkowo zaimplementować dwie metody -createFromParcel()
inewArray()
. Przykładowo, klasaPokemon
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];
}
- 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
orazint 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.
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.
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
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.
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.
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.Serializable
.