пятница, 15 июня 2012 г.

Синглетоны и наследники класса Application

При разработке приложений для android иногда бывает нужно чтобы какие-то переменные или методы были доступны из всех Activities (Деятельностей) приложения. Например, при работе с базой данной, как правило, пишется класс, который обеспечивает соединение с БД и выборку данных. Но создавать экземпляр этого класса в каждой Activity весьма расточительно. Хочется иметь один экземпляр и обращаться к нему из любой точки приложения.



Singleton

В языке Java для подобных случаев используется паттерн проектирования Singleton. Синглетон это класс у которого гарантировано есть только один экземпляр. В простом случае он выглядит так:
  1. public class Singleton { 
  2.  private static Singleton instance; 
  3.  
  4.  private Singleton (){ 
  5.  } 
  6.  
  7.  public static Singleton getInstance(){ 
  8.   if (null == instance){ 
  9.    instance = new Singleton(); 
  10.   } 
  11.   return instance; 
  12.  } 
  13. }

Это класс в котором определен статический экземпляр этого же класса. В методе getInstance (), если этот экземпляр еще не был инициализирован, то вызывается конструктор. Сам конструктор определен как private, а значит мы не можем вне этого класса создать экземпляр с помощью оператора new.

Попробуем сделать это в приложении android. Я создала приложение с двумя похожими деятельностями



Создадим класс MySingleton (в отдельном файле MySingleton.java):
  1. class MySingleton {
  2.  private static MySingleton mInstance;
  3.  private String MyVariable;
  4.  
  5.  public static MySingleton getInstance() {
  6.   Log.w("MY_TAG""MySingleton::getInstance()");
  7.   if (mInstance == null) {
  8.    mInstance = new MySingleton();
  9.   }
  10.   return mInstance;
  11.  }
  12.  
  13.  private MySingleton() {
  14.   Log.w("MY_TAG""MySingleton::MySingleton()");
  15.   MyVariable = "This is my Variable";
  16.  }
  17.  
  18.  public String getMyVariable() {
  19.   return MyVariable;
  20.  }
  21.  
  22.  public void setMyVariable(String var) {
  23.   MyVariable = var;
  24.  }
  25. }

В классе определена переменная MyVariable, которая инициализируется в конструкторе и два метода для чтения и записи этой переменной getMyVariable() и setMyVariable().

В методы getInstance() и Singleton() я добавила отладочную печать Log.w(), чтобы в процессе работы приложения посмотреть как создается объект.

В обе деятельности добавим строчки
  1. public void onCreate(Bundle savedInstanceState) {
  2.  ...
  3.  Log.w("MY_TAG""First Activity Start");
  4.  EditText text = (EditText) findViewById(R.id.text);
  5.  Singleton ms = MySingleton.getInstance();
  6.  text.setText(ms.getMyVariable());
  7. }

Запустим: в обоих деятельностях в EditText пишется значение переменой.

Посмотрим отладочную печать, для этого в Eclipse надо включить панель LogCat. В меню выберем Window->ShowView->Other. В открывшемся окошке находим Android->LogCat и нажимаем ОК. Теперь внизу появилась вкладка LogCat, куда выводятся все сообщения с эмулятора или подключенного устройства. Для удобства можно включить фильтр по тегу (у меня это «MY_TAG»).

При работе нашего приложения мы увидим что-то такое:
* First Activity Start
* MySingleton::getInstance()
* MySingleton::MySingleton()
* Second Activity Start
* MySingleton::getInstance()
* First Activity Start
* MySingleton::getInstance()

Как мы видим, конструктор вызывался только при первом запуске FirstActivity, а дальше getInstance() только возвращал переменную. Свою основную задачу класс Singleton выполняет: у нас есть один экземпляр класса доступный из любого места приложения.

