Как отправлять объекты через бандл

119

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

Проблема в том, что он не имеет ничего общего с намерениями или контекстами и содержит большое количество непримитивных объектов. Как мне упаковать класс в пакетный / сериализуемый и передать его классу startActivityForResult?

ahodder
источник
2
«Мне нужно передать ссылку на класс, который выполняет большую часть моей обработки через пакет» - почему?
CommonsWare,
1
У меня есть объект (DataManager), он обрабатывает сервер и запускает несколько бэкэндов для некоторых графических интерфейсов. Когда когда-либо устанавливается новое соединение, я хочу, чтобы пользователь мог начать новое действие, которое перечисляет в ListView все активные соединения и позволяет пользователю выбрать одно. Затем полученные данные будут привязаны к новому графическому интерфейсу. На самом деле это просто выбор скина для бэкенда.
ahodder
3
Если вы имеете дело с одним и тем же экземпляром объекта в нескольких действиях, вы можете рассмотреть шаблон singleton . Там хороший учебник здесь .
sotrh

Ответы:

55

Чтобы понять, какой путь выбрать, необходимо ответить не только на ключевой вопрос CommonsWare «почему», но и на вопрос «к чему?» вы проходите мимо.

Реальность такова, что единственное, что может проходить через пакеты, - это простые данные - все остальное основано на интерпретации того, что эти данные означают или на что указывают. Вы не можете буквально передать объект, но вы можете сделать одно из трех:

1) Вы можете разбить объект на составляющие данные, и если то, что на другом конце знает об объекте того же типа, оно может собрать клон из сериализованных данных. Вот как большинство распространенных типов проходят через связки.

2) Можно передать непрозрачную ручку. Если вы передаете его в одном контексте (хотя можно спросить, зачем беспокоиться), это будет дескриптор, который вы можете вызвать или разыменовать. Но если вы передадите его через Binder в другой контекст, его буквальным значением будет произвольное число (фактически, эти произвольные числа считаются последовательно с момента запуска). Вы не можете ничего делать, кроме как отслеживать его, пока не передадите его обратно в исходный контекст, что заставит Binder преобразовать его обратно в исходный дескриптор, что снова сделает его полезным.

3) Вы можете передать волшебный дескриптор, такой как дескриптор файла или ссылку на определенные объекты ОС / платформы, и если вы установите правильные флаги, Binder создаст клон, указывающий на тот же ресурс для получателя, который фактически может быть использован на другой конец. Но это работает только для очень немногих типов объектов.

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

Крис Стрэттон
источник
1
Спасибо за ответ тура. Ваше право, все, что мне нужно сделать, это просто передать ссылку на список объектов моему новому действию. Новое действие возьмет некоторые данные из списка и отобразит список ListView, который можно выбрать. onSelect, действие вернет результат (некоторые данные, относящиеся к объекту щелчка) для действия узла. Если я правильно понимаю, я считаю, что ваш вариант 2 справляется с этим наиболее подходящим образом; как мне получить эту непрозрачную ручку?
ahodder
Другое ваше действие не может извлечь какие-либо данные из непрозрачного объекта для отображения. Что вы, вероятно, захотите сделать, так это создать и передать некоторые суррогатные объекты поддерживаемого типа, которые содержат копии информации, которая будет отображаться.
Крис Стрэттон,
158

Вы также можете использовать Gson для преобразования объекта в JSONObject и передачи его в пакет. Для меня это был самый элегантный способ сделать это. Я не тестировал, как это влияет на производительность.

В начальной деятельности

Intent activity = new Intent(MyActivity.this,NextActivity.class);
activity.putExtra("myObject", new Gson().toJson(myobject));
startActivity(activity);

В следующем действии

String jsonMyObject;
Bundle extras = getIntent().getExtras();
if (extras != null) {
   jsonMyObject = extras.getString("myObject");
}
MyObject myObject = new Gson().fromJson(jsonMyObject, MyObject.class);
Laranjeiro
источник
3
Поскольку речь идет о передаче данных между действиями, это происходит не так часто, чтобы сильно повлиять на общую производительность приложения. При этом я сомневаюсь, что это сработает для сериализации DataManager исходного сообщения, поскольку похоже, что у него есть соединения сокетов и другие подобные классы.
britzl 07
4
Также Google предлагает это решение вместо Serialize: ознакомьтесь с последним разделом «Рекомендуемые альтернативы» на этой странице документа
TechNyquist
3
просто в качестве предупреждения, я некоторое время следовал этой технике, но есть ограничение памяти на то, что вы можете передать как String, поэтому убедитесь, что ваши данные не слишком велики.
jiduvah
См. Blog.madadipouya.com/2015/09/21/…, чтобы узнать, как добавить поддержку Gson в свой проект.
geekQ 05
Умное решение, снимаю шляпу!
Rohit Mandiwal
20

