Numarayı veya nesneyi bir niyetten geçirmek (en iyi çözüm)


221

Başladığımda iki farklı ArrayListe erişmesi gereken bir etkinliğim var. Her iki Liste de kendi oluşturduğum farklı Nesnelerdir.

Temelde bu nesneleri bir niyetten aktiviteye geçirmenin bir yoluna ihtiyacım var. AddExtras () kullanabilirsiniz, ancak bu bir Parceable uyumlu sınıf gerektirir. Sınıflarımı serileştirilebilir hale getirebilirdim ama anladığım kadarıyla programı yavaşlatır.

Seçeneklerim neler?

Bir Enum'u geçebilir miyim?

Bir kenara: parametreleri bir niyetinden bir etkinlik yapıcısına geçirmenin bir yolu var mı?


Belki bir şey eksik, ama bir arum ArrayList ile nasıl ilişkilidir?
Martin Konecny

Yanıtlar:


558

Bu eski bir sorudur, ancak herkes Enum'ların aslında Serializableve dolayısıyla bir Niyet'e ekstra olarak mükemmel bir şekilde eklenebileceğinden bahsetmez . Bunun gibi:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

Statik veya uygulama geneli değişkenlerin kullanılması önerisi gerçekten kötü bir fikirdir. Bu, faaliyetlerinizi gerçekten bir devlet yönetim sistemine bağlar ve bakımı, hata ayıklaması ve sorunla bağlanması zordur.


ALTERNATİFLERİ:

Tedzyc tarafından Oderik tarafından sağlanan çözümün size bir hata verdiği konusunda iyi bir noktaya dikkat çekildi . Bununla birlikte, sunulan alternatif kullanımı biraz hantaldır (jenerik ilaç kullanarak bile).

Eğer bir Niyet'e enum eklemenin performansından gerçekten endişeleniyorsanız bunun yerine şu alternatifleri öneriyorum:

SEÇENEK 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)];
  }
}

Kullanımı:

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

SEÇENEK 2: (jenerik, tekrar kullanılabilir ve numaralandırmadan ayrılmıştır)

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

Kullanımı:

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

SEÇENEK 3 (Kotlin ile):

Bir süre oldu, ama şimdi Kotlin'e sahibiz, yeni paradigma için başka bir seçenek ekleyeceğimi düşündüm. Burada eklenti işlevlerini ve birleştirilmiş türleri (derlerken türü koruyan) kullanabiliriz.

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] }

Bu şekilde yapmanın birkaç faydası vardır.

  • Serileştirmeyi yapmak için ara nesnenin "tepegözünü" gerektirmez, çünkü tüm inlinefonksiyonlar yerine çağrıları işlev içindeki kodla değiştirir.
  • İşlevler SDK'lara benzer olduklarından daha tanıdıktır.
  • IDE bu işlevleri otomatik olarak tamamlayacaktır, bu da yardımcı program sınıfı hakkında önceden bilgi sahibi olmanıza gerek olmadığı anlamına gelir.

Dezavantajlardan biri, Emumların sırasını değiştirirsek, eski referansların işe yaramayacağıdır. Bu, güncellemelerden kurtulabildikleri için bekleyen hedefler içindeki Hedefler gibi bir sorun olabilir. Ancak, zamanın geri kalanı için, tamam olmalıdır.

Değerlerden herhangi birini yeniden adlandırırsak, konum yerine adı kullanmak gibi diğer çözümlerin de başarısız olacağını unutmayın. Bununla birlikte, bu durumlarda, yanlış Enum değeri yerine bir istisna alırız.

Kullanımı:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()

14
Onları uygulama yapmak için "gerçek bir kötü fikir" olduğunu belirtmek için +1.
bugfixr

3
Aslında serileştirme veya nesneleri bir araya getirmek istemediğim bir proje üzerinde çalıştım (içlerinde çok fazla değişken olan çok sayıda nesne) ve statik küresel değişkenler kullanmak iyiydi ... projesi. Bu küresellerin kullanımını koordine etmeye çalışmanın maliyeti beni "vidalayın. Bana bazı Parcelables yapmak için bir kod üreteci yazıyorum". Hata sayısı önemli ölçüde düştü
Joe Plante

2
@Coeffect Evet, bu anlaşılabilir bir öneri, ancak çoğu durumda, binlerce numarayı ayrıştırmadıkça (bu, doğası gereği durumu işlemek için kullanıldıkları için sadece birkaç tane olması gerekir) erken optimizasyon olarak nitelendirilebilir. 1 ms iyileştirme alıyorsunuz ( developerphil.com/parcelable-vs-serializable ) ekstra bacak çalışmasına değdiğinden emin değilsiniz, ancak daha sonra önerdiğim diğer alternatiflere
sahipsiniz

1
@rgv Kaputun altında Kotlin cross enum classtürleri düz bir Java ile derler enum. Sanırım daha kolay bir çözüm enum classaracı yapmaktır Serializable: enum class AwesomeEnum : Serializable { A, B, C }İdeal değil, ama çalışması gerekir.
pablisco