Но в таком подходе есть один недостаток. Мы создаем экземпляр класса Singleton в контексте текущей Activity и если эта Activity завершает свою работу, то и экземпляр класса будет уничтожен (причем не сразу, а когда сработает сборщик мусора Java). А значит при следующем вызове getInstance() объект будет создаваться заново (вновь вызовется конструктор). Если в классе Singleton мы храним какие-то статичные переменные или методы, то это можно просто проигнорировать.

Вполне очевидным решением данной проблемы является создание экземпляра класса Singleton (первый вызов getInstance()) в какой-то Activity, которая будет жить на протяжении всего времени работы приложения. Но система android не может гарантировать, что неактивная Activity не будет уничтожена в случае нехватки памяти.

Класс Application

Класс Application - это базовый класс приложения android. При запуске программы вначале создается экземпляр этого класса, а потом уже необходимые деятельности. Напишем собственную реализацию этого класса.

Напишем в MyApp.java
  1. public class MyApp extends Application {
  2.  @Override
  3.  public void onCreate() {
  4.   super.onCreate();
  5.   Log.w("MY""onCreate MyApp");
  6.  
  7.   MySingleton.initInstance();
  8.  }
  9. }

В классе MySingleton разделим getInstance() на два метода:
  1. class MySingleton {
  2.  ...
  3.  
  4.  public static void initInstance() {
  5.   Log.d("MY""MySingleton::InitInstance()");
  6.   if (mInstance == null) {
  7.    mInstance = new MySingleton();
  8.   }
  9.  }
  10.  
  11.  public static MySingleton getInstance() {
  12.   Log.d("MY""MySingleton::getInstance()");
  13.   return mInstance;
  14.  }
  15.  
  16.  ...
  17. }

Также надо прописать класс MyApp в AndroidManifest.xml
  1. <application
  2.  android:icon="@drawable/ic_launcher"
  3.  android:label="@string/app_name" 
  4.  android:name=".MyApp">
  5.  
  6.  ...
  7. </application>

Запустим
Внешне ничего не изменилось, но теперь вне зависимости от того какие activities работают экземпляр класса MySingleton будет существовать.

Посмотрим логи:
onCreate MyApp
Singleton::InitInstance()
Singleton::Singleton()
First Activity Start
Singleton::getInstance()

Первым вызывается метод onCreate() класса MyApp в котором создается экземпляр класса MySingleton. Только после этого запускается FirstActivity.

В классе Application кроме метода void onCreate(), который вызывается при старте приложения есть также методы:
void onConfigurationChanged(Configuration newConfig) - Вызывается при изменении конфигурации устройства
void onLowMemory() - Вызывается когда система работает в условиях нехватки памяти, и просит работающие процессы попытаться сэкономить ресурсы.
void onTrimMemory(int level) - Вызывается, когда операционная система решает, что сейчас хорошее время для обрезания ненужной памяти из процесса.

Кстати, в классе Application можно хранить не только синглетоны, но и просто методы и переменные:
  1. public class MyApp extends Application {
  2.  private String MyVariable;
  3.  
  4.  @Override
  5.  public void onCreate() {
  6.   ...
  7.  }
  8.  
  9.  public String getMyVariable() {
  10.   return MyVariable;
  11.  }
  12.  
  13.  public void setMyVariable(String MyVariable) {
  14.   this.MyVariable = MyVariable;
  15.  }
  16. }

Обращаться к этим методам из любой activity можно так:
  1. public void onCreate(Bundle savedInstanceState) {
  2.  ...
  3.  MyApp app = ((MyApp) getApplicationContext());
  4.  text.setText(app.getMyVariable());
  5. }

2 комментария:

  1. Спасибо. Чего дальше не пишите ? Реально очень все интересно. Лучше чем по английски объяснили. +100.

    ОтветитьУдалить
    Ответы
    1. Спасибо, рада, что оказалось полезным :)
      Не всегда руки доходят писать, скоро постараюсь новую статью написать )

      Удалить