通过意图传递枚举或对象(最佳解决方案)


221

我有一个活动,启动时需要访问两个不同的ArrayList。这两个列表都是我创建的不同对象。

基本上,我需要一种将这些对象从Intent传递到活动的方法。我可以使用addExtras(),但这需要一个Parceable兼容类。我可以使我的类通过序列化传递,但是据我了解,这会使程序变慢。

我有什么选择?

我可以通过枚举吗?

顺便说一句:有没有办法将参数从Intent传递给Activity构造函数?


也许我缺少了一些东西,但是枚举与ArrayList有什么关系?
Martin Konecny 2015年

Answers:


558

这是一个古老的问题,但是每个人都没有提到枚举实际上是枚举Serializable,因此可以完美地将Enums 作为额外的东西添加到Intent中。像这样:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

建议使用静态或应用程序范围内的变量确实是个坏主意。这确实将您的活动与状态管理系统耦合在一起,并且难以维护,调试和解决问题。


备择方案:

tedzyc指出了一个很好的观点,即Oderik提供的解决方案会给您带来错误。但是,提供的替代方法使用起来有些麻烦(甚至使用泛型)。

如果您真的担心将枚举添加到Intent中的性能,我建议使用以下替代方法:

选项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)];
  }
}

用法:

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

选项2 :( 通用,可重用且与枚举分离)

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

用法:

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

选项3(使用Kotlin):

已经有一段时间了,但是自从我们有了Kotlin以来,我想我会为新范式添加另一个选择。在这里,我们可以利用扩展功能和已类型化的类型(在编译时保留类型)。

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

这样做有一些好处。

  • 我们不需要中间对象的“开销”来进行序列化,因为序列化已全部完成,inline这将使用函数内的代码替换调用。
  • 这些功能与SDK相似,因此更加熟悉。
  • IDE将自动完成这些功能,这意味着您无需事先了解实用程序类。

缺点之一是,如果我们更改Emums的顺序,则任何旧的参考将不起作用。这可能与挂起的Intent中的Intent之类的问题有关,因为它们可能会在更新后幸存下来。但是,在其余时间里应该没问题。

重要的是要注意,如果重命名任何值,其他解决方案(例如使用名称代替位置)也将失败。尽管在这种情况下,我们得到的是异常而不是错误的Enum值。

用法:

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

14
+1表示使它们在应用程序范围内扩展是“真正的坏主意”。
bugfixr 2012年

3
实际上,我确实在一个我不想处理序列化或捆绑对象(其中有很多变量的对象)的项目中工作,并且使用静态全局变量很好...直到队友加入项目。试图协调使用这些全局变量的代价使我“不知所措。我正在编写代码生成器以使我成为Parcelables”。错误的数量大大减少了
Joe Plante

2
@Coeffect是的,这是可以理解的建议,但是在大多数情况下,除非您要解析数千个枚举(考虑到它们用于处理状态,它们应该只是少数),否则可以将其视为过早的优化。您获得了1ms的改善(developerphil.com/parcelable-vs-serializable),不确定是否值得进行额外的工作,但是您再次提出了我建议的其他替代方法;)
pablisco 2014年

1
@rgv Kotlin交叉将enum class类型编译为纯Java enum。我猜较容易的解决方法是使enum class工具Serializableenum class AwesomeEnum : Serializable { A, B, C }不理想,但应该可以。
pablisco

1
@Pierre与任何好的答案一样,有一个“取决于”。无需扩展Serialisable。最初的答案是有效的。这些额外的选择是在您可能遇到瓶颈的情况下,例如您要反序列化数百万条记录(不希望)。使用您认为合适的...
pablisco

114

您可以使枚​​举实现Parcelable,这对于枚举非常简单:

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

然后,您可以使用Intent.putExtra(String,Parcelable)。

更新:请注意wreckgar的注释,该注释enum.values()在每次调用时分配一个新数组。

更新:Android Studio具有一个ParcelableEnum可实现此解决方案的实时模板。(在Windows上,使用Ctrl+ J


3
也可以使用enum的toString()和valueOf()方法代替普通方法。
Natix 2012年

2
当开发人员插入新的枚举成员时,使用ordinal()可能会中断。当然,重命名枚举成员也会破坏name()。但是,开发人员更有可能插入新成员而不是重命名,因为重命名要求他重新构建整个项目。
Cheok Yan Cheng 2013年

2
我不同意其他(或重新排序的)枚举值比重命名的枚举值更有可能。使用像IntelliJ IDEA重构这样的复杂IDE并不重要。但是您的观点仍然很好:您必须确保在任何共存实现中序列化都是一致的。对于任何类型的序列化都是如此。我认为在大多数情况下,可包裹性是在只有一个实现存在的一个应用程序中传递的,因此这不是问题。
Oderik

2
values()在每次调用时都会创建一个新数组,因此最好将其缓存在例如。私有静态数组
wreckgar23 2014年

2
通常情况下(例如数据库存储),ordinal()不安全这一事实与Android包裹无关。包裹不用于长期(持久)存储。他们死于应用程序。因此,当您添加/重命名枚举时,将获得新的包裹。
noamtm

24

您可以将枚举作为字符串传递。

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

如果支持字符串,则应该可以毫无问题地传递枚举值。


3
所有这些的最简单实现。
阿维·科恩

22

为了通过意图传递枚举,可以将枚举转换为整数。

例如:

public enum Num{A ,B}

发送(枚举到整数):

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

接收(整数到枚举):

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

最好的祝福。:)


