Показаны сообщения с ярлыком Intent. Показать все сообщения
Показаны сообщения с ярлыком Intent. Показать все сообщения

вторник, 24 мая 2011 г.

Android: передача объектов между Activity

В одном из предыдущих постов мы затронули тему перехода между Activity в Android-приложениях. И, в частности, обсуждали передачу данных из одного Activity в другое. Очевидное решение  intent.putExtra(NextActivity.FIELD_NAME, field_value) хорошо служит нам до тех пор, пока мы передаём данные "простых" типов: String, int, long или массивы. Но стоит нам попытаться передать таким образом объект из нашей модели данных, начинаются трудности.
Объекты таким способом передавать можно, но они должны реализовывать интерфейс Serializable или Parcelable. Первый вариант по некоторым найденным мной отзывам, снижает производительность приложения. Второй - давайте рассмотрим внимательнее.
Вот пример класса одного из моих приложений:

  1. public class Email implements Parcelable {
  2.  
  3.   private String address;
  4.   private String type;
  5.  
  6.   private Email(Parcel in) {
  7.     this.address = in.readString();
  8.     this.type = in.readString();
  9.   }
  10.  
  11.   public String getAddress() {
  12.     return address;
  13.   }
  14.  
  15.   public void setAddress(String address) {
  16.     this.address = address;
  17.   }
  18.  
  19.   public String getType() {
  20.     return type;
  21.   }
  22.  
  23.   public void setType(String t) {
  24.     this.type = t;
  25.   }
  26.  
  27.   public Email(String a, String t) {
  28.     this.address = a;
  29.     this.type = t;
  30.   }
  31.  
  32.   public int describeContents() {
  33.     return 0;
  34.   }
  35.   public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
  36.  
  37.     public Email createFromParcel(Parcel in) {
  38.       return new Email(in);
  39.     }
  40.  
  41.     public Email[] newArray(int size) {
  42.       return new Email[size];
  43.     }
  44.   };
  45.  
  46.   public void writeToParcel(Parcel parcel, int i) {
  47.     parcel.writeString(address);
  48.     parcel.writeString(type);
  49.   }
  50. }
Как видим,он реализует интерфейс Parcelable, а значит может легко передаваться между Activity. Как мы этого добиваемся? Мы дополняем логику класса методами для его Parcel-изации и Де-parcel-изации, проще говоря, сохранения в Parcel и восстановления из него. Parcel тут можно рассматривать как некий буфер, в который можно сложить в определённом порядке данные любых типов и затем (в том же порядке !) их оттуда извлечь.
Складывать содержимое полей класса в Parcel очень просто. Это реализуется в методе writeToParcel. Для восстановления объекта действуем так: Создаём вложенный класс Parcelable.Creator, в котором реализуем два метода. Второй выглядит всегда одинаково, а вот первый для нас весьма важен: он вызовет новый конструктор нашего класса, передавая в него Parcel. В конструкторе мы должны реализовать логику, обратную методу writeToParcel, т.е. вычитать из Parcel-а значения полей класса в том же порядке, в каком их туда записывали. В случае простых полей типа String это несложно. Если же у нас есть поля типа ArrayList, то делаем так:
p.writeInt(phone.size());
for (int i = 0; i < phone.size(); i++) {
  p.writeString(phone.get(i));
}
для сохранения, и
int count = p.readInt();
for (int i = 0; i < count; i++) {
  String ph = p.readString();
  phone.add(ph);
}
для восстановления. Тут p, как вы, наверное, догадались - объект класса Parcel, а phone - поле типа ArrayList.
Если полями нашего класса являются другие наши классы, то мы должны их также "научить Parcel-изоваться".  Тогда поля таких типов мы будем укладывать в Parcel методом
p.writeParcelable(organization, Parcelable.CONTENTS_FILE_DESCRIPTOR);
и извлекать оттуда методом
organization = p.readParcelable(getClass().getClassLoader());

воскресенье, 8 мая 2011 г.

Android: переход между окнами в приложении

Давайте проведём небольшую аналогию. Допустим мы умеем делать сайты, а хотим научиться писать приложения под Android. Казалось бы, мало общего... Но это как посмотреть. Большая часть интерфейсов так или иначе подразумевают переход между окнами (страницами) приложения (сайта). Динамичные web-интерфейсы сводят переход между страницами к минимуму, подменяя отдельные части контента ajax-ом. А там, где это целесообразно, меняем страницу целиком. Есть аналогичный подход в Andriod-приложении? Конечно.
Каждое окно Android-приложения представлено классом, расширяющим Activity. В этом классе изначально нет "контента". Заполняем его методом setContentView(View v), где v - корневой элемент иерархии контейнеров и виджетов, каждый из которых также наследует View. Ничего не  напоминает? Activity похож на Document, а построение иерархии элементов с помощью addView() похоже на appendChild() при построении dom-дерева в javascript. Не правда ли? Теоретически, любое Android-приложение можно построить на базе одного единственного Activity, подменяя в нужный момент его содержимое или часть его с помощью setContentView. Это похоже на чисто ajax-овый сайт из одной страницы. И даже недостатки такого подхода кое-где похожи. На таком сайте бесполезна кнопка "назад" в браузере, а в таком приложении бесполезна аппаратная кнопка "назад" устройства.  Конечно, это поправимо и для сайта и для Android-приложения, но вопрос не в этом. Рассмотрим второй подход. Допустим, мы решили сделать "честное" приложение, где каждому экрану интерфейса соответствует одно Activity. Поступаем примерно так:
Intent intent = new Intent();
intent.setClass(this, NextActivity.class);
startActivity(intent);

Тут NextActivity представляет экран к которому мы переходим. Несколько сложнее, чем в web, конечно. Теперь посмотрим на передачу данных. У каждого Activity своя область видимости переменных, как и у каждой страницы для javascript. Переходя к другому Activity мы теряем возможность использовать поля предыдущего. Данные нужно передавать из одного окна в другое также как и из одной страницы на другую, используя посредника. В случае web, это сервер, который принимает данные из запроса и строит на основании их новую страницу. В случае Android-приложения это среда исполнения, которая по сути делает то же самое.
Тут становится понятна роль Intent. Это что-то вроде запроса. В него мы "вкладываем" команды и данные, и из него потом получаем страницу результата. Происходит это примерно так. В первом Activity:
Intent intent = new Intent();
intent.setClass(this, NextActivity.class);
intent.putExtra(NextActivity.FIELD_NAME, field_value);
startActivity(intent);

А во втором, соответственно, получаем данные из Intent:

  1. public class NextActivity extends Activity {
  2.     public static final String FIELD_NAME = "field_name";
  3.     
  4.     @Override
  5.     public void onCreate(Bundle icicle) {
  6.         super.onCreate(icicle);
  7.         String field_value = getIntent().getExtras().getStirng(FIELD_NAME);
  8.         Toast.makeText(this, "field value is "+field_value, Toast.LENGTH_SHORT).show();
  9.     }
  10. }
Вот так мы передаём строку из первого окна и отображаем её во втором. Первое окно при этом уходит "в фон", а кнопка "назад" в любой момент может вернуть ему управление. При этом данные второго окна останутся недоступными, как и в случае с web-страницами. А чтобы передать данные обратно нужна снова помощь "посредника". В общем, по моему, аналогия есть. Конечно, как и все аналогии она не точна. Но этого и не требуется. Главное в том, что с её помощью легче понять устройство Android-приложений.