Использование общего объекта в нескольких активити
При написании программ часто возникает необходимость передачи данных между окнами, активити или представлениями. Например, в Android, чтобы передать простые данные между активити, рекомендуется использовать Extras. Однако возникает необходимость передавать не только простые переменные, но еще и объекты, да так, чтобы сам объект был общим для всех активити в приложении.
Подобная ситуация у меня возникла при использовании объекта класса SoundPool. Его используют для воспроизведения коротких звуков с минимальной задержкой, например, эффекты в играх. Чтобы получить доступ к звукам с минимальной задержкой, необходимо эти звуки записать в память. Сама загрузка происходит не очень быстро, особенно если звуков много, поэтому загружать их для каждого активити не очень грамотно.
Реализация
Для решения этой задачи можно использовать класс Application - класс нашего приложения. Мы можем хранить в нем данные, которые требуется использовать на протяжении всей работы приложения, нужно только создать свой класс, наследованный от Application.
public class ApMy extends Application { }
Осталось не забыть прописать наш класс в манифесте.
<application android:icon="@drawable/icon" android:label="@string/app_name" android:name="ApMy">
Для того, чтобы не использовать в коде магических цифры, нужно объявить необходимые константы.
private static final int COUNT = 2; private static final int QUALITY = 0; private static final int PRIORITY = 1;
Назначение этих констант рассмотрено ниже, в месте их использования.
Теперь необходимо глобально объявить требуемые объекты. В нашем случае это объект класса SoundPool и целочисленный массив SoundID, в котором будут храниться ID загруженных звуков.
private SoundPool soundPool; private int[] soundID;
Чтобы иметь доступ к этим объектам, нужно для этого создать специальные функции в классе ApMy, которые будут возвращать указатели для дальнейшего использования в нужной нам активити.
Первая функция getSoundPool возвращает указатель на объект класса SoundPool:
public SoundPool getSoundPool() { return soundPool; }
Вторая getSoundID - указатель на целочисленный массив soundID:
public int[] getSoundID() { return soundID; }
Но чтобы было что возвращать, нужно провести инициализацию объектов. Я ее специально вынес в отдельную функцию initSound, чтобы была возможность организовать загрузку звуков в отдельном потоке, так как это довольно дорогостоящее по времени удовольствие.
Кстати, звуки boom.mp3 и bang.mp3 хранятся в папке res\raw\ нашего приложения (кроме mp3 можно использовать любой другой формат, который поддерживает Android).
public void initSound() { soundPool = new SoundPool(COUNT, AudioManager.STREAM_MUSIC, QUALITY); soundID = new int[COUNT]; soundID[0] = soundPool.load(getApplicationContext(), R.raw.boom, PRIORITY); soundID[1] = soundPool.load(getApplicationContext(), R.raw.bang, PRIORITY); }
Немного подробней о передаваемых параметрах и константах.
При создании объекта класса SoundPool используется конструктор, в который первым параметром передается максимальное количество звуковых потоков, вторым - типа аудиопотока и, наконец, третьим - качество звука.
В массиве используется та же константа COUNT, что и в конструкторе SoundPool. Здесь она означает размер создаваемого массива. Сколько звуков в SoundPool, столько ID для их использования.
Функция load класса SoundPool первым параметром принимает контекст, вторым - сам звук (точней ID ресурса), который хранится в ресурсах и третьим - приоритет. Результатом выполнения этой функции является ID звука, который мы присваиваем соответствующему элементу массива soundID.
Кстати, на данный момент константы QUALITY и PRIORITY не имеют никакого эффекта, но для совместимости с будущими версиями Android рекомендуется использовать именно эти значения.
Как же теперь этим воспользоваться? А очень просто. Звуки нужно загрузить в самом начале программы с помощью функции initSound, при этом показав пользователю сообщение о том, что придется подождать. Затем в каждой активити, в которой нам потребуются звуки, будем просто присваивать местным объектам указатель на объект, созданный в ApMy. Для этого и будут использоваться функции getSoundPool и getSoundID
Допустим активити, в котором будет происходить загрузка звуков, называется AcMain.
public class AcMain extends Activity { }
Сразу объявим глобально объекты и константы, которые нам понадобятся:
//Номер нашего диалога, нужен для вызова диалога private static final int DIALOG_PROGRESS = 0; //Объект нашего приложения private ApMy myApp; //Поток, в котором будет происходить инициализация private Runnable runnableInitSound; //Диалог занятости, который будет показывать пользователю, что программа не зависла, //а просто понадобилось некоторое время для загрузки ресурсов private ProgressDialog progressDialog;
В наследуемом методе onCreate напишем инициализацию наших объектов. Начнем с создания самого метода. В нем загрузим интерфейс, описание которого находится в res\layout\ac_main.xml. Приводить его я не буду, так как содержимое особого значения не имеет.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ac_main); }
Объект класса ApMy создается очень просто:
myApp = (ApMy) getApplicationContext();
А вот инициализация потока выглядит немого крупнее. На самом деле в нем мы только загружаем звуки в буфер, а после этого закрываем диалог ожидания.
runnableInitSound = new Runnable() { @Override public void run() { myApp.initSound(); progressDialog.dismiss(); } };
После всей инициализации в том же onCreate вызываем диалог ожидания.
showDialog(DIALOG_PROGRESS);
Осталось создать диалог и запустить поток загрузки звуков в буфер.
Для создания и отображения диалога, нам нужно переписать функцию onCreateDialog.
@Override protected Dialog onCreateDialog(int id) { }
В качестве параметра эта функция принимает ID диалога (в нашем случае он занесен в константу DIALOG_PROGRESS), а возвращает созданный диалог. Для работы с определенным диалогом, рекомендуется использовать перебор (в нашем случае перебор будет состоять из одного варианта).
switch(id) { case DIALOG_PROGRESS: //Создаем сам объект progressDialog = new ProgressDialog(this); //Выставляем сообщение, которое будет показываться пользователю progressDialog.setMessage("Идет загрузка звуков..."); //Устанавливаем стиль диалога в "ожидающий" progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); //Запрещаем закрываться диалогу при нажатии на аппаратную кнопку "Отмена" progressDialog.setCancelable(false); //Показываем наш диалог пользователю progressDialog.show(); //А здесь просто создаем поток для загрузки звуков и запускаем его Thread thread = new Thread(threadInitSound); thread.start(); return progressDialog; default: return null; }
Вот и все. Теперь можно в любой активити нашего приложения создавать объект класса ApMy и получать из него ссылки на объекты без повторной загрузки звуков в буфер.