Sollte ich die Verwendung von Enums unter Android unbedingt vermeiden?

92

Ich habe eine Reihe verwandter Konstanten wie BundleSchlüssel zusammen in einer Schnittstelle wie der folgenden definiert:

public interface From{
    String LOGIN_SCREEN = "LoginSCreen";
    String NOTIFICATION = "Notification";
    String WIDGET = "widget";
}

Dies bietet mir eine bessere Möglichkeit, verwandte Konstanten zu gruppieren und sie durch einen statischen Import (keine Implementierungen) zu verwenden. Ich weiß, dass AndroidFramework die Konstanten auch auf die gleiche Weise verwendet wie Toast.LENTH_LONG, View.GONE.

Ich habe jedoch oft das Gefühl, dass dies Java Enumseine viel bessere und leistungsfähigere Möglichkeit darstellt, die Konstante darzustellen.

Aber gibt es ein Leistungsproblem bei der Verwendung von enumson Android?

Mit ein wenig Recherche geriet ich in Verwirrung. Aus dieser Frage ? „Vermeiden Sie Aufzählungen , wo man nur Ints Need“ von Android Performance - Tipps entfernt ist es klar , dass Googleentfernt ‚Vermeiden Sie Aufzählungen‘ von seiner Leistung Tipps, aber von ihm offizielles Training docs ist Beachten Sie Speicher - Overhead Abschnitt sagt es deutlich: „Aufzählungen benötigen oft mehr als doppelt so viel Speicher wie statische Konstanten. Sie sollten die Verwendung von Enums unter Android unbedingt vermeiden. " Gilt dies immer noch? (Sagen wir in JavaVersionen nach 1.6)

Ein weiteres Problem , dass ich beobachtet wird, ist zu senden enumsüber intentsVerwendung Bundleich sie durch Serialisierung senden (dh putSerializable(), dass ich eine teuere Operation denke , im Vergleich zu primitiver putString()Methode, obwohl enumsbietet es kostenlos).

Kann jemand bitte klären, auf welche Weise er am besten dargestellt werden kann Android? Sollte ich die Verwendung von enumson unbedingt vermeiden Android?

nvinayshetty
quelle
5
Sie sollten die verfügbaren Tools verwenden. Tatsächlich beansprucht eine Aktivität oder ein Fragment viel Speicher und CPU-Auslastung, aber das ist kein Grund, sie nicht mehr zu verwenden. Verwenden Sie ein statisches int, wenn Sie das nur benötigen, und verwenden Sie Aufzählungen, wenn Sie diese benötigen.
Patrick
11
Genau. Dies riecht nach vorzeitiger Optimierung. Verwenden Sie sie dort, wo es sinnvoll ist, es sei denn, Sie haben ein Leistungs- und / oder Speicherproblem und können durch Profilerstellung nachweisen, dass Aufzählungen die Ursache sind.
GreyBeardedGeek
2
Früher glaubte man, dass Aufzählungen eine nicht trivalente Leistungseinbuße verursachten, aber neuere Benchmarks zeigen keinen Nutzen für die Verwendung von Konstanten. Siehe stackoverflow.com/questions/24491160/… sowie stackoverflow.com/questions/5143256/…
Benjamin Sergent
1
Um die Leistungseinbußen beim Serialisieren einer Enum in einem Bundle zu vermeiden, können Sie sie Enum.ordinal()stattdessen als int übergeben .
BladeCoder
1
Schließlich gibt es einige Erklärungen zu Leistungsproblemen auf Enum hier youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Antworten:

115

Verwenden enumSie diese Option, wenn Sie ihre Funktionen benötigen. Vermeiden Sie es nicht streng .

Java-Enum ist leistungsfähiger, aber wenn Sie seine Funktionen nicht benötigen, verwenden Sie Konstanten, sie nehmen weniger Platz ein und können selbst primitiv sein.