1
@Pierre herhangi bir iyi cevapta olduğu gibi, bir "bağlıdır". Serialisable'ı genişletmeye gerek yoktur. İlk cevap geçerlidir. Bu ekstra seçenekler, milyonlarca kaydın serileştirilmesi gibi bir şişe boynunun olabileceği bir durumunuz olması durumundadır (umarım). Uygun gördüğünüzü kullanın ...
pablisco

114

Numaralandırmanızı, numaralandırmalar için oldukça kolay olan Parcelable uygulamasını uygulayabilirsiniz:

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];
        }
    };
}

Daha sonra Intent.putExtra (String, Parcelable) kullanabilirsiniz.

GÜNCELLEME: Lütfen enum.values()her aramada yeni bir dizi ayıran enkazın yorumuna dikkat edin .

GÜNCELLEME: Android Studio, ParcelableEnumbu çözümü uygulayan canlı bir şablona sahiptir. (Windows'ta Ctrl+ kullanın J)


3
Ayrıca sıralamalar yerine enum'un toString () ve valueOf () yöntemleri de mümkündür.
Natix

2
Geliştirici, yeni numaralandırma üyesi eklerken ordinal () kullanımı bozulabilir. Tabii ki, enum üyesini yeniden adlandırmak name () 'yi de kıracaktır. Ancak, yeniden adlandırmak tüm projeyi yeniden hesaba katmasını gerektirdiğinden, geliştiricilerin yeniden adlandırmak yerine yeni üye ekleme olasılığı daha yüksektir.
Cheok Yan Cheng

2
Ek (veya yeniden sıralanan) numaralandırma değerlerinin, yeniden adlandırılmış olanlardan daha olası olduğunu kabul etmiyorum. IntelliJ IDEA yeniden düzenleme gibi karmaşık bir IDE kullanmak büyük bir sorun değildir. Ancak amacınız hala iyi: serileştirmenin bir arada var olan uygulama boyunca tutarlı olduğundan emin olmalısınız. Her türlü serileştirme için bu geçerlidir. Çoğu durumda parcelables'ın tek bir uygulamanın bulunduğu bir uygulamada geçirildiğini düşünüyorum, bu yüzden bir sorun olmamalı.
Oderik

2
değerleri () her çağrıda yeni bir dizi oluşturur, bu nedenle örneğin önbelleğe almak en iyisidir. özel bir statik dizi
wreckgar23

2
Genel durumlarda (db depolama gibi), ordinal () güvenli olmadığı Android parselleri ile ilgili değildir. Parseller uzun süreli (kalıcı) depolama için değildir. Onlar uygulaması ile ölür. Bir enum eklediğinizde / yeniden adlandırdığınızda, yeni parseller alacaksınız.
noamtm

24

Bir numaralandırmayı dize olarak geçirebilirsiniz.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Verilen dizeler desteklendiğinde, enumun değerini sorunsuz bir şekilde geçirebilmelisiniz.


3
Hepsinin en basit uygulaması.
Avi Cohen

22

Bir numaralandırmayı niyetle geçirmek için, enumu tamsayıya dönüştürebilirsiniz.

Ör:

public enum Num{A ,B}

Gönderme (tamsayıya tamsayı):

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

Alma (numaralandırmaya tam sayı):

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

Saygılarımla. :)


8
ya da Num.A.name () ile bir dize (böylece okunabilir) olarak gönderebilir ve daha sonra Num.ValueOf (intent.getStringExtra ("TEST")) ile geri alabilirsiniz
8'de Benoit Jadinon

1
Bence Benoit'in yolu daha güvenlidir çünkü temp.ordinal () pratikte tercih edilmez çünkü ordinal () değeri değişebilir. Bu
gönderiye

15

Gerçekten ihtiyacınız varsa , aşağıdaki gibi name()ve kullanarak bir enum değerini bir String olarak serileştirebilirsiniz valueOf(String):

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

Sekmelerinizin değiştirilebilir durumu varsa (ki gerçekten olmamalıdır) bu açık bir şekilde çalışmaz.


4

Enum'unuzun Serializable uygulamasını yapmak mümkün olabilir, sonra bunu serileştirilebilir olarak geçirmenin bir yöntemi olduğundan, Niyet aracılığıyla iletebilirsiniz. Numaralandırma yerine int kullanma tavsiyesi sahte. Numaralandırmalar, kodunuzun okunmasını ve bakımını kolaylaştırmak için kullanılır. Enum'ları kullanmamak karanlık çağlara doğru geriye doğru büyük bir adım atardı.


2
Herhangi bir Enum türü varsayılan olarak Enum üst sınıfını genişletir, bu da Serializable'ı zaten uygular.
Arcao

2

Oderik'in gönderisi hakkında:

Numaralandırmanızı, numaralandırmalar için oldukça kolay olan Parcelable uygulamasını uygulayabilirsiniz:

public enum MyEnum Parcelable {...} uygular Intent.putExtra (String, Parcelable) kullanabilirsiniz.

