Lorsque l'on souhaite passer des objets entre deux applications Java, le langage propose un mécanisme de serialisation. Les objets devant transiter implémentent l'interface java.io.Serializable. Le JRE utilise le mécanisme de reflection pour "marshaliser" et "unmarshaliser" vos objets.
Mais le mécanisme de reflection prend beaucoup de temps. Pour remédier à cette situation vous pouvez implémenter l'interface java.io.Externalizable qui étend la classe java.io.Serializable. Cette interface vous demande d'écrire deux méthodes pour gérer vous même les mécanismes pour "marshaliser" et "unmarshaliser" vos objets.
Cette problématique était surtout très pénalisante sur les premières versions des JVM mais elle reste toujours vraie dans une moindre mesure aujourd'hui (je vous laisse parcourir toute la littérature sur ce sujet sur le web). Lors de la mise en place de la VM Android Dalvik, les ingénieurs de Google ont proposé leur propre interface android.os.Parcelable qui a l'avantage de ne pas hériter de Seriaizable et de forcer les développeurs à implémenter eux mêmes leur mécanisme de sérialisation.
Si nous revenons à notre application FeuilleDeMatch, nous avons besoin de faire transiter des informations d'une activité à une autre. La persistance des données ne se faisant que lorsque l'utilisateur sort de l'application, ou lorsqu'il choisit de les enregistrer.
Comment écrire une classe Parcelable ?
Un objet Parcel dans le SDK Android représente un conteneur de message amené à être envoyé. L'objet Parcel propose plusieurs méthodes pour écrire et lire des objets de types primitifs ou d'autres objets Parcelable.Si nous revenons à notre application FeuilleDeMatch, nous avons besoin de faire transiter des informations d'une activité à une autre. La persistance des données ne se faisant que lorsque l'utilisateur sort de l'application, ou lorsqu'il choisit de les enregistrer.
Dans un match il peut y avoir plusieurs types de faits marquants. Un fait marquant est représenté par la classe MatchTypeEvent
public class MatchTypeEvent {
private Long id;
private String label;
private Integer score;
//... getter et setter
//... hashcode et equals
}
Nous allons faire implémenter l'interface Parcelable à notre classe. Cette interface demande de remplir le contrat suivant
public int describeContents();
public void writeToParcel(Parcel dest, int flags);
et de déclarer un champ static CREATOR implémentant l'interface Parcelable.Creator pour fournir les méthodes
public MatchTypeEvent createFromParcel(Parcel in);public MatchTypeEvent[] newArray(int size);
Ces méthodes vous imposent de décrire comment votre objet sera "marshaliser" et "unmarshaliser"
public class MatchTypeEvent implements Parcelable {
private Long id;
private String label;
private Integer score;
//... getter et setter
//... hashcode et equals
public MatchTypeEvent(Parcel in) {
//We read in the order we wrote in the parcel
id = in.readLong();
label = in.readString();
score = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//we write each fieds in the parcel
//the order is important when you read the information
dest.writeLong(id);
dest.writeString(label);
dest.writeInt(score);
}
public static final Parcelable.Creator<MatchTypeEvent> CREATOR = new
Parcelable.Creator<MatchTypeEvent>() {
public MatchTypeEvent createFromParcel(Parcel in) {
return new MatchTypeEvent(in);
}
public MatchTypeEvent[] newArray(int size) {
return new MatchTypeEvent[size];
}
};
}
Comment utilise t'on un objet Parcelable ?
Un objet Parcelable est destiné à être transférer d'une activity à une autre. Quand vous créez par exemple une Intent vous pouvez lui ajouter des attributs de type primitifs ou de type Parcelable
Intent i = new Intent(this, MatchTypeEventUpdater.class);
i.putExtra("matchTypeEventToUpdate", matchTypeEvent);
Une liste de Parcelable peut également être passée
Intent i = new Intent(this, MatchEventAdder.class);
i.putParcelableArrayListExtra("listeTypeEvent", (ArrayList<MatchTypeEvent>) scoreSheet.getSport().getMatchTypesEvents());
Comment déclarer une liste de Parcelable dans un objet ?
Prenons un exemple, chaque sport à un ensemble de faits de matchs pouvant se dérouler
public class Sport {
private Long id;
private String label;
}
Les writers et les readers auront cette forme
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(label);
dest.writeTypedList(matchTypesEvents);
}
public Sport createFromParcel(Parcel in) {
Sport sport = new Sport();
id = in.readLong();
label = in.readString();
in.readTypedList(matchTypesEvents, MatchTypeEvent.CREATOR);
return sport;
}
Comment déclarer un objet Parcelable dans un autre ?
Un fait de match est d'un certain type et est généralement attribué à une équipe. Dans notre première version de l'application FeuilleDeMatch, l'équipe est représentée par une enum Java pouvant prendre deux valeurs HOME ou VISITOR
public class MatchEvent {
private Long id;
private Team team;
private MatchTypeEvent matchTypeEvent;
private Integer minute;
private Integer player;
}
Les writers et les readers auront cette forme
public void writeToParcel(Parcel dest, int flags) {
//we write each fieds in the parcel
//the order is important when you read the information
dest.writeLong(id);
dest.writeParcelable(matchTypeEvent, flags);
dest.writeInt(minute);
dest.writeInt(player);
dest.writeString(team.name());
dest.writeString(comment);
}
public MatchEvent createFromParcel(Parcel in) {
MatchEvent event = new MatchEvent();
id = in.readLong();
matchTypeEvent = in.readParcelable(MatchTypeEvent.class.getClassLoader());
minute = in.readInt();
player = in.readInt();
team = Team.valueOf(in.readString());
return event ;
}
Des exemples
Pour consulter un exemple complet je vous laisse regarder le modèle objet utilisé par l'application FeuilleDeMatch https://github.com/javamind/FeuilleDeMatch/tree/master/src/com/ehret/scoresheet/domain
Article précédent / Sommaire / Article suivant
Il faudra quand même m'expliquer pourquoi on est obligé de passer des objets parcelables au sein d'une même application androïd. Dans mon cas, entre deux écrans qui partagent une même instance (en l'occurrence un moteur d'apprentissage), impossible de partager la référence entre les deux activités, il a fallu reinstancier le moteur avec une perte de performances. Google avait déjà fait le coup avec le serializable a la sauce Gwt ! Il faudrait peut être qu'il arrête un peu de prendre des libertés avec les standards java !
RépondreSupprimerUne application Android n'est pas une application classique qui multiplie les écrans. Par exemple les téléphones ou les tablettes ne sont pas faites pour faire de la saisie de masse. On souhaite rapidement trouvé de l'info, un mail, un contact, un film à aller voir... Une application Android ne dépasse rarement les 5, 6 écrans. Ceci est vrai pour une application native ainsi que pour un site web pour mobile.
RépondreSupprimerL'expérience utilisateur est une notion importante chez Google et ils ont conçu un système où les développeurs et concepteurs doivent faire des efforts pour faire des applications simples, légères en taille car la mémoire est limitée, peu consommatrice de CPU car il faut préserver la batterie des utilisateurs...
La mise en place de l'interface Parcelable va dans ce sens. Après en tant que développeur Java, je suis aussi parfois agacé par les libertés prises par Google sur les standards mais je suis aussi content quand Google partage ses librairies utilitaires (Guava) qui améliorent notre code.
Bonjour, J'ai une question que je n'arrive pas à trouver la réponse sur internet, dans mon appli je prend une photo et je clique sur suivant j'aimerai récupérer l'image prise (ImageView) dans la 2ème activité. Comment faire ?
RépondreSupprimerQuel est ton besoin savoir récupérer une image lorsque tu appelles l'Intent pour prendre une photo ? Ou savoir comment la stocker ? Personnellement quand je veux prendre une image je délègue la prise de photo à l'application Android à laquelle je passe l'emplacement à laquelle sauvegarder une image
RépondreSupprimerUri uri = Uri.fromFile(new File(context.getExternalFilesDir(Environment.DIRECTORY_DCIM), "monfichier"));
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 1);