【问题标题】:Passing enum or object through an intent (the best solution)通过意图传递枚举或对象(最佳解决方案)
【发布时间】:2011-02-19 15:28:33
【问题描述】:

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

基本上,我需要一种将这些对象从 Intent 传递给活动的方法。我可以使用 addExtras() 但这需要 Parceable 兼容类。我可以让我的类传递可序列化,但据我所知,这会减慢程序的速度。

我有什么选择?

我可以传递一个枚举吗?

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

【问题讨论】:

  • 也许我遗漏了什么,但枚举与 ArrayList 有什么关系?

标签: android


【解决方案1】:

这是一个老问题,但每个人都没有提到枚举实际上是Serializable,因此可以完美地添加到 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 的顺序,那么任何旧的引用都将不起作用。这可能是待处理意图中的意图之类的问题,因为它们可能会在更新后继续存在。不过,在剩下的时间里,应该没问题。

请务必注意,如果我们重命名任何值,其他解决方案(例如使用名称而不是位置)也会失败。虽然在这些情况下,我们会得到一个异常而不是错误的 Enum 值。

用法:

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

【讨论】:

  • +1 指出将它们扩大应用范围是一个“真正的坏主意”。
  • 我实际上在一个项目中工作过,我只是不想处理序列化或捆绑对象(很多对象中包含很多变量),并且使用静态全局变量很好。 ..直到一个队友进入这个项目。尝试协调使用这些全局变量的成本让我“搞砸了。我正在编写一个代码生成器来制作一些 Parcelables”。错误数量显着下降
  • @Coeffect 是的,这是一个可以理解的建议,但在大多数情况下,这可以被视为过早优化,除非您要解析数千个枚举(本质上它们应该只是少数,因为它们被使用处理状态)在 Nexus 4 上,你得到了 1ms 的改进(developerphil.com/parcelable-vs-serializable)不确定是否值得额外的腿工作,但是你又可以得到我建议的其他选择;)
  • @rgv 在底层,Kotlin 将 enum class 类型交叉编译为普通的 Java enum。我想更简单的解决方法是让 enum class 实现 Serializable: enum class AwesomeEnum : Serializable { A, B, C } 不理想,但应该可以。
  • @Pierre 与任何好的答案一样,有一个“取决于”。不需要扩展 Serialisable。最初的答案是有效的。这些额外的选项是在您遇到可能存在瓶颈的情况下,例如您正在反序列化数百万条记录(希望不会)。使用你认为合适的...
【解决方案2】:

你可以让你的枚举实现 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

【讨论】:

  • 也可以用枚举的 toString() 和 valueOf() 方法代替序数。
  • 当开发人员插入新的枚举成员时,使用 ordinal() 可能会中断。当然,重命名枚举成员也会破坏 name() 。但是,开发人员更有可能插入新成员,而不是重命名,因为重命名需要他重构整个项目。
  • 我不同意额外的(或重新排序的)枚举值比重命名的枚举值更有可能。使用像 IntelliJ IDEA 重构这样复杂的 IDE 并不是什么大问题。但是您的观点仍然很好:您必须确保序列化在任何共存实现中都是一致的。对于任何类型的序列化都是如此。我认为在大多数情况下,parcelables 是在一个应用程序中传递的,只有一个实现存在,所以这应该不是问题。
  • values() 在每次调用时都会创建一个新数组,因此最好将其缓存在例如。私有静态数组
  • 在一般情况下(例如数据库存储),ordinal() 不安全的事实与 Android 包裹无关。包裹不适用于长期(永久)存储。他们死于应用程序。所以当你添加/重命名一个枚举时,你会得到新的包裹。
【解决方案3】:

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

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

如果支持字符串,您应该能够毫无问题地传递枚举的值。

【讨论】:

  • 最简单的实现。
【解决方案4】:

要按意图传递枚举,您可以将枚举转换为整数。

例如:

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

最好的问候。 :)

