Comment envoyer des objets via un bundle

119

Je dois transmettre une référence à la classe qui effectue la majorité de mon traitement via un bundle.

Le problème est qu'il n'a rien à voir avec les intentions ou les contextes et qu'il contient une grande quantité d'objets non primitifs. Comment emballer la classe dans un colis / sérialisable et le passer à un startActivityForResult?

ahodder
la source
2
"J'ai besoin de passer une référence à la classe qui effectue la majorité de mon traitement via un bundle" - pourquoi?
CommonsWare
1
J'ai un objet (DataManager), il gère un serveur et exécute quelques backends pour certaines interfaces graphiques. Chaque fois qu'une nouvelle connexion est établie, je veux que l'utilisateur puisse démarrer une nouvelle activité qui répertorie dans ListView toutes les connexions actives et que l'utilisateur en choisisse une. Les données résultantes seront alors liées à une nouvelle interface graphique. Ce n'est vraiment qu'un choix de peau pour le back-end.
ahodder
3
Si vous avez affaire à la même instance d'un objet sur plusieurs activités, vous pouvez envisager le modèle singleton . Il y a un bon tutoriel ici .
sotrh

Réponses:

55

Pour déterminer la voie à suivre, il faut répondre non seulement à la question clé de CommonsWare: «pourquoi», mais également à la question «vers quoi?». le passez-vous.

La réalité est que la seule chose qui peut passer par des bundles est des données simples - tout le reste est basé sur des interprétations de ce que ces données signifient ou vers lesquelles elles indiquent. Vous ne pouvez pas littéralement passer un objet, mais ce que vous pouvez faire est l'une des trois choses suivantes:

1) Vous pouvez décomposer l'objet en ses données de constitution, et si ce qui se trouve à l'autre extrémité a connaissance du même type d'objet, il peut assembler un clone à partir des données sérialisées. C'est ainsi que la plupart des types courants passent par des bundles.

2) Vous pouvez passer une poignée opaque. Si vous le transmettez dans le même contexte (bien que l'on puisse se demander pourquoi cela vous dérange), ce sera un handle que vous pouvez invoquer ou déréférencer. Mais si vous le transmettez via Binder à un contexte différent, sa valeur littérale sera un nombre arbitraire (en fait, ces nombres arbitraires comptent séquentiellement à partir du démarrage). Vous ne pouvez rien faire d'autre que le suivre, jusqu'à ce que vous le renvoyiez au contexte d'origine, ce qui obligera Binder à le transformer en poignée d'origine, le rendant à nouveau utile.

3) Vous pouvez passer un handle magique, tel qu'un descripteur de fichier ou une référence à certains objets OS / Platform, et si vous définissez les bons indicateurs, Binder créera un clone pointant vers la même ressource pour le destinataire, qui peut en fait être utilisé sur l'autre extrémité. Mais cela ne fonctionne que pour quelques types d'objets.

Très probablement, vous transmettez votre classe juste pour que l'autre extrémité puisse la suivre et vous la restituer plus tard, ou vous la transmettez à un contexte où un clone peut être créé à partir de données constitutives sérialisées ... ou bien vous essayez de faire quelque chose qui ne fonctionnera tout simplement pas et vous devez repenser l'approche dans son ensemble.

