Передача enum или объекта через намерение (лучшее решение)


221

У меня есть активность, что при запуске нужен доступ к двум различным спискам ArrayLists. Оба списка - это разные объекты, которые я создал сам.

По сути, мне нужен способ передать эти объекты в действие из намерения. Я могу использовать addExtras (), но для этого требуется совместимый класс Parceable. Я могу сделать так, чтобы мои классы передавались сериализуемо, но, насколько я понимаю, это замедляет работу программы.

Какие у меня варианты?

Могу ли я передать Enum?

Как отступление: есть ли способ передать параметры в конструктор операций из намерения?


Может быть, я что-то упустил, но как enum связан с ArrayList?
Мартин Конечни

Ответы:


559

Это старый вопрос, но все не упоминают, что Enums на самом деле Serializableи, следовательно, могут быть добавлены в Intent в качестве дополнительного. Как это:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

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

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

Предложение использовать статические переменные или переменные всего приложения - действительно плохая идея. Это действительно связывает вашу деятельность с системой управления состоянием, и ее трудно поддерживать, отлаживать и решать проблемы.


АЛЬТЕРНАТИВЫ:

Тедзик отметил хороший момент в том, что решение, предоставленное Oderik, дает вам ошибку. Тем не менее, предлагаемая альтернатива немного громоздка в использовании (даже с использованием дженериков).

Если вы действительно беспокоитесь о возможности добавления enum к 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, я подумал, что добавлю еще одну опцию для новой парадигмы. Здесь мы можем использовать функции расширения и reified-типы (которые сохраняют тип при компиляции).

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, то любая старая ссылка не будет работать. Это может быть проблемой с такими вещами, как Intents внутри ожидаемых намерений, поскольку они могут пережить обновления. Однако в остальное время все должно быть в порядке.

Важно отметить, что другие решения, такие как использование имени вместо позиции, также потерпят неудачу, если мы переименуем любое из значений. Хотя в этих случаях вместо неправильного значения Enum мы получаем исключение.

Использование:

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

14
+1 за то, что указал на то, что «очень плохая идея» - сделать их широкими.
bugfixr

3
Я действительно работал над проектом, где я просто не хотел иметь дело с сериализацией или связыванием объектов (много объектов с большим количеством переменных в них), и использование статических глобальных переменных было хорошо ... пока товарищ по команде не попал на проект. Стоимость попыток скоординировать использование этих глобальных переменных заставила меня пойти наперекосяк. Я пишу генератор кода, чтобы сделать из меня несколько Parcelables. Количество ошибок значительно упало
Джо Планте

2
@Coeffect Да, это понятное предложение, но в большинстве случаев это можно квалифицировать как преждевременную оптимизацию, если вы не анализируете тысячи перечислений (которые по своей природе должны быть только немногими, учитывая, что они используются для обработки состояния) На Nexus 4 Вы получаете улучшение в 1 мс ( developerphil.com/parcelable-vs-serializable ), не уверенное, что это стоит дополнительных усилий, но опять же у вас есть другие альтернативы, которые я предложил;)
pablisco

1
@rgv Под капотом Kotlin кросс-компилирует enum classтипы в простую Java enum. Я полагаю, что более простой обходной путь - сделать enum classинструмент Serializable: enum class AwesomeEnum : Serializable { A, B, C }не идеальный, но должен работать.
Паблиско

1
@Pierre, как с любым хорошим ответом, есть «это зависит». Нет необходимости расширять Serialisable. Первоначальный ответ действителен. Эти дополнительные опции существуют в том случае, если у вас есть случай, когда может быть бутылочное горлышко, например, если вы десериализуете миллионы записей (надеюсь, что нет). Используйте то, что считаете нужным ...
Паблиско

114

Вы можете заставить свой enum реализовывать 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
Также возможны методы перечисления toString () и valueOf () вместо ординалов.
Natix

2
Использование ordinal () может прерваться, когда разработчик вставит новый член перечисления. Конечно, переименование члена enum также нарушит name (). Но разработчики с большей вероятностью вставляют нового участника, а не переименовывают, поскольку переименование требует от него перефакторинга всего проекта.
Чок Ян Ченг

2
Я не согласен с тем, что дополнительные (или переупорядоченные) значения перечисления более вероятны, чем переименованные. Использование сложной IDE, такой как рефакторинг IntelliJ IDEA, не имеет большого значения. Но ваша точка зрения по-прежнему хороша: вы должны убедиться, что сериализация последовательна на протяжении любой сосуществующей реализации. Это верно для любого вида сериализации. Я полагаю, что в большинстве случаев посылки передаются внутри одного приложения, где существует только одна реализация, так что это не должно быть проблемой.
Одерик