【讨论】:

  • 或者您可以使用 Num.A.name() 将其作为字符串发送(因此它是可读的),然后使用 Num.ValueOf(intent.getStringExtra("TEST"))
  • 我认为 Benoit 的方式更安全,因为 temp.ordinal() 在实践中不是首选,因为 ordinal() 值可能会改变。看到这个帖子:stackoverflow.com/questions/2836256/…
【解决方案5】:

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

如果您的枚举具有可变状态(它们不应该,真的),这显然不起作用。

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • 任何 Enum 类型默认扩展 Enum 超类,它已经实现了 Serializable。
    【解决方案7】:

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

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

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

    【讨论】:

      【解决方案8】:

      关于 Oderik 的帖子:

      你可以让你的枚举实现 Parcelable,这对枚举来说很容易:

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

      如果你定义了一个MyEnum变量myEnum,然后做intent.putExtra("Parcelable1", myEnum),你会得到一个“The method putExtra(String, Parcelable) is ambiguous for the type Intent”的错误信息。 因为还有一个 Intent.putExtra(String, Parcelable) 方法,而原来的 'Enum' 类型本身实现了 Serializable 接口,所以编译器不知道选择哪个方法(intent.putExtra(String, Parcelable/or 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));
      

      【讨论】:

      • 您可以通过“强制转换”参数来明确选择正确的签名,例如intent.putExtra("myEnum", (Parcelable) enumValue);
      • 使用序数就完美了!
      • 这是bundle.putExtra("key", AnotherEnum.X.ordinal()) 的一种非常复杂的说法。
      【解决方案9】:

      您可以使用枚举构造函数让枚举具有原始数据类型..

      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 作为附加值传递,然后使用它的值从枚举中提取它。

      【讨论】:

        【解决方案10】:

        我喜欢简单。

        • Fred 活动有两种模式——HAPPYSAD
        • 创建一个静态IntentFactory,为您创建Intent。将您想要的 Mode 传递给它。
        • IntentFactory 使用 Mode 类的名称作为额外的名称。
        • IntentFactory 使用name()Mode 转换为String
        • 进入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 如何确保通过 Mode?​​span>
        【解决方案11】:

        我认为您最好的选择是将这些列表转换为可打包的东西,例如字符串(或映射?)以将其传递给 Activity。然后 Activity 必须将其转换回数组。

        实施自定义 Parcelables 恕我直言,我会很痛苦,所以如果可能的话,我会避免它。

        【讨论】:

          【解决方案12】:

          考虑以下枚举::

          public static  enum MyEnum {
              ValueA,
              ValueB
          }
          

          传递::

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

          从意图/捆绑/参数中检索::

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

          【讨论】:

            【解决方案13】:

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

            首先声明一个包含一些值的枚举(可以通过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);
            

            小菜一碟!

            【讨论】:

              【解决方案14】:

              使用 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>()
              

              【讨论】:

                【解决方案15】:

                不要使用枚举。原因 #78 不使用枚举。 :) 使用整数,可以通过 Bundle 和 Parcelable 轻松远程。

                【讨论】:

                • @hackbod - 其他 77 个原因是什么? ;) 说真的 - 枚举似乎确实有很多好处,而且它们并不是很难“远程” - 你有没有机会扩展你反对它们的理由?
                • @hackbod 请详细说明。如果不应使用枚举,则将它们从 API 中删除。
                • 枚举是 Java 语言规范的一部分,因此删除它们有点困难,并且仍然具有兼容的 Java 实现:)
                • mEnum.ordinal() 怎么样?它返回元素的位置
                • Enum.ordinal() 的值在编译时是固定的。唯一适用注释的情况是在具有不同枚举版本的 apps 之间传递数据,或者在更改枚举中元素顺序的应用更新之间传递数据。每当您使用任何不是原始的东西时,这种事情都是危险的。对于在单个应用程序内的活动之间传递意图,Enum.ordinal() 应该是完全安全的。
                猜你喜欢
                • 1970-01-01
                • 2014-02-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-05-12
                • 1970-01-01
                • 2012-11-15
                相关资源
                最近更新 更多