Wann man enum benutzt:

  • Typprüfung - Sie können nur aufgelistete Werte akzeptieren , die nicht fortlaufend sind (siehe unten, was ich hier als fortlaufend bezeichne ).
  • Methodenüberladung - Jede Enum-Konstante hat ihre eigene Implementierung einer Methode

    public enum UnitConverter{
        METERS{
            @Override
            public double toMiles(final double meters){
                return meters * 0.00062137D;
            }
    
            @Override
            public double toMeters(final double meters){
                return meters;
            }
        },
        MILES{
            @Override
            public double toMiles(final double miles){
                return miles;
            }
    
            @Override
            public double toMeters(final double miles){
                return miles / 0.00062137D;
            }
        };
    
        public abstract double toMiles(double unit);
        public abstract double toMeters(double unit);
    }
  • mehr Daten - Ihre eine Konstante enthält mehr als eine Information, die nicht in eine Variable eingefügt werden kann

  • komplizierte Daten - Sie benötigen ständig Methoden, um die Daten zu verarbeiten

Wann nicht enum verwenden:

  • Sie können alle Werte eines Typs akzeptieren, und Ihre Konstanten enthalten nur die am häufigsten verwendeten
  • Sie können fortlaufende Daten akzeptieren

    public class Month{
        public static final int JANUARY = 1;
        public static final int FEBRUARY = 2;
        public static final int MARCH = 3;
        ...
    
        public static String getName(final int month){
            if(month <= 0 || month > 12){
                throw new IllegalArgumentException("Invalid month number: " + month);
            }
    
            ...
        }
    }
  • für Namen (wie in Ihrem Beispiel)
  • für alles andere, was wirklich keine Aufzählung braucht

Aufzählungen nehmen mehr Platz ein

  • Eine einzelne Referenz auf eine Enum-Konstante belegt 4 Bytes
  • Jede Enum-Konstante belegt Platz, der eine Summe der Feldgrößen ist, die auf 8 Bytes + Overhead des Objekts ausgerichtet sind
  • Die Enum-Klasse selbst nimmt etwas Platz ein

Konstanten nehmen weniger Platz ein

  • Eine Konstante hat keine Referenz, es handelt sich also um reine Daten (selbst wenn es sich um eine Referenz handelt, wäre die Enum-Instanz eine Referenz auf eine andere Referenz).
  • Konstanten können zu einer vorhandenen Klasse hinzugefügt werden - es ist nicht erforderlich, eine weitere Klasse hinzuzufügen
  • Konstanten können inline sein; Es bietet erweiterte Funktionen zur Kompilierungszeit (z. B. Nullprüfung, Auffinden von totem Code usw.).
Kamil Jarosz
quelle
2
Mit der Annotation @IntDef können Sie die Typprüfung für int-Konstanten simulieren. Es ist ein Argument weniger für Java Enums.
BladeCoder
3
@ BladeCoder nein, Sie brauchen keinen Verweis auf Konstante
Kamil Jarosz
1
Es ist auch gut zu beachten, dass die Verwendung von Aufzählungen die Verwendung von Reflexion fördert. Und es ist bekannt, dass die Verwendung von Reflektion ein RIESIGER Leistungseinbruch für Android ist. Siehe hier: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark
3
@ w3bshark Wie fördern Aufzählungen die Verwendung von Reflexion?
Kevin Krumwiede
3
Eine andere Sache zu beachten ist, dass ein Heap-Dump aus dem Standard leer "Hallo, Welt!" Das Projekt umfasst über 4.000 Klassen und 700.000 Objekte. Selbst wenn Sie eine lächerlich große Aufzählung mit Tausenden von Konstanten hätten, können Sie sicher sein, dass der Leistungseffekt neben dem Aufblähen des Android-Frameworks selbst vernachlässigbar ist.
Kevin Krumwiede
57

Wenn die Aufzählungen nur Werte haben, sollten Sie versuchen, IntDef / StringDef zu verwenden, wie hier gezeigt:

https://developer.android.com/studio/write/annotations.html#enum-annotations

Beispiel: statt:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

Sie nutzen:

@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

und in der Funktion, die es als Parameter / Rückgabewert hat, verwenden Sie:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

Wenn die Aufzählung komplex ist, verwenden Sie eine Aufzählung. Ist doch nicht so schlimm.

Um Aufzählungen mit konstanten Werten zu vergleichen, sollten Sie hier lesen:

http://hsc.com/Blog/Best-Practices-For-Memory-Optimization-on-Android-1

Ihr Beispiel ist eine Aufzählung mit 2 Werten. Es dauert 1112 Bytes in der Dex-Datei im Vergleich zu 128 Bytes, wenn konstante Ganzzahlen verwendet werden. Sinnvoll, da Aufzählungen echte Klassen sind, im Gegensatz zu C / C ++.