Интерфейс Parcelable - хороший способ передать объект с намерением.

Как я могу сделать свои пользовательские объекты доступными для посылки? - довольно хороший ответ о том, как использовать Parcelable

Официальные документы Google также включают пример

Крис
источник
1
или они также могут быть сериализуемыми.
Джеффри Блаттман
1
Но значительно снижает производительность в 10 раз !! Посмотрите этот тест: developerphil.com/parcelable-vs-serializable
saiyancoder
2
+1 комментарий @ Mati, однако, чтобы поместить его в контекст, 10x при применении к одиночному объекту эквивалентно 1 мс. Так что, возможно, не так плохо, как кажется.
pinoyyid 09
1
Согласен. Проблема в том, что вы имеете дело с коллекциями, что является очень распространенным вариантом использования, если вы получаете ресурсы из Rest API. Но для одного объекта не должно быть чего-то печально известного. В любом случае, если весь шаблонный код что-то мешает вам, вы можете попробовать эту библиотеку, которая генерирует все это для вас: github.com/johncarl81/parceler . Действительно хороший подход!
saiyancoder
Неработающая ссылка: 404 (не найдено)
Gallal
14

Вы можете использовать глобальное состояние приложения .

Обновить:

Настройте, а затем добавьте это в свой AndroidManifest.xml:

<application android:label="@string/app_name" android:debuggable="true" android:name=".CustomApplication"

А затем создайте в своем проекте такой класс:

package com.example;

import android.app.Application;

public class CustomApplication extends Application {
    public int someVariable = -1;
}

И поскольку «к нему можно получить доступ через getApplication () из любого действия или службы », вы используете его следующим образом:

CustomApplication application = (CustomApplication)getApplication();
application.someVariable = 123; 

Надеюсь, это поможет.

Нил
источник
1
Спасибо за ответ, а как?
ahodder
Я считаю, что вы просто подклассифицируете Application и можете хранить все, что захотите. Необходимые вам изменения xml указаны в приведенной выше ссылке.
Марк Сторер,
9
Как общий принцип дизайна, рекомендуется избегать глобальных переменных, если они вам действительно не нужны. В этом случае есть хорошие альтернативы.
dhaag23
Хорошо, я думаю, что понимаю, о чем вы говорите. Просто расширьте Application и добавьте переменную, содержащую объект, который необходимо передать; Я просмотрел справочную страницу и не увидел необходимых изменений xml.
ahodder
Я тоже хотел написать это как ответ. Это определенно один из способов сделать это. Но имейте в виду, что эти объекты остаются в памяти, если вы не отмените их референцию (или контекст приложения не будет уничтожен) и могут занимать место, когда они вам не нужны.
Игорь Кордаш
12

Вы также можете сделать свои объекты сериализуемыми и использовать методы getSerializable и putSerializable из Bundle .

dhaag23
источник
1
Я попробовал это и быстро понял, что это непрактично. Я не думаю, что большинство объектов, хранящихся в переданном классе (потоках), сериализуемы. :) Спасибо хоть.
ahodder
10

Возможное решение:

Bundle bundle = new Bundle();
bundle.putSerializable("key", new CustomObject());

Класс CustomObject:

class CustomObject implements Serializable{
 private SubCustomObject1 sc1;
 private SubCustomObject2 sc2;
}

Подпользовательские объекты:

class SubCustomObject1 implements Serializable{ }

class SubCustomObject2  implements Serializable{ }
PraKhar
источник
7

Еще один способ отправить объекты через пакет - использовать bundle.putByteArray
образец кода

public class DataBean implements Serializable {
private Date currentTime;

public setDate() {
    currentTime = Calendar.getInstance().getTime();
 }

public Date getCurrentTime() {
    return currentTime;
 }
}

поместите объект DataBean в Bundle:

class FirstClass{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//When you want to start new Activity...
Intent dataIntent =new Intent(FirstClass.this, SecondClass.class);
            Bundle dataBundle=new Bundle();
            DataBean dataObj=new DataBean();
            dataObj.setDate();
            try {
                dataBundle.putByteArray("Obj_byte_array", object2Bytes(dataObj));

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }

