Aufzählung oder Objekt durch eine Absicht führen (die beste Lösung)

221

Ich habe eine Aktivität, die beim Start Zugriff auf zwei verschiedene ArrayLists benötigt. Beide Listen sind unterschiedliche Objekte, die ich selbst erstellt habe.

Grundsätzlich brauche ich eine Möglichkeit, diese Objekte aus einer Absicht an die Aktivität zu übergeben. Ich kann addExtras () verwenden, dies erfordert jedoch eine Parceable-kompatible Klasse. Ich könnte meine Klassen serialisierbar machen, aber so wie ich es verstehe, verlangsamt dies das Programm.

Was sind meine Optionen?

Kann ich eine Aufzählung bestehen?

Nebenbei: Gibt es eine Möglichkeit, Parameter aus einer Absicht an einen Aktivitätskonstruktor zu übergeben?

jax
quelle
Vielleicht fehlt mir etwas, aber wie hängt eine Aufzählung mit einer ArrayList zusammen?
Martin Konecny

Antworten:

558

Dies ist eine alte Frage, aber jeder erwähnt nicht, dass Enums tatsächlich sind Serializableund daher perfekt zu einer Absicht als Extra hinzugefügt werden können. So was:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

intent.putExtra("AwesomeEnum", AwesomeEnum.SOMETHING);

AwesomeEnum result = (AwesomeEnum) intent.getSerializableExtra("AwesomeEnum");

Der Vorschlag, statische oder anwendungsweite Variablen zu verwenden, ist eine wirklich schlechte Idee. Dies koppelt Ihre Aktivitäten wirklich an ein Statusverwaltungssystem, und es ist schwierig, sie zu warten, zu debuggen und an Probleme zu binden.


ALTERNATIVEN:

Tedzyc hat einen guten Punkt in Bezug auf die Tatsache festgestellt , dass die von Oderik bereitgestellte Lösung einen Fehler liefert. Die angebotene Alternative ist jedoch etwas umständlich (auch bei Verwendung von Generika).

Wenn Sie sich wirklich Sorgen über die Leistung des Hinzufügens der Aufzählung zu einer Absicht machen, schlage ich stattdessen diese Alternativen vor:

OPTION 1:

public enum AwesomeEnum {
  SOMETHING, OTHER;
  private static final String name = AwesomeEnum.class.getName();
  public void attachTo(Intent intent) {
    intent.putExtra(name, ordinal());
  }
  public static AwesomeEnum detachFrom(Intent intent) {
    if(!intent.hasExtra(name)) throw new IllegalStateException();
    return values()[intent.getIntExtra(name, -1)];
  }
}

Verwendung:

// Sender usage
AwesomeEnum.SOMETHING.attachTo(intent);
// Receiver usage
AwesomeEnum result = AwesomeEnum.detachFrom(intent);

OPTION 2: (generisch, wiederverwendbar und von der Aufzählung entkoppelt)

public final class EnumUtil {
    public static class Serializer<T extends Enum<T>> extends Deserializer<T> {
        private T victim;
        @SuppressWarnings("unchecked") 
        public Serializer(T victim) {
            super((Class<T>) victim.getClass());
            this.victim = victim;
        }
        public void to(Intent intent) {
            intent.putExtra(name, victim.ordinal());
        }
    }
    public static class Deserializer<T extends Enum<T>> {
        protected Class<T> victimType;
        protected String name;
        public Deserializer(Class<T> victimType) {
            this.victimType = victimType;
            this.name = victimType.getName();
        }
        public T from(Intent intent) {
            if (!intent.hasExtra(name)) throw new IllegalStateException();
            return victimType.getEnumConstants()[intent.getIntExtra(name, -1)];
        }
    }
    public static <T extends Enum<T>> Deserializer<T> deserialize(Class<T> victim) {
        return new Deserializer<T>(victim);
    }
    public static <T extends Enum<T>> Serializer<T> serialize(T victim) {
        return new Serializer<T>(victim);
    }
}

Verwendung:

// Sender usage
EnumUtil.serialize(AwesomeEnum.Something).to(intent);
// Receiver usage
AwesomeEnum result = 
EnumUtil.deserialize(AwesomeEnum.class).from(intent);

OPTION 3 (mit Kotlin):

Es ist schon eine Weile her, aber da wir jetzt Kotlin haben, dachte ich, ich würde eine weitere Option für das neue Paradigma hinzufügen. Hier können wir Erweiterungsfunktionen und reifizierte Typen verwenden (die den Typ beim Kompilieren beibehalten).