Chris Stratton
la source
1
Merci pour la réponse de la tournée. Votre droit, il ne me reste plus qu'à passer une référence d'une liste d'objets à ma nouvelle activité. La nouvelle activité prendra certaines données de la liste et affichera un ListView sélectionnable. onSelect, l'activité renverra un résultat (certaines données relatives à l'objet clic) à l'activité hôte. Si je comprends bien, je pense que votre option 2 gère cela de la manière la plus appropriée; comment obtenir cette poignée opaque?
ahodder
Votre autre activité ne peut extraire aucune donnée d'un objet opaque à afficher. Ce que vous souhaitez probablement faire est de créer et de transmettre des objets de substitution d'un type pris en charge qui contiennent des copies des informations qui seraient affichées.
Chris Stratton
158

Vous pouvez également utiliser Gson pour convertir un objet en JSONObject et le transmettre à un bundle. Pour moi, c'était la manière la plus élégante que j'ai trouvée de faire cela. Je n'ai pas testé comment cela affecte les performances.

En activité initiale

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

Dans l'activité suivante

String jsonMyObject;
Bundle extras = getIntent().getExtras();
if (extras != null) {
   jsonMyObject = extras.getString("myObject");
}
MyObject myObject = new Gson().fromJson(jsonMyObject, MyObject.class);
Laranjeiro
la source
3
Comme il s'agit de passer des éléments entre les activités, cela ne se produit pas assez souvent pour avoir un impact important sur les performances globales de l'application. Cela étant dit, je doute que cela fonctionne pour sérialiser le DataManager de la publication originale car il semble qu'il ait des connexions de socket et d'autres classes similaires.
britzl
4
Google suggère également cette solution au lieu de Serialize: consultez la dernière section "Alternatives recommandées" de cette page de doc
TechNyquist
3
juste un mot d'avertissement, j'ai suivi cette technique pendant un moment, mais il y a une limite de mémoire à ce que vous pouvez passer en tant que chaîne, alors assurez-vous que vos données ne sont pas trop volumineuses.
jiduvah
Voir blog.madadipouya.com/2015/09/21/… pour savoir comment ajouter le support Gson à votre projet.
geekQ
Solution intelligente, chapeau à vous!
Rohit Mandiwal
20

L' interface Parcelable est un bon moyen de transmettre un objet avec une intention.

Comment puis-je rendre mes objets personnalisés parcelables? est une très bonne réponse sur la façon d'utiliser Parcelable

Les documents Google officiels incluent également un exemple

Chris
la source
1
ou ils peuvent également être sérialisables.
Jeffrey Blattman
1
Mais réduit considérablement les performances 10x !! Découvrez ce benchmark: developerphil.com/parcelable-vs-serializable
saiyancoder
2
+1 @ Le commentaire de Mati, cependant le mettre en contexte 10x lorsqu'il est appliqué à un seul objet équivaut à 1 ms. Alors peut-être pas aussi mauvais que ça en a l'air.
pinoyyid
1
Se mettre d'accord. Le problème est lorsque vous traitez avec des collections, ce qui est un cas d'utilisation très courant si vous obtenez des ressources à partir d'une API Rest. Mais pour un seul objet, cela ne devrait pas être quelque chose de notoire. Quoi qu'il en soit, si tout le code standard est quelque chose qui vous gêne , vous pouvez essayer cette bibliothèque qui génère tout cela pour vous: github.com/johncarl81/parceler . Une très belle approche!
saiyancoder
Lien brisé: 404 (introuvable)
Gallal
14

Vous pouvez utiliser l' état global de l' application .

Mettre à jour:

Personnalisez et ajoutez ceci à votre AndroidManifest.xml:

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

Et puis ayez une classe dans votre projet comme ceci:

package com.example;

import android.app.Application;

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

Et parce que " Il est accessible via getApplication () depuis n'importe quelle activité ou service ", vous l'utilisez comme ceci:

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

J'espère que cela pourra aider.

Neil
la source
1
Merci pour la réponse, mais comment?
ahodder
Je crois que vous venez de sous-classer Application et que vous pouvez ensuite stocker tout ce que vous voulez. Les modifications xml dont vous avez besoin sont mentionnées dans le lien ci-dessus.
Mark Storer
9
En tant que principe de conception général, c'est une bonne idée d'éviter les globaux à moins que vous n'en ayez vraiment besoin. Dans ce cas, il existe de bonnes alternatives.
dhaag23
Ok, je pense que je comprends ce que vous dites. Développez simplement Application et ajoutez une variable contenant l'objet à passer; J'ai examiné la page de référence et je n'ai pas vu les modifications XML nécessaires.
ahodder
Je voulais aussi écrire ceci comme réponse. C'est certainement l'une des façons de le faire. Mais gardez à l'esprit que ces objets restent dans la mémoire à moins que vous ne les dé-référenciez (ou que le contexte de l'application soit détruit) et qu'ils puissent occuper de l'espace lorsque vous n'en avez pas besoin.
Igor Čordaš
12

Vous pouvez également rendre vos objets sérialisables et utiliser les méthodes getSerializable et putSerializable du Bundle .

dhaag23
la source
1
J'ai essayé cela et j'ai rapidement réalisé que ce ne serait pas pratique. Je ne pense pas que la plupart des objets stockés dans la classe passée (threads) soient sérialisables. :) Merci quand même.
ahodder
10

Solution possible:

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

Classe CustomObject:

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

Objets sous-personnalisés:

class SubCustomObject1 implements Serializable{ }

class SubCustomObject2  implements Serializable{ }
PraKhar
la source
7

Un autre moyen d'envoyer des objets via le bundle consiste à utiliser un bundle.putByteArray
exemple de code

public class DataBean implements Serializable {
private Date currentTime;

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

public Date getCurrentTime() {
    return currentTime;
 }
}