            dataIntent.putExtras(dataBundle);

            startActivity(dataIntent);
}

Преобразование объектов в байтовые массивы

/**
 * Converting objects to byte arrays
 */
static public byte[] object2Bytes( Object o ) throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream( baos );
      oos.writeObject( o );
      return baos.toByteArray();
    }

Вернуть объект из пакета:

class SecondClass{
DataBean dataBean;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//Get Info from Bundle...
    Bundle infoBundle=getIntent().getExtras();
    try {
        dataBean = (DataBean)bytes2Object(infoBundle.getByteArray("Obj_byte_array"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Метод получения объектов из байтовых массивов:

/**
 * Converting byte arrays to objects
 */
static public Object bytes2Object( byte raw[] )
        throws IOException, ClassNotFoundException {
      ByteArrayInputStream bais = new ByteArrayInputStream( raw );
      ObjectInputStream ois = new ObjectInputStream( bais );
      Object o = ois.readObject();
      return o;
    }

Надеюсь, это поможет другим приятелям.

Рупеш Ядав
источник
это выглядит гладко и легко, глядя на код. Но я чувствую, что есть еще кое-что о том, почему SDK не предлагает что-то подобное для передачи объектов. Вы можете рассказать мне что-нибудь об этом решении?
Mario Lenci
3
Во всем этом коде нет необходимости! Используйте bundle.putSerializable (objectImplementingSerializable) - это работает под тем, что вы снова реализуете здесь ...
Рисадинья
3

1. Очень простой и простой в использовании пример: создайте объект, который нужно передать, с реализацией Serializable.

class Object implements Serializable{
    String firstName;
   String lastName;
}

2. передать объект в связку

Bundle bundle = new Bundle();
Object Object = new Object();
bundle.putSerializable("object", object);

3. получить переданный объект из пакета как Serializable, а затем передать его в Object.

Object object = (Object) getArguments().getSerializable("object");
king_T
источник
0

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

заявка

Во-первых, это приложение , так как об этом здесь больше всего говорят. Приложение - хороший объект для размещения сущностей, которым нужна ссылка на контекст. ServerSocket, несомненно, будет нуждаться в контексте (для файлового ввода-вывода или простых обновлений ListAdapter). Я лично предпочитаю этот маршрут. Мне нравятся приложения, они полезны для извлечения контекста (потому что они могут быть статичными и вряд ли вызовут утечку памяти) и имеют простой жизненный цикл.

обслуживание

` обслуживания - второй. `Сервис` на самом деле лучший выбор для моей проблемы, потому что сервисы предназначены для этого:
Служба - это компонент приложения, который может выполнять длительные операции в
фон и не предоставляет пользовательский интерфейс.
Сервисы удобны тем, что имеют более определенный жизненный цикл, которым легче управлять. Кроме того, при необходимости службы могут работать вне приложения (т. Е. При загрузке). Это может быть необходимо для некоторых приложений или просто изящной функции.

Это не было полным описанием, но я оставил ссылки на документы для тех, кто хочет узнать больше. В целом Serviceэто лучше для экземпляра, который мне нужен - запуска ServerSocket на моем устройстве SPP.

ahodder
источник
0

Я столкнулся с этим вопросом, когда искал способ передать объект Date. В моем случае, как было предложено среди ответов, я использовал Bundle.putSerializable (), но это не сработало бы для такой сложной вещи, как описанный в исходном сообщении DataManager.

Мое предложение, которое даст очень похожий результат на размещение указанного DataManager в приложении или превращение его в Singleton, состоит в том, чтобы использовать Dependency Injection и привязать DataManager к области Singleton и внедрить DataManager везде, где это необходимо. Вы не только получите преимущество повышенной тестируемости, но также получите более чистый код без всего стандартного кода «передачи зависимостей между классами и действиями». (Робо) С Guice очень легко работать, и новый фреймворк Dagger также выглядит многообещающим.

britzl
источник
1
Что ж, с чем-то вроде Date вы можете просто передать длинное значение. Но в остальном звучит хорошо. Спасибо.
ahodder
0

еще один простой способ передать объект с помощью пакета:

  • в объекте класса создайте статический список или другую структуру данных с ключом
  • когда вы создаете объект, поместите его в список / структуру данных с ключом (например, длинную метку времени при создании объекта)
  • создать метод static getObject (длинный ключ) для получения объекта из списка
  • в пакете передайте ключ, чтобы вы могли получить объект позже из другой точки кода
Шаолинь
источник