Решения конкретных задач программирования. Java, Android, JavaScript, Flex и прочее... Настройка софта под Linux, методики разработки и просто размышления.
Показаны сообщения с ярлыком Service. Показать все сообщения
Показаны сообщения с ярлыком Service. Показать все сообщения
среда, 13 ноября 2013 г.
"Вечный Service" в Android
Когда мы делаем приложения, мы руководствуемся самыми гумаными соображениями. Мы хотим облегчить жизнь нашим клиентам, помочь им, защитить, вооружить против любых проблем этого ужасного мира. Но клиенты почему-то не хотят покоряться нашему мудрому руководству. Закрывают наше приложения, останавливают его, находят в списке "запущенных процессов" через настройки и опять-таки останавливают. Ну, как дети малые, ей-богу ;) Если вы в своей всеобъемлющей мудрости хотите спасти своих клиентов от их самих, вам наверняка прийдётся защититься от их жалких попыток упавлять своим смартфоном. Как же это сделать? Перенести логику в Service, чтобы закрытие приложения не останавливало его работу? Недостаточно. Сделать в нём вечный цикл, вызвать startService() в onDestroy() - тоже. В конце концов эти неразумные существа могут перезагрузить смартфон и мы утратим над ними власть не сможем помогать им. Можно, конечно ловить событие загрузки (и ещё стопицот других системных событий) и поднимать наш сервис. Однако система на то и система, чтобы жить своими, непредсказуемыми событиями, а значит никакой гарантии, что эти события произойдут когда нам нужно, увы, нет. Как же нам быть? Ответ очевиден:
понедельник, 14 мая 2012 г.
BroadcastReceiver: общение процессов в Android-приложении
Давайте снова вернёмся к вопросу организации работы фоновых процессов в Android-приложении. В этой статье я описал, как выполнять загрузку файлов с удалённого сервера в фоне. Задача замечательно решается с помощью AsyncTask, но только в случае небольших файлов и недолгого ожидания. Что произойдёт в случае если файл будет загружаться двадцать минут? Телефон уснёт, вы воспользуетесь другим приложением и т.п. - в любом случае загрузка будет прервана. Решение тут очевидно: заменить AsyncTask сервисом, который может работать и без основонго приложения. В случае же использования WakefulIntentService мы практически гарантировано докачаем наш файл несмотря ни на что. И только одну проблему остаётся решить для получения окончательной победы разума над материей: обновлять прелоадер нам нужно в Activity, которая недоступна из сервиса. Выход: использовать внутренний механизм платформы для обмена сообщениями между отдельными компонентами нашего приложения.
Итак, давайте переработаем наше приложения для загрузки файлов так, чтобы оно могло докачать файл при любых обстоятельствах. Научимся использовать BroadcastReceiver.
Итак, давайте переработаем наше приложения для загрузки файлов так, чтобы оно могло докачать файл при любых обстоятельствах. Научимся использовать BroadcastReceiver.
вторник, 24 апреля 2012 г.
Оповещаем Android о событиях на сервере
Допустим, у нас есть достаточно популярное Android-приложение, в которое мы хотим добавить опцию оповещения пользователей о каких-либо событиях. Например, пользователь должен иметь возможность оперативно получить новость или персональное сообщение от другого пользователя. Как решить такую задачу?
Решение в виде сервиса на устройстве, который регулярно опрашивает сервер выглядит несовременно, да и опасно: достаточно большое количество пользователей могут задосить нам сервер. Увеличение интервала опроса в нашем случае решает проблему нагрузки с одной стороны, но снижает скорость доставки с другой.
Второй вариант: использовать Cloud to Device Messaging Framework. Действительно хорошее решение, но в ряде случаев не подходит. Во-первых, передаче сообщений через сторонние сервера могут препятствовать соображения конфиденциальности, а во-вторых, если приложение должно поддерживать версию Android 2.1 и ниже, то этот фреймворк нам не поможет.
Третий вариант - использовать свой http push сервер, например nginx + nginx push stream module. Можно поспорить, что лучше, много запросов или много коннектов а также сравнить производительность разных серверов, но цель у нас сейчас другая. Давайте рассмотрим простой вариант реализации такого оповещения.
Решение в виде сервиса на устройстве, который регулярно опрашивает сервер выглядит несовременно, да и опасно: достаточно большое количество пользователей могут задосить нам сервер. Увеличение интервала опроса в нашем случае решает проблему нагрузки с одной стороны, но снижает скорость доставки с другой.
Второй вариант: использовать Cloud to Device Messaging Framework. Действительно хорошее решение, но в ряде случаев не подходит. Во-первых, передаче сообщений через сторонние сервера могут препятствовать соображения конфиденциальности, а во-вторых, если приложение должно поддерживать версию Android 2.1 и ниже, то этот фреймворк нам не поможет.
Третий вариант - использовать свой http push сервер, например nginx + nginx push stream module. Можно поспорить, что лучше, много запросов или много коннектов а также сравнить производительность разных серверов, но цель у нас сейчас другая. Давайте рассмотрим простой вариант реализации такого оповещения.
вторник, 12 июля 2011 г.
Пишем виджет для Android
В сети есть достаточно много инструкций по написанию виджетов для Android. Большая часть из них "HelloWorld-ы", в остальных местах упущены какие-то важные мелочи... В общем давайте разберём создание виджета "по взрослому": с фоновыми процессами, обработкой кликов, локализацией. Пусть наш виджет получает курс валюты Bitcoin по отношению к доллару из API биржы Mt. Gox и отображает его текущее значение. В результате мы получим что-то вроде этого.
Во-первых, чем виджет отчается от Activity?
Виджет работает в рамках "рабочего стола" нашего смартфона и от этого имеет как плюсы так и минусы. Плюсы: можно настроить обновление средствами системы. Интервал обновления при этом не может быть меньше получаса (180000 ms). Приложение всегда на виду: клиенту можно что-то сообщать не дожидаясь его действий. Минусы: ограниченный набор компонентов, доступных для использования в интерфейсе (из компоновщиков можно использовать только "FrameLayout", "LinearLayout" и "RelativeLayout". Из View: "AnalogClock", "Button", "Chromometer", "ImageButton", "ImageView", "ProgressBar" и "TextView".). Также ограничено время, которое отводится на исполнение запросов. Ну, и, само собой, ограниченный размер "рабочей площади". Также достаточно сложно реализовать обработку событий в виджете: onclickListener на кнопку в виджете "повесить" не получится.
Как регистрировать виджет?
Так же как и всё остальное в нашем приложении, виджет описывается в AndroidManifest.xml. В тег application добавляем структуру:
Как описать параметры виджета?
Вот, например, так:
Как описать интерфейс виджета?
Вот, например, так:
Как обеспечить локализацию виджета?
Как сказано выше: вынести все константы в xml-файл вида
Как описать логику, которая должна выполняться при обновлении виджета?
Вот тут мы, наконец-то, начинаем писать java-код. Для описания логики обновления данных мы должны реализовать класс, имя которого указано в самом первом xml-конфиге, приведённом в этой статье. Это CourceWidget.java:
Что такое сервис и зачем он нам нужен?
В контексте виджета мы не можем обращаться к сетевым ресурсам: время обновления "рабочего стола" должно быть минимальным. Сервис - это фоновый процесс, который Android исполняет в отдельном потоке настолько долго, насколько это нам нужно. Чтобы зарегистрировать сервис в AndroidManifest.xml внутри тега application добавим:
Как сервис получит данные из API?
Очень просто. Отправив get-запрос, мы получаем строку, которая на самом деле представляет собой JSON-структуру. Парсинг JSON выполняем при помощи классов org.json.* которые доступны без подключения всяких сторонних библиотек. Передаём полученную из API строку в конструктор объекта JSONObject. Методами этого объекта мы получаем вложенные объекты и их поля, т.е. значения из ответа (строки 28-30). В случае любой ошибки присваиваем переменным значения по умолчанию.
Как сервис передаёт данные в виджет?
У виджета нельзя обратиться к отдельному элементу View, чтобы изменить его свойства. Можно только заменить всю иерархию компонентов целиком. Для этого создаём объект RemoteViews (строка 21) и "вкладываем" в него полученные из API данные с помощью метода setTextViewText. Потом иерархию с установленными свойствами выдаём в виджет с помощью метода updateAppWidget (строка 13).
И напоследок: как обрабатывать нажатие на виджет?
Тут мы с сожалением вспомним удобные Listener-ы из Activity. В виджетах всё существенно сложнее. Добавим в наш класс CourceWidget константу:
public static String ACTION_WIDGET_RELOAD = "reload";
Затем в нашем сервисе создадим событие:
Intent active = new Intent(context, CourceWidget.class);
active.setAction(ACTION_WIDGET_RELOAD);
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
... и зарегистрируем его (привязав к какому-нибудь id элемента из нашей иерархии RemoteViews):
views.setOnClickPendingIntent(R.id.reload, actionPendingIntent);
В данном случае мы "навешиваем" событие на элемент с id "reload".
Чтобы получить и обработать событие в классе CourceWidget переопределяем метод:
P.S: по многочисленным просьбам даю ссылку на исходники моего проекта, "по мотивам" которого была написана эта статья.
Во-первых, чем виджет отчается от Activity?
Виджет работает в рамках "рабочего стола" нашего смартфона и от этого имеет как плюсы так и минусы. Плюсы: можно настроить обновление средствами системы. Интервал обновления при этом не может быть меньше получаса (180000 ms). Приложение всегда на виду: клиенту можно что-то сообщать не дожидаясь его действий. Минусы: ограниченный набор компонентов, доступных для использования в интерфейсе (из компоновщиков можно использовать только "FrameLayout", "LinearLayout" и "RelativeLayout". Из View: "AnalogClock", "Button", "Chromometer", "ImageButton", "ImageView", "ProgressBar" и "TextView".). Также ограничено время, которое отводится на исполнение запросов. Ну, и, само собой, ограниченный размер "рабочей площади". Также достаточно сложно реализовать обработку событий в виджете: onclickListener на кнопку в виджете "повесить" не получится.
Как регистрировать виджет?
Так же как и всё остальное в нашем приложении, виджет описывается в AndroidManifest.xml. В тег application добавляем структуру:
<receiver android:name=".CourceWidget" android:label="@string/app_label">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_cource" />
</receiver>
Тут .CourceWidget - класс, описывающий виджет, widget_cource - xml файл (фактически: res/xml/widget_cource.xml), описывающий параметры виджета.Как описать параметры виджета?
Вот, например, так:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="72dip"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/cource_message"
/>
Тут android:minWidth и android:minHeight соответственно ширина и высота виджета. Рекомендуется приводить размеры по формуле: число пикселей = (число ячеек * 74) – 2. Одну ячейку на "рабочем столе" занимает одна иконка. Значение android:updatePeriodMillis, как уже говорили, определяет период обновления виджета в миллисекундах. В атрибуте android:initialLayout указываем ссылку на xml файл (res/layout/cource_message.xml) с описанием интерфейса виджета.Как описать интерфейс виджета?
Вот, например, так:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:background="@drawable/widget_bg">
<LinearLayout
android:orientation="vertical"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="fill_parent">
<TextView
android:layout_marginTop="10sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:gravity="center"
android:text="@string/buy" />
<TextView
android:id="@+id/message_b"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:text="@string/loading" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:gravity="center"
android:text="@string/sell" />
<TextView
android:id="@+id/message_s"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:text="@string/loading" />
</LinearLayout>
</LinearLayout>
Это обычный LinearLayout с четырьмя TextView, два из которых содержат строковые константы ("покупка" и "продажа") а другие два заполняются данными, полученными из API. Строковые константы для поддержки "мультиязычности" выносим в файл res/values/strings.xml (значения по умолчанию - английская локализация) и res/values-ru/strings.xml (русская локализация).Как обеспечить локализацию виджета?
Как сказано выше: вынести все константы в xml-файл вида
<resources>
<string name="app_name">Exchange rate of Bitcoin</string>
<string name="app_label">Bitcoin:USD exchange rate</string>
<string name="buy">Buy:</string>
<string name="sell">Sell:</string>
<string name="loading">Loading</string>
<string name="err_connect">Connect error</string>
</resources>
... и обращаться к ним как: @string/loading (вернёт "Loading"). Система сама выберет нужный файл с константами в зависимости от настроек языка в системе. В java-коде обращение к локализованным строковым константам будет выглядеть так: R.string.loading.Как описать логику, которая должна выполняться при обновлении виджета?
Вот тут мы, наконец-то, начинаем писать java-код. Для описания логики обновления данных мы должны реализовать класс, имя которого указано в самом первом xml-конфиге, приведённом в этой статье. Это CourceWidget.java:
Наш класс расширяет AppWidgetProvider, переопределяя его метод onUpdate. В этом методе мы ничего не делаем, кроме одного: мы запускаем сервис UpdateService, описанный тут же, как вложенный класс.
- public class CourceWidget extends AppWidgetProvider {
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- context.startService(new Intent(context, UpdateService.class));
- }
- public static class UpdateService extends Service {
- @Override
- public void onStart(Intent intent, int startId) {
- RemoteViews updateViews = buildUpdate(this);
- AppWidgetManager.getInstance(this).updateAppWidget(new ComponentName(this, CourceWidget.class), updateViews);
- }
- /**
- * Build a widget update
- */
- public RemoteViews buildUpdate(Context context) {
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.cource_message);
- String buy = "";
- String sell = "";
- // get gata from JSON
- try {
- JSONObject resp = new JSONObject(Http.Request("https://mtgox.com/code/data/ticker.php"));
- buy = resp.getJSONObject("ticker").getString("buy");
- sell = resp.getJSONObject("ticker").getString("sell");
- } catch (Exception e) {
- buy = "...";
- sell = "...";
- e.printStackTrace(System.err);
- Toast.makeText(this, R.string.err_connect, Toast.LENGTH_SHORT).show();
- }
- views.setTextViewText(R.id.message_b, "$" + buy);
- views.setTextViewText(R.id.message_s, "$" + sell);
- return views;
- }
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- }
- }
Что такое сервис и зачем он нам нужен?
В контексте виджета мы не можем обращаться к сетевым ресурсам: время обновления "рабочего стола" должно быть минимальным. Сервис - это фоновый процесс, который Android исполняет в отдельном потоке настолько долго, насколько это нам нужно. Чтобы зарегистрировать сервис в AndroidManifest.xml внутри тега application добавим:
<service android:name=".CourceWidget$UpdateService" />
Как сервис получит данные из API?
Очень просто. Отправив get-запрос, мы получаем строку, которая на самом деле представляет собой JSON-структуру. Парсинг JSON выполняем при помощи классов org.json.* которые доступны без подключения всяких сторонних библиотек. Передаём полученную из API строку в конструктор объекта JSONObject. Методами этого объекта мы получаем вложенные объекты и их поля, т.е. значения из ответа (строки 28-30). В случае любой ошибки присваиваем переменным значения по умолчанию.
Как сервис передаёт данные в виджет?
У виджета нельзя обратиться к отдельному элементу View, чтобы изменить его свойства. Можно только заменить всю иерархию компонентов целиком. Для этого создаём объект RemoteViews (строка 21) и "вкладываем" в него полученные из API данные с помощью метода setTextViewText. Потом иерархию с установленными свойствами выдаём в виджет с помощью метода updateAppWidget (строка 13).
И напоследок: как обрабатывать нажатие на виджет?
Тут мы с сожалением вспомним удобные Listener-ы из Activity. В виджетах всё существенно сложнее. Добавим в наш класс CourceWidget константу:
public static String ACTION_WIDGET_RELOAD = "reload";
Затем в нашем сервисе создадим событие:
Intent active = new Intent(context, CourceWidget.class);
active.setAction(ACTION_WIDGET_RELOAD);
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
... и зарегистрируем его (привязав к какому-нибудь id элемента из нашей иерархии RemoteViews):
views.setOnClickPendingIntent(R.id.reload, actionPendingIntent);
В данном случае мы "навешиваем" событие на элемент с id "reload".
Чтобы получить и обработать событие в классе CourceWidget переопределяем метод:
@Override
public void onReceive(Context context, Intent intent) {
// Receive event
final String action = intent.getAction();
if (ACTION_WIDGET_RELOAD.equals(action)) {
context.startService(new Intent(context, UpdateService.class));
}
super.onReceive(context, intent);
}
Тут мы заново стартуем сервис прои нажатии на виджет, тем самым вызывая "внеочередное" обновление данных.P.S: по многочисленным просьбам даю ссылку на исходники моего проекта, "по мотивам" которого была написана эта статья.
Подписаться на:
Сообщения (Atom)