mettre Object of DataBean dans 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);
}

Conversion d'objets en tableaux d'octets

/**
 * 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();
    }

Récupérer l'objet du bundle:

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();
    }
}

Méthode pour obtenir des objets à partir de tableaux d'octets:

/**
 * 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;
    }

J'espère que cela aidera d'autres amis.

Rupesh Yadav
la source
cela semble lisse et facile à regarder le code. Mais je pense qu'il y a quelque chose de plus sur la raison pour laquelle le SDK n'offre pas quelque chose comme ça pour transmettre des objets. Pouvez-vous m'en dire plus sur cette solution?
Mario Lenci le
3
Pas besoin du tout de tout ce code! Utilisez bundle.putSerializable (objectImplementingSerializable) - cela fait en dessous de ce que vous réinstallez ici encore ...
Risadinha
3

1.Un exemple très direct et facile à utiliser, faites en sorte que l'objet à passer soit implémenté Sérialisable.

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

2. passer l'objet dans le paquet

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

3. obtenez l'objet passé du bundle en tant que sérialisable, puis convertissez-le en objet.

Object object = (Object) getArguments().getSerializable("object");
king_T
la source
0

C'est une réponse très tardive à ma propre question, mais elle continue à retenir l'attention, donc je sens que je dois y répondre. La plupart de ces réponses sont correctes et gèrent parfaitement le travail. Cependant, cela dépend des besoins de l'application. Cette réponse sera utilisée pour décrire deux solutions à ce problème.

Application

La première est l' application , car elle a été la réponse la plus parlée ici. L'application est un bon objet pour placer des entités qui ont besoin d'une référence à un contexte. Un `ServerSocket` aurait sans aucun doute besoin d'un contexte (pour les E / S de fichier ou les mises à jour simples de` ListAdapter`). Personnellement, je préfère cette voie. J'aime les applications, elles sont utiles pour la récupération de contexte (car elles peuvent être rendues statiques et ne causent probablement pas de fuite de mémoire) et ont un cycle de vie simple.

Un service

Le service est le deuxième. Un `Service` est en fait le meilleur choix pour mon problème car c'est ce que les services sont conçus pour faire:
Un service est un composant d'application qui peut effectuer des opérations de longue durée dans
l'arrière-plan et ne fournit pas d'interface utilisateur.
Les services sont soignés dans la mesure où ils ont un cycle de vie plus défini et plus facile à contrôler. De plus, si nécessaire, les services peuvent s'exécuter en externe de l'application (c'est-à-dire au démarrage). Cela peut être nécessaire pour certaines applications ou simplement pour une fonctionnalité intéressante.

Ce n'était pas une description complète de l'un ou l'autre, mais j'ai laissé des liens vers les documents pour ceux qui veulent enquêter davantage. Dans l'ensemble, Servicec'est mieux pour l'instance dont j'avais besoin - exécuter un ServerSocket sur mon périphérique SPP.

ahodder
la source
0

Je suis tombé sur cette question lorsque je cherchais un moyen de passer un objet Date. Dans mon cas, comme cela a été suggéré parmi les réponses, j'ai utilisé Bundle.putSerializable () mais cela ne fonctionnerait pas pour une chose complexe comme le DataManager décrit dans l'article original.

Ma suggestion qui donnera un résultat très similaire à celui de placer ledit DataManager dans l'application ou d'en faire un Singleton est d'utiliser Dependency Injection et de lier le DataManager à une portée Singleton et d'injecter le DataManager partout où il est nécessaire. Non seulement vous bénéficiez d'une plus grande testabilité, mais vous obtiendrez également un code plus propre sans que tout le code "passant des dépendances entre les classes et les activités" de la plaque chauffante. (Robo) Guice est très facile à utiliser et le nouveau framework Dagger semble également prometteur.

britzl
la source
1
Eh bien, avec quelque chose comme une date, vous pouvez simplement passer la valeur longue. Mais, le reste sonne bien. Merci.
ahodder
0

un autre moyen simple de passer un objet à l'aide d'un bundle:

  • dans l'objet de classe, créez une liste statique ou une autre structure de données avec une clé
  • lorsque vous créez l'objet, placez-le dans la structure liste / données avec la clé (par exemple, l'horodatage long lorsque l'objet est créé)
  • créer la méthode static getObject (clé longue) pour récupérer l'objet de la liste
  • dans le bundle, passez la clé, afin que vous puissiez obtenir l'objet ultérieurement à partir d'un autre point du code
Shaolin
la source