Jak wysyłać obiekty w pakiecie

119

Muszę przekazać odwołanie do klasy, która wykonuje większość mojego przetwarzania, za pośrednictwem pakietu.

Problem polega na tym, że nie ma to nic wspólnego z intencjami ani kontekstami i zawiera dużą liczbę nieprymitywnych obiektów. Jak spakować klasę do pliku parcelable / serializowalnego i przekazać go do pliku startActivityForResult?

ahodder
źródło
2
„Muszę przekazać odwołanie do klasy, która wykonuje większość mojego przetwarzania za pośrednictwem pakietu” - dlaczego?
CommonsWare
1
Mam obiekt (DataManager), który obsługuje serwer i uruchamia kilka backendów dla niektórych GUI. Zawsze, gdy nawiązywane jest nowe połączenie, chcę, aby użytkownik mógł rozpocząć nowe działanie, które wyświetla w ListView wszystkie aktywne połączenia i poprosić użytkownika o wybranie jednego. Wynikowe dane zostaną następnie powiązane z nowym GUI. To naprawdę tylko wybór skórki dla zaplecza.
ahodder
3
Jeśli masz do czynienia z tym samym wystąpieniem obiektu w wielu działaniach, możesz rozważyć wzorzec singleton . Jest to dobry poradnik tutaj .
sotrh

Odpowiedzi:

55

Zastanowienie się, jaką ścieżkę obrać, wymaga odpowiedzi nie tylko na kluczowe pytanie CommonsWare „dlaczego”, ale także na pytanie „do czego?” zdajesz to.

W rzeczywistości jedyną rzeczą, która może przejść przez pakiety, są zwykłe dane - wszystko inne opiera się na interpretacjach tego, co te dane oznaczają lub na co wskazują. Nie możesz dosłownie przekazać obiektu, ale możesz zrobić jedną z trzech rzeczy:

1) Możesz rozbić obiekt na dane składowe, a jeśli to, co znajduje się po drugiej stronie, ma wiedzę o tym samym rodzaju obiektu, może złożyć klon z serializowanych danych. W ten sposób większość typowych typów przechodzi przez pakiety.

2) Możesz przejść przez nieprzezroczysty uchwyt. Jeśli przekazujesz go w tym samym kontekście (choć można by zapytać, po co się przejmować), będzie to uchwyt, który możesz wywołać lub wyłuskać. Ale jeśli przekażesz go przez Binder do innego kontekstu, jego wartość literalna będzie dowolną liczbą (w rzeczywistości te arbitralne liczby liczą się sekwencyjnie od uruchomienia). Nie możesz nic zrobić, ale śledzić to, dopóki nie przekażesz go z powrotem do oryginalnego kontekstu, co spowoduje, że Binder przekształci go z powrotem w oryginalny uchwyt, czyniąc go ponownie użytecznym.

3) Możesz przekazać magiczny uchwyt, taki jak deskryptor pliku lub odniesienie do pewnych obiektów systemu operacyjnego / platformy, a jeśli ustawisz odpowiednie flagi, Binder utworzy klon wskazujący na ten sam zasób dla odbiorcy, który może być faktycznie użyty na Drugi koniec. Ale działa to tylko w przypadku kilku typów obiektów.

Najprawdopodobniej albo przekazujesz swoją klasę tylko po to, aby drugi koniec mógł ją śledzić i zwrócić ci później, albo przekazujesz ją do kontekstu, w którym można utworzyć klon z serializowanych danych składowych ... albo też próbujesz zrobić coś, co po prostu nie zadziała i musisz przemyśleć całe podejście.

Chris Stratton
źródło
1
Dzięki za odpowiedź na wycieczkę. Masz prawo, wystarczy, że przekażę odniesienie do listy obiektów mojej nowej działalności. Nowe działanie pobierze niektóre dane z listy i wyświetli wybieralny ListView. onSelect, działanie zwróci wynik (niektóre dane dotyczące obiektu kliknięcia) do działania hosta. Jeśli dobrze rozumiem, uważam, że Wasza opcja 2 radzi sobie z tym najlepiej; jak uzyskać ten nieprzezroczysty uchwyt?
ahodder
Twoja inna aktywność nie może wyodrębnić żadnych danych z nieprzezroczystego obiektu do wyświetlenia. To, co prawdopodobnie chcesz zrobić, to utworzyć i przekazać kilka zastępczych obiektów obsługiwanego typu, które zawierają kopie wyświetlanych informacji.
Chris Stratton
158