inline fun <reified T : Enum<T>> Intent.putExtra(victim: T): Intent =
    putExtra(T::class.java.name, victim.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(): T? =
    getIntExtra(T::class.java.name, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

Es gibt einige Vorteile, wenn Sie dies so tun.

  • Wir benötigen nicht den "Overhead" eines Zwischenobjekts, um die Serialisierung durchzuführen, da alles an Ort und Stelle erfolgt, inlinewodurch die Aufrufe durch den Code innerhalb der Funktion ersetzt werden.
  • Die Funktionen sind vertrauter, da sie den SDK-Funktionen ähnlich sind.
  • Die IDE vervollständigt diese Funktionen automatisch, sodass keine Vorkenntnisse der Utility-Klasse erforderlich sind.

Einer der Nachteile ist, dass, wenn wir die Reihenfolge der Emums ändern, eine alte Referenz nicht funktioniert. Dies kann ein Problem mit Dingen wie Absichten in ausstehenden Absichten sein, da diese Aktualisierungen überleben können. Für den Rest der Zeit sollte es jedoch in Ordnung sein.

Es ist wichtig zu beachten, dass andere Lösungen, wie die Verwendung des Namens anstelle der Position, ebenfalls fehlschlagen, wenn wir einen der Werte umbenennen. In diesen Fällen erhalten wir jedoch eine Ausnahme anstelle des falschen Enum-Werts.

Verwendung:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()
Pablisco
quelle
14
+1 für den Hinweis, dass es eine "wirklich schlechte Idee" ist, sie anwendungsweit zu machen.
Bugfixr
3
Ich habe tatsächlich an einem Projekt gearbeitet, bei dem ich mich einfach nicht mit Serialisierung oder Bündelung von Objekten befassen wollte (viele Objekte mit vielen Variablen), und die Verwendung statischer globaler Variablen war in Ordnung ... bis ein Teamkollege auf die Website kam Projekt. Die Kosten für den Versuch, die Verwendung dieser Globals zu koordinieren, haben mich dazu gebracht, "es zu vermasseln. Ich schreibe einen Codegenerator, um mir ein paar Pakete zu machen". Die Anzahl der Fehler ging deutlich zurück
Joe Plante
2
@Coeffect Ja, es ist ein verständlicher Vorschlag, aber in den meisten Fällen kann dies als vorzeitige Optimierung qualifiziert werden, es sei denn, Sie analysieren Tausende von Aufzählungen (die von Natur aus nur wenige sein sollten, da sie für den Status verwendet werden). Auf einem Nexus 4 Sie erhalten 1ms Verbesserung ( developerphil.com/parcelable-vs-serializable ) nicht sicher, ob es die zusätzliche Beinarbeit wert ist, aber andererseits haben Sie die anderen Alternativen, die ich vorgeschlagen habe;)
Pablisco
1
@rgv Unter der Haube kompiliert Kotlin Cross enum classTypen zu einem einfachen Java enum. Ich denke, die einfachere Problemumgehung besteht darin, das enum classGerät zu erstellen Serializable: enum class AwesomeEnum : Serializable { A, B, C }Nicht ideal, sollte aber funktionieren.
Pablisco
1
@Pierre wie bei jeder guten Antwort gibt es ein "es kommt darauf an". Serialisable muss nicht erweitert werden. Die erste Antwort ist gültig. Diese zusätzlichen Optionen sind für den Fall vorgesehen, dass Sie einen Fall haben, in dem möglicherweise ein Flaschenhals vorhanden ist, z. B. wenn Sie Millionen von Datensätzen deserialisieren (hoffentlich nicht). Verwenden Sie, was Sie für richtig halten ...
Pablisco
114

Sie können Ihre Aufzählung Parcelable implementieren lassen, was für Aufzählungen recht einfach ist:

public enum MyEnum implements Parcelable {
    VALUE;


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(final Parcel dest, final int flags) {
        dest.writeInt(ordinal());
    }

    public static final Creator<MyEnum> CREATOR = new Creator<MyEnum>() {
        @Override
        public MyEnum createFromParcel(final Parcel source) {
            return MyEnum.values()[source.readInt()];
        }

        @Override
        public MyEnum[] newArray(final int size) {
            return new MyEnum[size];
        }
    };
}

Sie können dann Intent.putExtra (String, Parcelable) verwenden.

UPDATE: Bitte beachten Sie den Kommentar von Wreckgar, enum.values()der bei jedem Aufruf ein neues Array zuweist .

UPDATE: Android Studio bietet eine Live-Vorlage ParcelableEnum, die diese Lösung implementiert. (Verwenden Sie unter Windows Ctrl+ J)

Oderik
quelle
3
Es ist auch möglich, die Methoden toString () und valueOf () von enum anstelle von Ordnungszahlen zu verwenden.
Natix
2
Die Verwendung von ordinal () kann unterbrochen werden, wenn Entwickler ein neues Enum-Mitglied einfügen. Wenn Sie das Enum-Mitglied umbenennen, wird natürlich auch name () beschädigt. Es ist jedoch wahrscheinlicher, dass Entwickler neue Mitglieder einfügen, anstatt sie umzubenennen, da er beim Umbenennen das gesamte Projekt neu faktorisieren muss.
Cheok Yan Cheng
2
Ich bin nicht damit einverstanden, dass zusätzliche (oder neu geordnete) Aufzählungswerte wahrscheinlicher sind als umbenannte. Die Verwendung einer ausgeklügelten IDE wie IntelliJ IDEA Refactoring ist keine große Sache. Ihr Standpunkt ist jedoch immer noch gut: Sie müssen sicherstellen, dass die Serialisierung bei jeder koexistierenden Implementierung konsistent ist. Das gilt für jede Art von Serialisierung. Ich gehe davon aus, dass in den meisten Fällen Pakete innerhalb einer App weitergegeben werden, in der nur eine Implementierung vorhanden ist, sodass dies kein Problem darstellen sollte.
Oderik
2
values ​​() erstellt bei jedem Aufruf ein neues Array. Am besten zwischenspeichern Sie es z. ein privates statisches Array
wragar23
2
Die Tatsache, dass ordinal () in allgemeinen Fällen (z. B. Datenbankspeicher) nicht sicher ist, ist für Android-Pakete nicht relevant. Pakete sind nicht für die langfristige (dauerhafte) Lagerung vorgesehen. Sie sterben mit der App. Wenn Sie also eine Aufzählung hinzufügen / umbenennen, erhalten Sie neue Pakete.
Noamtm
24

Sie können eine Aufzählung als Zeichenfolge übergeben.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Wenn Zeichenfolgen unterstützt werden, sollten Sie in der Lage sein, den Wert der Aufzählung problemlos weiterzugeben.

Cameron Lowell Palmer
quelle
3
Die einfachste Implementierung von allen.
Avi Cohen
22

Um eine Aufzählung absichtlich zu übergeben, können Sie die Aufzählung in eine Ganzzahl umwandeln.

Ex:

public enum Num{A ,B}

Senden (Aufzählung zu Ganzzahl):

Num send = Num.A;
intent.putExtra("TEST", send.ordinal());

Empfangen (Ganzzahl zu Aufzählung):

Num rev;
int temp = intent.getIntExtra("TEST", -1);
if(temp >= 0 && temp < Num.values().length)
    rev = Num.values()[temp];

Freundliche Grüße. :) :)