Android-Entwickler
quelle
Obwohl ich die anderen Antworten mit Vor- und Nachteilen von Aufzählungen schätze, sollte diese Antwort die akzeptierte Antwort sein, da sie speziell für Android die beste Lösung bietet. Support-Anmerkungen sind der richtige Weg. Wir als Android-Entwickler sollten denken, "wenn ich keinen starken Grund habe, Aufzählungen zu verwenden, werde ich Konstanten mit Anmerkungen verwenden" und nicht die Denkweise der anderen Antworten ", es sei denn, ich mache mir später Sorgen um Speicher / Leistung. Ich kann Aufzählungen verwenden ". Verhindern Sie jetzt, dass Sie später Leistungsprobleme haben!
w3bshark
3
@ w3bshark Wenn Sie Leistungsprobleme haben, ist es unwahrscheinlich, dass Aufzählungen das erste sind, an das Sie denken sollten, um sie zu lösen. Anmerkungen haben ihre eigenen Kompromisse: Sie haben keine Compiler-Unterstützung, sie können keine eigenen Felder und Methoden haben, sie sind mühsam zu schreiben, Sie können leicht vergessen, ein Int mit Anmerkungen zu versehen, und so weiter. Sie sind insgesamt keine bessere Lösung, sondern nur dazu da, bei Bedarf Heap-Speicherplatz zu sparen.
Malcolm
1
@androiddeveloper Sie können keinen Fehler machen, indem Sie eine Konstante übergeben, die nicht in der Enum-Deklaration definiert ist. Dieser Code wird niemals kompiliert. Wenn Sie eine falsche Konstante mit einer IntDefAnmerkung übergeben, wird dies der Fall sein. Was die Uhren betrifft, denke ich, dass es auch ziemlich klar ist. Welches Gerät hat mehr CPU-Leistung, RAM und Akku: ein Telefon oder eine Uhr? Und welche benötigen Software, um die Leistung zu optimieren?
Malcolm
3
@androiddeveloper OK, wenn Sie auf strenger Terminologie bestehen, habe ich mit Fehlern Fehler gemeint, die keine Fehler zur Kompilierungszeit sind (Laufzeit- oder Programmlogikfehler). Trollst du mich oder bekommst du wirklich keinen Unterschied zwischen ihnen und den Vorteilen von Fehlern bei der Kompilierung? Dies ist ein gut diskutiertes Thema. Schlagen Sie dies im Internet nach. In Bezug auf Uhren enthält Android zwar mehr Geräte, aber die am stärksten eingeschränkten sind der kleinste gemeinsame Nenner.
Malcolm
2
@androiddeveloper Ich habe bereits auf diese beiden Aussagen geantwortet. Wenn ich sie noch einmal wiederhole, wird das, was ich gesagt habe, nicht ungültig.
Malcolm
12

Zusätzlich zu früheren Antworten möchte ich hinzufügen, dass, wenn Sie Proguard verwenden (und dies auf jeden Fall tun sollten, um die Größe zu verringern und Ihren Code zu verschleiern), Ihr Code Enumsautomatisch konvertiert wird@IntDef wo immer dies möglich ist:

https://www.guardsquare.com/de/proguard/manual/optimizations

Klasse / Unboxing / Aufzählung

Vereinfacht Aufzählungstypen nach Möglichkeit zu ganzzahligen Konstanten.

Wenn Sie also einige diskrete Werte haben und eine Methode es erlauben sollte, nur diese Werte und nicht andere des gleichen Typs zu verwenden, würde ich verwenden Enum , da Proguard diese manuelle Arbeit zur Optimierung des Codes für mich erledigt.

Und hier ist ein guter Beitrag über die Verwendung von Enums von Jake Wharton.

Als Bibliotheksentwickler erkenne ich diese kleinen Optimierungen, die vorgenommen werden sollten, da wir die Größe, den Speicher und die Leistung der konsumierenden App so wenig wie möglich beeinflussen möchten. Es ist jedoch wichtig zu wissen, dass es völlig in Ordnung ist, [...] gegebenenfalls eine Aufzählung in Ihre öffentliche API oder ganzzahlige Werte einzufügen. Es ist wichtig, den Unterschied zu kennen, um fundierte Entscheidungen zu treffen