8
或者你可以用Num.A.name把它作为一个字符串(所以它是可读的),(),然后使用Num.ValueOf(intent.getStringExtra(“TEST”))拿回来
贝努瓦Jadinon

1
我认为Benoit的方法更安全,因为在实践中不建议使用temp.ordinal(),因为ordinal()的值可能会发生变化。看到这个帖子:stackoverflow.com/questions/2836256/...
Shnkc

15

如果确实需要,可以使用name()和将枚举序列化为String,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;
     }
   }
 }

如果您的枚举具有可变状态(这实际上是不应该的),则这显然不起作用。


4

可能有可能使您的Enum实现序列化,然后可以通过Intent传递它,因为有一种方法可以将其作为序列化传递。使用int代替枚举的建议是虚假的。枚举用于使您的代码更易于阅读和维护。如果不能使用枚举,将向后退一步,进入黑暗时代。


2
默认情况下,任何Enum类型都扩展了Enum超类,该超类已经实现了Serializable。
阿卡

2

关于Oderik的帖子:

您可以使枚​​举实现Parcelable,这对于枚举非常简单:

公共枚举MyEnum实现Parcelable {...}然后可以使用Intent.putExtra(String,Parcelable)。

如果定义MyEnum变量myEnum,然后执行intent.putExtra(“ Parcelable1”,myEnum),则会收到“ Intent类型的方法putExtra(String,Parcelable)模棱两可”的错误消息。因为也有一个Intent.putExtra(String,Parcelable)方法,并且原始的“ Enum”类型本身实现了Serializable接口,所以编译器不知道选择哪种方法(intent.putExtra(String,Parcelable /或Serializable))。

建议从MyEnum中删除Parcelable接口,然后将核心代码移动到wrap类的Parcelable实现中,如下所示(Father2是Parcelable并包含枚举字段):

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

}

然后我们可以做:

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

5
您可以通过“投射”参数来明确选择正确的签名,例如intent.putExtra("myEnum", (Parcelable) enumValue);
Oderik 2013年

使用序数是完美的!
老虎机

这是一种非常复杂的说法bundle.putExtra("key", AnotherEnum.X.ordinal())
TWiStErRob '16

2

您可以使用enum构造函数使enum具有原始数据类型。

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作为附加值传递,然后使用其值将其从枚举中拉出。


2

这里使用Parcelable概念的大多数答案都在Java代码中。在Kotlin中这样做更容易。

只需使用@Parcelize注释您的枚举类 并实现Parcelable接口。

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

1

我喜欢简单。

  • Fred活动有两种模式- HAPPYSAD
  • 创建一个为您IntentFactory创建自己的静态对象Intent。通过Mode你想要的。
  • IntentFactory使用的名称Mode类作为额外的名字。
  • IntentFactory转换ModeString使用name()
  • 进入onCreate使用后,此信息将转换回Mode
  • 您也可以使用ordinal()Mode.values()。我喜欢字符串,因为我可以在调试器中看到它们。

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

很好奇..谁叫IntentFactory?您能否详细说明另一个活动如何调用Fred,以及Fred如何确保通过模式?
erik

0

我认为您最好的选择是将这些列表转换为可打包的内容,例如字符串(或映射?),然后将其发送到Activity。然后,活动将不得不将其转换回数组。

实施自定义可打包邮件是恕我直言的痛苦,因此,如果可能的话,我会避免这样做。


0

考虑以下枚举::

public static  enum MyEnum {
    ValueA,
    ValueB
}

对于传递::

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

要从intent / bundle / arguments中取回::

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

0

如果您只想发送一个枚举,则可以执行以下操作:

首先声明一个包含一些值的枚举(可以通过intent传递):

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

然后:

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

而当您想要得到它时:

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

小菜一碟!


0

使用Kotlin扩展功能

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

这使您可以灵活地传递多个相同的枚举类型,或者默认使用类名。

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

不要使用枚举。原因#78不使用枚举。:)使用整数,可以很容易地通过Bundle和Parcelable对其进行远程控制。


8
@hackbod-其他77个原因是什么?;)严重的是-枚举似乎确实有很多好处,并且它们并不是很难“消除”的机会-您是否有机会针对自己的理由展开扩展?
ostergaard

3
@hackbod请详细说明。如果不应使用枚举,则将它们从API中删除。
dcow

2
枚举是Java语言规范的一部分,因此很难删除,并且仍然具有兼容的Java实现:)
tad 2014年

1
那又如何mEnum.ordinal()呢?它返回元素的位置
Saif Hamed 2014年

3
的值Enum.ordinal()在编译时固定。注释唯一适用的时间是在具有不同枚举版本的应用程序之间或在更改枚举中元素顺序的应用程序更新之间传递数据。每当您使用非原始的东西时,这种事情都是危险的。在单个应用程序中的活动之间传递意图时,Enum.ordinal()应该完全安全。
伊恩·麦克劳德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.