SCHILDKRÖTE
quelle
8
oder Sie können es als Zeichenfolge (damit es lesbar ist) mit Num.A.name () senden und es dann mit Num.ValueOf (intent.getStringExtra ("TEST"))
Benoit Jadinon
1
Ich denke, Benoits Weg ist sicherer, da temp.ordinal () in der Praxis nicht bevorzugt wird, da sich der ordinale () Wert ändern kann. Siehe diesen Beitrag: stackoverflow.com/questions/2836256/…
Shnkc
15

Wenn Sie es wirklich brauchen, können Sie eine Aufzählung wie folgt mit name()und valueOf(String)wie folgt serialisieren :

 class Example implements Parcelable { 
   public enum Foo { BAR, BAZ }

   public Foo fooValue;

   public void writeToParcel(Parcel dest, int flags) {
      parcel.writeString(fooValue == null ? null : fooValue.name());
   }

   public static final Creator<Example> CREATOR = new Creator<Example>() {
     public Example createFromParcel(Parcel source) {        
       Example e = new Example();
       String s = source.readString(); 
       if (s != null) e.fooValue = Foo.valueOf(s);
       return e;
     }
   }
 }

Dies funktioniert offensichtlich nicht, wenn Ihre Aufzählungen einen veränderlichen Zustand haben (was sie eigentlich nicht sollten).

Jan Berkel
quelle
4

Möglicherweise können Sie Ihr Enum-Implementierungsprogramm serialisierbar machen, dann können Sie es über den Intent übergeben, da es eine Methode gibt, um es als serialisierbar zu übergeben. Der Rat, int anstelle von enum zu verwenden, ist falsch. Aufzählungen werden verwendet, um Ihren Code leichter lesbar und leichter zu pflegen. Es wäre ein großer Schritt zurück in die dunklen Zeiten, Enums nicht verwenden zu können.