2
values ​​() создает новый массив при каждом вызове, поэтому лучше всего его кэшировать, например. приватный статический массив
wreckgar23

2
Тот факт, что в общем случае (например, хранение в БД), ordinal () небезопасен, не относится к посылкам Android. Посылки не предназначены для длительного (постоянного) хранения. Они умирают с приложением. Поэтому, когда вы добавляете / переименовываете enum, вы получаете новые посылки.
noamtm

24

Вы можете передать перечисление в виде строки.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Данные строки поддерживаются, вы должны быть в состоянии передать значение перечисления без проблем.


3
Самая простая реализация их всех.
Ави Коэн

22

Для передачи enum по назначению вы можете преобразовать enum в целое число.

Пример:

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"))
Бенуа Джадинон,

1
Я думаю, что путь Бенуа более безопасен, так как temp.ordinal () не является предпочтительным на практике, потому что значение ordinal () может измениться. Смотрите это сообщение: stackoverflow.com/questions/2836256/…
Shnkc

15

Если вам действительно нужно, вы можете сериализовать enum как String, используя 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;
     }
   }
 }

Это, очевидно, не работает, если ваши перечисления имеют изменяемое состояние (что на самом деле не должно).


4

Может быть возможно сделать ваш Enum реализующим Serializable, тогда вы можете передать его через Intent, так как есть метод для передачи его в качестве сериализуемого. Совет использовать int вместо enum - фальшивка. Перечисления используются, чтобы сделать ваш код легче для чтения и поддержки. Это было бы большим шагом назад в темные века, чтобы не иметь возможности использовать Enums.


2
Любой тип Enum по умолчанию расширяет суперкласс Enum, который уже реализует Serializable.
Arcao

2

о посте Одерика:

Вы можете заставить свой enum реализовывать Parcelable, что довольно просто для перечислений:

public enum MyEnum реализует Parcelable {...} Вы можете использовать Intent.putExtra (String, Parcelable).

Если вы определите переменную MyEnum myEnum, а затем выполните intent.putExtra («Parcelable1», myEnum), вы получите «Метод putExtra (String, Parcelable) неоднозначен для сообщения об ошибке типа Intent». поскольку есть также метод Intent.putExtra (String, Parcelable), а сам оригинальный тип Enum реализует интерфейс Serializable, поэтому компилятор не знает, какой метод выбрать (intent.putExtra (String, Parcelable / или Serializable)).

Предложите удалить интерфейс Parcelable из MyEnum и переместить основной код в реализацию Parcelable класса wrap, например, так (Father2 - Parcelable и содержит поле enum):

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);
приведя

Использование порядкового номера идеально!
slott

Это действительно сложный способ сказать bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob

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 как дополнительные функции, а затем извлечь его из enum, используя его значение.


2

Большинство ответов, использующих концепцию Parcelable, приведены в коде Java. Это проще сделать в Котлине.

Просто аннотируйте свой enum класс с помощью @Parcelize и реализуйте интерфейс Parcelable.

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

1

Мне нравится просто.

  • Фред деятельности имеет два режима - HAPPYиSAD .
  • Создайте статичный, IntentFactoryкоторый создает ваш Intentдля вас. Передайте этоMode вы хотите.
  • IntentFactoryИспользует имя изMode класса как имя дополнительного.
  • В IntentFactoryпреобразует Modeк Stringиспользуя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? Можете ли вы уточнить, как другая деятельность будет вызывать Фреда и как Фред может обеспечить прохождение режима?
Erik

0

Я думаю, что вам лучше всего будет преобразовать эти списки во что-нибудь, например строку (или карту?), Чтобы получить его в Activity. Затем Activity должен будет преобразовать его обратно в массив.

Реализация пользовательских посылок - это боль в шее, ИМХО, поэтому я бы избегал этого, если это возможно.


0

Рассмотрим следующее enum:

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

0

Если вы просто хотите отправить enum, вы можете сделать что-то вроде:

Сначала объявите перечисление, содержащее некоторое значение (которое может быть передано через намерение):

 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 :)
чуть

1
о чем mEnum.ordinal()? это вернуть положение элемента
Саиф Хамед

3
Значение Enum.ordinal()фиксируется во время компиляции. Единственный момент, когда применяется комментарий, - это передача данных между приложениями с различными версиями перечисления или между обновлениями приложений, которые изменяют порядок элементов в перечислении. Подобные вещи опасны, когда вы используете что-то, что не является примитивным. Для передачи намерений между действиями в рамках одного приложения, Enum.ordinal()должны быть полностью безопасными.
Йен Маклаирд
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.