Gaket
quelle
11

Sollte ich die Verwendung von Enums unter Android unbedingt vermeiden?

Nein. " Streng " bedeutet, dass sie so schlecht sind, dass sie überhaupt nicht verwendet werden sollten. Möglicherweise können Leistungsprobleme in einer extremen Situation auftreten, wie z. B. vielen, vielen (Tausenden oder Millionen von) Operationen mit Aufzählungen (fortlaufend im UI-Thread). Weitaus häufiger sind die Netzwerk-E / A-Vorgänge, die unbedingt in einem Hintergrund-Thread ausgeführt werden sollten. Die häufigste Verwendung von Aufzählungen ist wahrscheinlich eine Art Typprüfung - ob es sich bei einem Objekt um dieses oder jenes handelt , das so schnell ist, dass Sie keinen Unterschied zwischen einem einzelnen Vergleich von Aufzählungen und einem Vergleich von Ganzzahlen feststellen können.

Kann jemand bitte klären, welcher Weg der beste ist, um dasselbe in Android darzustellen?

Hierfür gibt es keine allgemeine Faustregel. Verwenden Sie alles, was für Sie funktioniert, und helfen Sie Ihnen, Ihre App fertig zu machen. Später optimieren - nachdem Sie festgestellt haben, dass es einen Engpass gibt, der einige Aspekte Ihrer App verlangsamt.

stan0
quelle
1
Warum sollten Sie später optimieren, wenn es genauso einfach ist, Konstanten mit Support-Annotationen zu verwenden und jetzt zu optimieren? Siehe die Antwort von @ android_developer hier.
w3bshark
4

Zwei Fakten.

1, Enum ist eine der mächtigsten Funktionen in JAVA.

2, Android-Handy hat in der Regel viel Speicher.

Meine Antwort lautet also NEIN. Ich werde Enum in Android verwenden.

Kai Wang
quelle
2
Wenn Android über viel Speicher verfügt, bedeutet dies nicht, dass Ihre App alles verbrauchen und keine für andere hinterlassen sollte. Sie sollten die Best Practices befolgen, um zu verhindern, dass Ihre App vom Android-Betriebssystem getötet wird. Ich sage nicht, verwenden Sie keine Aufzählungen, sondern nur, wenn Sie keine Alternative haben.
Varundroid
1
Ich stimme Ihnen zu, dass wir die Best Practice befolgen sollten. Die Best Practice bedeutet jedoch nicht immer, dass Sie am wenigsten Speicher verwenden. Es ist weitaus wichtiger, den Code sauber und leicht verständlich zu halten, als mehrere k Speicher zu sparen. Sie müssen ein Gleichgewicht finden.
Kai Wang
1
Ich stimme Ihnen zu, dass wir ein Gleichgewicht finden müssen, aber die Verwendung von TypeDef lässt unseren Code nicht schlecht oder weniger wartbar aussehen. Ich benutze es seit mehr als einem Jahr und hatte nie das Gefühl, dass mein Code nicht leicht zu verstehen ist. Außerdem hat sich keiner meiner Kollegen jemals darüber beschwert, dass TypeDefs im Vergleich zu Aufzählungen schwer zu verstehen sind. Mein Rat ist, Aufzählungen nur zu verwenden, wenn Sie keine andere Option haben, aber vermeiden Sie es, wenn Sie können.
Varundroid
2

Ich möchte hinzufügen, dass Sie @Annotations nicht verwenden können, wenn Sie eine Liste <> oder Map <> deklarieren, bei der entweder der Schlüssel oder der Wert einer Ihrer Anmerkungsschnittstellen ist. Sie erhalten die Fehlermeldung "Anmerkungen sind hier nicht erlaubt".

enum Values { One, Two, Three }
Map<String, Values> myMap;    // This works

// ... but ...
public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;

@Retention(RetentionPolicy.SOURCE)
@IntDef({ONE, TWO, THREE})
public @interface Values {}

Map<String, @Values Integer> myMap;    // *** ERROR ***

Wenn Sie es also in eine Liste / Map packen müssen, verwenden Sie enum, da diese hinzugefügt werden können, @annotated int / string groups jedoch nicht.

Grisgramm
quelle