Damien Cooke
quelle
2
Jeder Enum-Typ erweitert standardmäßig die Enum-Superklasse, die bereits Serializable implementiert.
Arcao
2

über Oderiks Beitrag:

Sie können Ihre Aufzählung Parcelable implementieren lassen, was für Aufzählungen recht einfach ist:

public enum MyEnum implementiert Parcelable {...} Sie können dann Intent.putExtra (String, Parcelable) verwenden.

Wenn Sie eine MyEnum-Variable myEnum definieren und dann intent.putExtra ("Parcelable1", myEnum) ausführen, wird die Fehlermeldung "Die Methode putExtra (String, Parcelable) ist für den Typ Intent nicht eindeutig" angezeigt. Da es auch eine Intent.putExtra-Methode (String, Parcelable) gibt und der ursprüngliche 'Enum'-Typ selbst die Serializable-Schnittstelle implementiert, weiß der Compiler nicht, welche Methode (intent.putExtra (String, Parcelable / oder Serializable)) ausgewählt wurde.

Schlagen Sie vor, die Parcelable-Schnittstelle aus MyEnum zu entfernen und den Kerncode wie folgt in die Parcelable-Implementierung der Wrap-Klasse zu verschieben (Father2 ist ein Parcelable und enthält ein Aufzählungsfeld):

public class Father2 implements Parcelable {

AnotherEnum mAnotherEnum;
int mField;

public Father2(AnotherEnum myEnum, int field) {
    mAnotherEnum = myEnum;
    mField = field;
}

private Father2(Parcel in) {
    mField = in.readInt();
    mAnotherEnum = AnotherEnum.values()[in.readInt()];
}

public static final Parcelable.Creator<Father2> CREATOR = new Parcelable.Creator<Father2>() {

    public Father2 createFromParcel(Parcel in) {
        return new Father2(in);
    }

    @Override
    public Father2[] newArray(int size) {
        return new Father2[size];
    }

};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(mField);
    dest.writeInt(mAnotherEnum.ordinal());
}

}

dann können wir tun:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));
tedzyc
quelle
5
Sie können die richtige Signatur explizit auswählen, indem Sie beispielsweise das Argument " intent.putExtra("myEnum", (Parcelable) enumValue);
umwandeln
Ordnungszahl zu verwenden ist perfekt!
Slott
Dies ist eine wirklich komplizierte Art zu sagen bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob
2

Sie können den Enum-Konstruktor für Enum verwenden, um den primitiven Datentyp zu haben.

public enum DaysOfWeek {
    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    private int value;
    private DaysOfWeek(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

    private static final SparseArray<DaysOfWeek> map = new SparseArray<DaysOfWeek>();

    static
    {
         for (DaysOfWeek daysOfWeek : DaysOfWeek.values())
              map.put(daysOfWeek.value, daysOfWeek);
    }

    public static DaysOfWeek from(int value) {
        return map.get(value);
    }
}

Sie können int als Extras übergeben und dann mit seinem Wert aus enum ziehen.

niczm25
quelle
2

Die meisten Antworten, die hier das Paketkonzept verwenden, sind in Java-Code. In Kotlin ist das einfacher.

Kommentieren Sie einfach Ihre Enum-Klasse mit @Parcelize und implementieren Sie die Parcelable-Schnittstelle.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}
Ramakrishna Joshi
quelle
1

Ich mag einfach.

  • Die Fred-Aktivität hat zwei Modi - HAPPYund SAD.
  • Erstellen Sie eine Statik IntentFactory, die Ihre Intentfür Sie erstellt. Gib es weiter, was Modedu willst.
  • Der IntentFactoryverwendet den Namen desMode Klasse als Namen des Extra.
  • Das IntentFactorykonvertiert das Modein einString usingname()
  • Bei der Eingabe onCreateverwenden Sie diese Informationen, um wieder in a zu konvertierenMode .
  • Sie könnten ordinal()und Mode.values()auch verwenden. Ich mag Strings, weil ich sie im Debugger sehen kann.

    public class Fred extends Activity {
    
        public static enum Mode {
            HAPPY,
            SAD,
            ;
        }
    
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.betting);
            Intent intent = getIntent();
            Mode mode = Mode.valueOf(getIntent().getStringExtra(Mode.class.getName()));
            Toast.makeText(this, "mode="+mode.toString(), Toast.LENGTH_LONG).show();
        }
    
        public static Intent IntentFactory(Context context, Mode mode){
            Intent intent = new Intent();
            intent.setClass(context,Fred.class);
            intent.putExtra(Mode.class.getName(),mode.name());
    
            return intent;
        }
    }