Bir MyEnum değişkeni myEnum tanımlarsanız, intent.putExtra ("Parcelable1", myEnum) komutunu kullanırsanız, "putExtra (String, Parcelable) yöntemi Intent türü için belirsizdir" hata iletisini alırsınız. çünkü ayrıca bir Intent.putExtra (String, Parcelable) yöntemi vardır ve orijinal 'Enum' türünün kendisi Serializable arabirimini uygular, bu nedenle derleyici hangi yöntemi seçeceğini bilmez (intent.putExtra (String, Parcelable / veya Serializable)).

Parcelable arabirimini MyEnum'dan kaldırmanızı ve çekirdek kodu wrap sınıfının Parcelable uygulamasına şu şekilde taşımanızı önerin (Father2 Parcelable ve bir numaralandırma alanı içerir):

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

}

o zaman yapabiliriz:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));

5
Açıkça doğru argümanı "dökümle" seçerek seçebilirsiniz, örneğinintent.putExtra("myEnum", (Parcelable) enumValue);
Oderik

Ordinal kullanmak mükemmel!
slott

Bu gerçekten karmaşık bir söylem şekli bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob

2

ilkel veri türüne sahip enum için enum yapıcısını kullanabilirsiniz.

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

int'i ekstralar olarak iletip sonra değerini kullanarak numaradan çekin.


2

Parcelable konseptini kullanan cevapların çoğu Java kodundadır. Kotlin'de yapmak daha kolaydır.

@Parcelize ve Parcelable arabirimini uygulayarak numaralandırma sınıfınıza açıklama ekleyin .

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}

1

Ben basit severim.

  • Fred etkinliğinin iki modu vardır - HAPPYveSAD .
  • Kendiniz için bir statik IntentFactoryoluşturun Intent. Geç şunuMode istersen.
  • IntentFactoryAdını kullanırMode ekstra adı olarak sınıfına.
  • IntentFactoryDönüştürür Modea Stringkullanarakname()
  • Girdikten sonra onCreatebu bilgileri geri dönüştürmek için kullanın Mode.
  • Sen kullanabilirsiniz ordinal()ve Mode.values()de. Dizeleri seviyorum çünkü hata ayıklayıcıda görebiliyorum.

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

meraklı .. IntentFactory'yi kim çağırıyor? Farklı bir etkinliğin Fred'i nasıl arayacağı ve Fred'in Modun geçildiğinden nasıl emin olabileceği hakkında ayrıntılı bilgi verebilir misiniz?
erik

0

En iyi bahis, bu listeleri bir dize (veya harita?) Etkinlik için almak için parcelable bir şeye dönüştürmek olacağını düşünüyorum. Ardından, Etkinliğin bir diziye geri dönüştürmesi gerekir.

Özel parcelables uygulamak boyun IMHO'da bir ağrıdır, bu yüzden mümkünse bundan kaçınırım.


0

Aşağıdaki numarayı düşünün ::

public static  enum MyEnum {
    ValueA,
    ValueB
}

Geçmek için ::

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

Niyet / paket / argümanlardan geri almak için ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");

0

Sadece bir numaralandırma göndermek istiyorsanız aşağıdaki gibi bir şey yapabilirsiniz:

İlk olarak, bir değer içeren (bir niyetten geçirilebilen) bir enum bildiriniz:

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

Sonra:

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

Ve bunu elde etmek istediğinizde:

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

Kekin parçası!


0

Kotlin Uzantı İşlevlerini Kullanma

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] }

Bu, aynı numaralandırma türünün birden çoğunu veya varsayılan olarak sınıf adını kullanma esnekliğini sağlar.

// 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>()

-2

Numaralandırma kullanmayın. 78 numaralı nedenin numaralandırma kullanmaması. :) Bundle ve Parcelable ile kolayca çıkarılabilen tamsayılar kullanın.


8
@hackbod - diğer 77 neden nedir? ;) Cidden - numaralandırmanın birçok faydası var gibi görünüyor ve 'uzaktan' tam olarak zor değiller - onlara karşı nedenlerinizi genişletme şansınız var mı?
ostergaard

3
@hackbod Lütfen detaylandırın. Numaralandırmaların kullanılmaması gerekiyorsa, bunları API'dan kaldırın.
dcow

2
Numaralandırmalar Java dil spesifikasyonunun bir parçasıdır, bu nedenle kaldırılması biraz zor ve yine de uyumlu bir Java uygulaması var :)
biraz

1
Ne hakkında mEnum.ordinal()? elemanın pozisyonuna geri dönüyor
Saif Hamed

3
Değeri Enum.ordinal()derleme zamanında sabitlenir. Yorumun geçerli olduğu tek zaman , numaralandırmanın farklı sürümlerine sahip uygulamalar arasında veya numaralandırma içindeki öğelerin sırasını değiştiren uygulama güncellemeleri arasında veri aktarmak olacaktır. İlkel olmayan bir şey kullandığınızda bu tür şeyler tehlikelidir. Tek bir uygulamadaki aktiviteler arasındaki niyetleri geçmek Enum.ordinal()için tamamen güvenli olmalıdır.
Ian McLaird
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.