Możesz także użyć Gson, aby przekonwertować obiekt na JSONObject i przekazać go w pakiecie. Dla mnie był to najbardziej elegancki sposób, w jaki to zrobiłem. Nie testowałem, jak to wpływa na wydajność.

W początkowej działalności

Intent activity = new Intent(MyActivity.this,NextActivity.class);
activity.putExtra("myObject", new Gson().toJson(myobject));
startActivity(activity);

W następnej czynności

String jsonMyObject;
Bundle extras = getIntent().getExtras();
if (extras != null) {
   jsonMyObject = extras.getString("myObject");
}
MyObject myObject = new Gson().fromJson(jsonMyObject, MyObject.class);
Laranjeiro
źródło
3
Ponieważ chodzi o przekazywanie rzeczy między czynnościami, nie zdarza się to na tyle często, aby mieć duży wpływ na ogólną wydajność aplikacji. Biorąc to pod uwagę, wątpię, czy zadziałałoby serializowanie DataManagera oryginalnego postu, ponieważ wygląda na to, że ma połączenia gniazd i inne podobne klasy.
britzl,
4
Również Google sugeruje to rozwiązanie zamiast Serializacji: sprawdź ostatnią sekcję „Zalecane alternatywy” na tej stronie z dokumentacją
TechNyquist,
3
tylko jako słowo ostrzeżenia, stosowałem tę technikę przez jakiś czas, ale istnieje ograniczenie pamięci do tego, co można przekazać jako ciąg, więc upewnij się, że twoje dane nie są zbyt duże.
jiduvah
Zobacz blog.madadipouya.com/2015/09/21/…, aby dowiedzieć się, jak dodać obsługę Gson do swojego projektu.
geekQ
Sprytne rozwiązanie, czapki z głów dla Ciebie!
Rohit Mandiwal
20

Parcelable interfejs jest dobrym sposobem, aby przekazać obiekt z zamiarem.

Jak mogę nadać przesyłkę moich niestandardowych obiektów? to całkiem dobra odpowiedź na temat korzystania z Parcelable

Oficjalne dokumenty Google zawierają również przykład

chris
źródło
1
lub też mogą być serializowane.
Jeffrey Blattman
1
Ale znacznie zmniejsza wydajność 10x !! Sprawdź ten test porównawczy: developerphil.com/parcelable-vs-serializable
saiyancoder
2
Komentarz +1 @ Mati, jednak umieszczenie go w kontekście 10x po zastosowaniu do pojedynczego obiektu jest odpowiednikiem 1 ms. Więc może nie tak źle, jak się wydaje.
pinoyyid
1
Zgodzić się. Problem występuje, gdy masz do czynienia z kolekcjami, co jest bardzo częstym przypadkiem użycia, jeśli pobierasz zasoby z interfejsu API Rest. Ale dla pojedynczego obiektu nie powinno to być coś notorycznego. W każdym razie, jeśli cały standardowy kod jest czymś, co przeszkadza , możesz wypróbować tę bibliotekę, która generuje to wszystko dla Ciebie: github.com/johncarl81/parceler . Naprawdę fajne podejście!
saiyancoder,
Uszkodzony link: 404 (nie znaleziono)
Gallal
14

Możesz użyć globalnego stanu aplikacji .

Aktualizacja:

Dostosuj, a następnie dodaj to do swojego pliku AndroidManifest.xml:

<application android:label="@string/app_name" android:debuggable="true" android:name=".CustomApplication"

A potem miej w swoim projekcie zajęcia takie jak ta:

package com.example;

import android.app.Application;

public class CustomApplication extends Application {
    public int someVariable = -1;
}

A ponieważ „ Dostęp do niego można uzyskać przez getApplication () z dowolnego działania lub usługi ”, używasz go w następujący sposób:

CustomApplication application = (CustomApplication)getApplication();
application.someVariable = 123; 

Mam nadzieję, że to pomoże.