Johnny Lambada
quelle
neugierig .. wer ruft die IntentFactory auf? Können Sie näher erläutern, wie eine andere Aktivität Fred anrufen würde und wie Fred sicherstellen kann, dass der Modus bestanden wird?
Erik
0

Ich denke, Ihre beste Wette ist es, diese Listen in etwas Parzellierbares wie eine Zeichenfolge (oder eine Karte?) Zu konvertieren, um sie zur Aktivität zu bringen. Dann muss die Aktivität sie wieder in ein Array konvertieren.

Das Implementieren von benutzerdefinierten Paketen ist meiner Meinung nach ein Schmerz im Nacken, daher würde ich es nach Möglichkeit vermeiden.

Brad Hein
quelle
0

Betrachten Sie folgende Aufzählung ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Zum Bestehen ::

 Intent mainIntent = new Intent(this,MyActivity.class);
 mainIntent.putExtra("ENUM_CONST", MyEnum.ValueA);
 this.startActivity(mainIntent);

So rufen Sie die Absicht / das Bündel / die Argumente ab ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");
Maulkorb
quelle
0

Wenn Sie nur eine Aufzählung senden möchten, können Sie Folgendes tun:

Deklarieren Sie zuerst eine Aufzählung mit einem Wert (der absichtlich übergeben werden kann):

 public enum MyEnum {
    ENUM_ZERO(0),
    ENUM_ONE(1),
    ENUM_TWO(2),
    ENUM_THREE(3);
    private int intValue;

    MyEnum(int intValue) {
        this.intValue = intValue;
    }

    public int getIntValue() {
        return intValue;
    }

    public static MyEnum getEnumByValue(int intValue) {
        switch (intValue) {
            case 0:
                return ENUM_ZERO;
            case 1:
                return ENUM_ONE;
            case 2:
                return ENUM_TWO;
            case 3:
                return ENUM_THREE;
            default:
                return null;
        }
    }
}

Dann:

  intent.putExtra("EnumValue", MyEnum.ENUM_THREE.getIntValue());

Und wenn Sie es bekommen wollen:

  NotificationController.MyEnum myEnum = NotificationController.MyEnum.getEnumByValue(intent.getIntExtra("EnumValue",-1);

Stück Kuchen!

MHN
quelle
0

Verwenden Sie Kotlin-Erweiterungsfunktionen

inline fun <reified T : Enum<T>> Intent.putExtra(enumVal: T, key: String? = T::class.qualifiedName): Intent =
    putExtra(key, enumVal.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(key: String? = T::class.qualifiedName): T? =
    getIntExtra(key, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

Dies gibt Ihnen die Flexibilität, mehrere gleiche Aufzählungstypen zu übergeben oder standardmäßig den Klassennamen zu verwenden.

// Add to gradle
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

// Import the extension functions
import path.to.my.kotlin.script.putExtra
import path.to.my.kotlin.script.getEnumExtra

// To Send
intent.putExtra(MyEnumClass.VALUE)

// To Receive
val result = intent.getEnumExtra<MyEnumClass>()
Gibolt
quelle
-2

Verwenden Sie keine Aufzählungen. Grund Nr. 78, keine Aufzählungen zu verwenden. :) Verwenden Sie Ganzzahlen, die einfach über Bundle und Parcelable entfernt werden können.

Hackbod
quelle
8
@ Hackbod - Was sind die anderen 77 Gründe? ;) Im Ernst - es scheint viele Vorteile zu geben, die aufzuzählen sind, und sie sind nicht gerade schwer zu "entfernen" - gibt es eine Chance, Ihre Gründe gegen sie zu erweitern?
Ostergaard
3
@ Hackbod Bitte näher erläutern. Wenn Enums nicht verwendet werden sollen, entfernen Sie sie aus der API.
dcow
2
Aufzählungen sind Teil der Java-Sprachspezifikation, daher sind sie etwas schwer zu entfernen und haben immer noch eine kompatible Java-Implementierung :)
tad
1
was ist mit mEnum.ordinal()? Es gibt die Position des Elements zurück
Saif Hamed
3
Der Wert von Enum.ordinal()wird zur Kompilierungszeit festgelegt. Der einzige Zeitpunkt, zu dem ein Kommentar gilt, ist die Weitergabe von Daten zwischen Apps mit unterschiedlichen Versionen der Aufzählung oder zwischen App-Updates, die die Reihenfolge der Elemente in der Aufzählung ändern. So etwas ist gefährlich, wenn Sie etwas verwenden, das nicht primitiv ist. Das Übergeben von Absichten zwischen Aktivitäten innerhalb einer einzelnen App Enum.ordinal()sollte absolut sicher sein.
Ian McLaird