Neil
źródło
1
Dzięki za odpowiedź, ale jak?
ahodder
Wierzę, że po prostu podklasujesz aplikację, a następnie możesz przechowywać wszystko, co chcesz. Potrzebne zmiany XML są wymienione w powyższym linku.
Mark Storer
9
Zgodnie z ogólną zasadą projektowania, dobrym pomysłem jest unikanie globali, chyba że naprawdę ich potrzebujesz. W takim przypadku istnieją dobre alternatywy.
dhaag23
Ok, myślę, że rozumiem to, co mówisz. Po prostu rozszerz aplikację i wrzuć zmienną zawierającą obiekt, który ma zostać przekazany; Przejrzałem stronę odniesienia i nie widziałem niezbędnych zmian XML.
ahodder
Też chciałem napisać to jako odpowiedź. To zdecydowanie jeden ze sposobów na zrobienie tego. Pamiętaj jednak, że te obiekty pozostają w pamięci, chyba że je usuniesz (lub kontekst aplikacji zostanie zniszczony) i mogą zajmować miejsce, gdy ich nie potrzebujesz.
Igor Čordaš
12

Możesz także uczynić swoje obiekty Serializowalnymi i użyć metod getSerializable i putSerializable pakietu .

dhaag23
źródło
1
Spróbowałem tego i szybko zdałem sobie sprawę, że byłoby to niepraktyczne. Nie sądzę, aby większość obiektów przechowywanych w przekazanej klasie (wątkach) była możliwa do serializacji. :) w każdym razie dzięki.
ahodder
10

Możliwe rozwiązanie:

Bundle bundle = new Bundle();
bundle.putSerializable("key", new CustomObject());

Klasa CustomObject:

class CustomObject implements Serializable{
 private SubCustomObject1 sc1;
 private SubCustomObject2 sc2;
}

Obiekty podrzędne:

class SubCustomObject1 implements Serializable{ }

class SubCustomObject2  implements Serializable{ }
PraKhar
źródło
7

Jeszcze jednym sposobem wysyłania obiektów za pośrednictwem pakietu jest użycie bundle.putByteArray
przykładowego kodu

public class DataBean implements Serializable {
private Date currentTime;

public setDate() {
    currentTime = Calendar.getInstance().getTime();
 }

public Date getCurrentTime() {
    return currentTime;
 }
}

umieść Object of DataBean w Bundle:

class FirstClass{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//When you want to start new Activity...
Intent dataIntent =new Intent(FirstClass.this, SecondClass.class);
            Bundle dataBundle=new Bundle();
            DataBean dataObj=new DataBean();
            dataObj.setDate();
            try {
                dataBundle.putByteArray("Obj_byte_array", object2Bytes(dataObj));

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }

            dataIntent.putExtras(dataBundle);

            startActivity(dataIntent);
}

Konwersja obiektów na tablice bajtowe

/**
 * Converting objects to byte arrays
 */
static public byte[] object2Bytes( Object o ) throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream( baos );
      oos.writeObject( o );
      return baos.toByteArray();
    }

Odzyskaj obiekt z paczki:

class SecondClass{
DataBean dataBean;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//Get Info from Bundle...
    Bundle infoBundle=getIntent().getExtras();
    try {
        dataBean = (DataBean)bytes2Object(infoBundle.getByteArray("Obj_byte_array"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Metoda pobierania obiektów z tablic bajtowych:

/**
 * Converting byte arrays to objects
 */
static public Object bytes2Object( byte raw[] )
        throws IOException, ClassNotFoundException {
      ByteArrayInputStream bais = new ByteArrayInputStream( raw );
      ObjectInputStream ois = new ObjectInputStream( bais );
      Object o = ois.readObject();
      return o;
    }

Mam nadzieję, że to pomoże innym kumplom.

Rupesh Yadav
źródło
wygląda to gładko i łatwo patrząc na kod. Ale czuję, że jest coś więcej na temat tego, dlaczego SDK nie oferuje czegoś takiego do przekazywania obiektów. Czy możesz powiedzieć coś więcej na temat tego rozwiązania?
Mario Lenci
3
Cały ten kod nie jest potrzebny! Użyj bundle.putSerializable (objectImplementingSerializable) - to robi pod tym, co ponownie
wdrażasz
3

1.Bardzo bezpośredni i łatwy w użyciu przykład, uczynienie obiektu, który ma zostać przekazany, implementuje możliwość serializacji.

class Object implements Serializable{
    String firstName;
   String lastName;
}

2. przekazać obiekt w pakiecie

Bundle bundle = new Bundle();
Object Object = new Object();
bundle.putSerializable("object", object);

3. get przekazany obiekt z pakietu jako Serializable, a następnie rzut do Object.

Object object = (Object) getArguments().getSerializable("object");
king_T
źródło
0

To bardzo spóźniona odpowiedź na moje własne pytanie, ale wciąż przykuwa uwagę, więc czuję, że muszę się nim zająć. Większość z tych odpowiedzi jest poprawna i doskonale radzi sobie z pracą. Zależy to jednak od potrzeb aplikacji. Ta odpowiedź posłuży do opisania dwóch rozwiązań tego problemu.

Podanie

Pierwszą z nich jest Aplikacja , ponieważ najczęściej mówiono o niej tutaj. Aplikacja jest dobrym obiektem do umieszczania jednostek, które wymagają odwołania do kontekstu. `ServerSocket` niewątpliwie potrzebowałby kontekstu (dla wejścia / wyjścia pliku lub prostych aktualizacji elementu ListAdapter). Ja osobiście wolę tę trasę. Lubię aplikacje, są przydatne do pobierania kontekstu (ponieważ mogą być statyczne i prawdopodobnie nie powodują wycieku pamięci) i mają prosty cykl życia.

Usługa

Service` jest drugi. „Usługa” jest właściwie lepszym wyborem dla mojego problemu, ponieważ do tego właśnie służą usługi:
Usługa to składnik aplikacji, który może wykonywać długotrwałe operacje w programie
tło i nie zapewnia interfejsu użytkownika.
Usługi są schludne, ponieważ mają bardziej zdefiniowany cykl życia, który jest łatwiejszy do kontrolowania. Ponadto, w razie potrzeby, usługi mogą działać poza aplikacją (tj. Podczas rozruchu). Może to być konieczne w przypadku niektórych aplikacji lub po prostu fajnej funkcji.

Nie był to pełny opis, ale zostawiłem linki do dokumentów dla tych, którzy chcą dokładniej zbadać sprawę. Ogólnie rzecz biorąc, Servicejest to lepsze dla instancji, której potrzebowałem - uruchamiając ServerSocket na moim urządzeniu SPP.

ahodder
źródło
0

Natknąłem się na to pytanie, kiedy szukałem sposobu na przekazanie obiektu Date. W moim przypadku, jak sugerowano w odpowiedziach, użyłem Bundle.putSerializable (), ale to nie zadziałałoby w przypadku tak złożonej rzeczy, jak opisany DataManager w oryginalnym poście.

Moja sugestia, która da bardzo podobny rezultat do umieszczenia wspomnianego DataManagera w Aplikacji lub uczynienia go Singletonem, to użycie Dependency Injection i powiązanie DataManagera z zakresem Singleton i wstrzyknięcie DataManagera tam, gdzie jest to potrzebne. Zyskasz nie tylko korzyść w postaci zwiększonej testowalności, ale także uzyskasz czystszy kod bez całego kotłowego kodu „przekazywania zależności między klasami i działaniami”. (Robo) Guice jest bardzo łatwy w obsłudze, a nowy framework Dagger również wygląda obiecująco.

britzl
źródło
1
Cóż, w przypadku czegoś takiego jak data możesz po prostu przekazać wartość długą. Ale reszta brzmi dobrze. Dzięki.
ahodder
0

inny prosty sposób na przekazanie obiektu za pomocą pakietu:

  • w obiekcie klasy utwórz listę statyczną lub inną strukturę danych z kluczem
  • podczas tworzenia obiektu umieść go w strukturze listy / danych z kluczem (np. długim znacznikiem czasu utworzenia obiektu)
  • utwórz metodę static getObject (długi klucz), aby pobrać obiekt z listy
  • w pakiecie przekaż klucz, aby można było pobrać obiekt później z innego punktu w kodzie
shaolin
źródło