Донецкий техникум промышленной автоматики

Пишемо віджет для Android, який відображає наявність сайту

  1. Зміст статті У сучасній матриці IT-технологій кваліфікований адміністратор - персона хоч і не завжди...
  2. Ping? Ні не знаю…
  3. Дозволи
  4. SQLite? No ... Shared Preferences!
  5. GUI
  6. фоновий сервіс
  7. INFO
  8. Віджет
  9. NinePatch vs PNG
  10. вместо Висновки
  11. Прев'ю для віджета
  12. WWW

Зміст статті

У сучасній матриці IT-технологій кваліфікований адміністратор - персона хоч і не завжди видима, зате важлива і дуже зайнята. Відбиваючись від настирливих користувачів, він постійно повинен наглядати за ареалом свого цифрового світу - сайтами. Давай же допоможемо нашому адміну і створимо корисний віджет, уважно стежить за доступністю всіх його ресурсів. І так, погоду цей віджет показувати не буде.

проектуємо віджет

Мета нашого проекту - створити віджет, який періодично пінг зазначені користувачем сайти і виводить відповідну інформацію на домашній екран. Традиційно будемо використовувати редактор коду Eclipse з плагіном ADT.

В андроїд будь віджет являє собою візуальний компонент, який працює в рамках того додатка, в яке він вбудовується (як правило, це домашній екран). Крім того, віджет вміє виводити пристрій з режиму очікування, щоб відобразити на екрані актуальну інформацію. Тому при розробці віджета потрібно звести до мінімуму час його поновлення (можна, звичайно, і знехтувати, але ANR в віджеті - це вже моветон). Цілком логічно напрошується деякий фоновий сервіс, який буде пінгувати сайти і записувати результат для подальшого аналізу. Таким чином, завдання віджета зведеться до вилучення цих даних і виведення інформації у вигляді текстового рядка - посилання на сайт і деякої графіки - доступності сайту (рис. 1). Також нам потрібно простенька форма для введення списку підконтрольних сайтів, тобто GUI. До речі, настійно рекомендую ознайомитися зі статтею в березневому номері «Хакера» «Хакерский Cron на Android», оскільки там докладно розглянута робота фонового сервісу.

Мал. 1. Хакерский віджет

Ping? Ні не знаю…

У Java є чудовий клас InetAddress, що має в своєму череві не менше чудовий метод isReachable (). Цей метод перевіряє доступність ресурсу і приймає як параметр тайм-аут, тобто час, після закінчення якого не відповідає ресурс вважається недоступним:

if (InetAddress.getByName ( "www.xakep.ru"). isReachable (5000)) ... // Сайт доступний

Лаконічно, чи не так? Вся проблема в тому, що цей код прекрасно працює в Windows, але в Android'е завжди повертає false, навіть якщо з додатком дати дозвіл на доступ в інтернет. З незрозумілої причини для відправки ICMP-пакета (Echo-Request) потрібно рут.

Ми ж нічого вимагати не будемо і поступимо таким чином: будемо підключатися до сайту по протоколу HTTP і дивитися на код відповіді. Якщо отримаємо код 200 (HTTP OK), значить, сайт живий і працює, в іншому випадку вважаємо, що щось не так (сайт недоступний).

Дозволи

Так як наша мета - сайти в інтернеті, необхідно отримати відповідні дозволи у користувача в маніфесті проекту (AndroidManifest.xml):

<Uses-permission android: name = "android.permission.INTERNET" /> <uses-permission android: name = "android.permission.ACCESS_NETWORK_STATE" />

Перший рядок запитує дозвіл на доступ в інтернет, тоді як друга дозволяє відстежувати стан підключення до мережі (в термінології Play Market - «перегляд стану мережі») - якщо мережа недоступна, пінгувати що-небудь особливого сенсу немає. Функція перевірки підключення до мережі виглядає наступним чином:

public boolean isConnected () {ConnectivityManager cm = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo (); if (ni! = null && ni.isConnected ()) {return true; } Return false; }

Для доступу до сервісу управління мережевими підключеннями використовується константа Context.CONNECTIVITY_SERVICE, після чого метод getActiveNetworkInfo повертає об'єкт типу NetworkInfo, який містить інформацію про поточний з'єднанні. Після підключення, метод isConnected поверне true.

SQLite? No ... Shared Preferences!

Як ти сам розумієш, нам необхідно десь зберігати список сайтів і їх (не) доступність. Спочатку я хотів знову використовувати базу даних SQLite, але вирішив не повторюватися і розповісти про іншої корисної технології Android - загальних настройках (Shared Preferences). Загальні настройки - досить простий механізм, заснований на парах «ключ - значення» і призначений для збереження примітивних даних програми (число, рядок, логічне значення). З точки зору Android настройки зберігаються в вигляді звичайного XML-файла всередині приватній директорії додатку (data / data / Имя_Пакета / shared_pref /).

Для наших цілей напишемо невеликий клас (PingPref) для збереження і читання даних:

public class PingPref {private static final String PREF = "pref"; private static final String pre_ = "site"; private static final String _url = "_url"; private static final String _status = "_status"; private SharedPreferences mSettings; PingPref (Context context) {mSettings = context.getSharedPreferences (PREF, Context.MODE_PRIVATE); } Public void setData (int num, String url, int status) {Editor editor = mSettings.edit (); // Формуємо рядок виду siteN, де N - порядковий номер сайту String key = pre_ + String.valueOf (num); editor.putString (key + _url, url); // Ключ siteN_url editor.putInt (key + _status, status); // Ключ siteN_status editor.commit (); } Public String [] getData (int num) {String key = pre_ + String.valueOf (num); String url = mSettings.getString (key + _url, ""); int status = mSettings.getInt (key + _status, -1); return new String [] {url, String.valueOf (status)}; }}

У конструкторі класу потрібно викликати метод getSharedPreferences в контексті програми, вказавши ім'я файлу загальних налаштувань і режим доступу. Константа Context.MODE_PRIVATE вказує на те, що файл настройок буде доступний тільки всередині програми (Google настійно рекомендує використовувати тільки це значення). Кожен сайт будемо зберігати в двох ключах: siteN_url (посилання) і siteN_status (доступність). В якості останньої використовуємо число: 1 - сайт живий, 0 - сайт пішов (читай: «його пішли»), -1 - статус не визначений (наприклад, в разі відсутності доступу до мережі). Сетери (put.String, put.Int) і геттери зі значеннями за замовчуванням (get.String, get.Int) в пояснень не потребують. Вміст файлу pref.xml в роботі представлено на рис. 2.

Мал. 2. Pref.xml трудиться

GUI

Тут все просто - кілька полів введення (EditText) та кнопка (Button). Вся ця краса представлена ​​на рис. 3. Оброблювач кнопки (див. Main.java) зчитує вміст полів (застосовується колекція ArrayList) і записує їх у файл загальних параметрів, використовуючи метод setData описаного вище класу PingPref:

ArrayList <String> sites = new ArrayList <String> (4); pf = new PingPref (this); ... public void bPing_click (View v) {fillSites (); for (int i = 0; i <sites.size (); i ++) pf.setData (i + 1, sites.get (i), -1); startservice (); } Public void fillSites () {sites.clear (); sites.add (ed1.getText (). toString ()); ...} public void startservice () {Intent i = new Intent (this, PingService.class); this.startService (i); }

В якості початкового статусу доступності сайту, як і домовилися, встановлюємо -1. На закінчення відбувається запуск фонового сервісу startService, докладніше про який поговоримо далі.

На закінчення відбувається запуск фонового сервісу startService, докладніше про який поговоримо далі

Мал. 3. GUI такий GUI

фоновий сервіс

Починаючи з Android 3.0 (версія API 11), будь-який функціонал, пов'язаний з мережевою активністю, повинен обов'язково виконуватися у вторинному потоці. Будь-яка спроба подібної роботи в головному (графічному) потоці додатки призведе до викиду винятку NetworkOnMainThreadException і негайного завершення програми. Це правило відноситься і до фонового сервісу, так як він теж фактично виконується в головному потоці. Як ти вже, напевно, здогадався (а якщо немає - терміново купуй березневий «Хакер»), ми будемо використовувати IntentService. Даний фоновий сервіс бере на себе всю роботу по створенню вторинного потоку, дозволяючи нам зосередитися безпосередньо на функціоналі.

Для початку зареєструємо сервіс в маніфесті:

<Service android: name = ". PingService"> </ service>

Основна робота сервісу кипить і вирує всередині onHandleIntent, текст якого наведено нижче (отладочная друк присутній):

private ArrayList <String> sites = new ArrayList <String> (4); private PingPref pf = new PingPref (this); private AlarmManager am = (AlarmManager) getSystemService (Context.ALARM_SERVICE); ... @Override protected void onHandleIntent (Intent intent) {Log.d (TAG_LOG, "Сеанс PingService працює ..."); loadSites (); // Читання списку сайтів boolean isConnected = isConnected (); // Є з'єднання? if (! isConnected) {setSitesFail (); // Ставимо для всіх сайтів прапор -1 Log.d (TAG_LOG, "З'єднання відсутня!"); } Else for (int i = 0; i <sites.size (); i ++) {String site = sites.get (i); if (! site.equalsIgnoreCase ( "")) {// Сайт доступний? Так - flag = 1, інакше flag = 0 int flag = isSiteAvail (site)? 1: 0; pf.setData (i + 1, site, flag); Log.d (Main.TAG_LOG, site + ": flag =" + flag); }} RefreshWidget (); // Оновлюємо віджет // Створюємо відкладене намір Intent si = new Intent (this, PingService.class); PendingIntent pi = PendingIntent.getService (this, 0, si, PendingIntent.FLAG_UPDATE_CURRENT); am.cancel (pi); // Скидаємо попередню сигналізацію // Зв'язок є? Так - повтор пинга через 15 хв, інакше - перевірка зв'язку через 30 хв long updateFreq = isConnected? AlarmManager.INTERVAL_FIFTEEN_MINUTES: AlarmManager.INTERVAL_HALF_HOUR; // Визначаємо час наступного запуску сервісу = поточний + інтервал long timeToRefresh = SystemClock.elapsedRealtime () + updateFreq; // Встановлюємо сигналізацію am.setInexactRepeating (AlarmManager.ELAPSED_REALTIME, timeToRefresh, updateFreq, pi); Log.d (Main.TAG_LOG, "Наступний сеанс приблизно через" + updateFreq / 60/1000 + "хв."); Log.d (Main.TAG_LOG, "Сеанс PingService завершений"); }

Функція loadSites заповнює колекцію ArrayList адресами сайтів, збережених раніше в загальних налаштуваннях. Далі відбувається перевірка на з'єднання з мережею: якщо isConnected повертає false - для всіх сайтів встановлюємо прапор -1 (setSitesFail), в іншому випадку запускаємо цикл опитування всіх ресурсів (isSiteAvail) із записом результатів (setData). Для активної роботи зі всесвітньою павутиною по протоколу HTTP в Java передбачений спеціальний клас HttpURLConnection, що використовує об'єкт-посилання (URL) для вказівки адреси сайту:

private boolean isSiteAvail (String site) {try {URL url = new URL (site); HttpURLConnection urlc = (HttpURLConnection) url.openConnection (); urlc.setRequestProperty ( "Connection", "close"); urlc.setConnectTimeout (5000); urlc.connect (); int Code = urlc.getResponseCode (); if (Code == HttpURLConnection.HTTP_OK) return true; } Catch (MalformedURLException e) {} catch (IOException e) {} return false; }

Так як ми не збираємося надалі запитувати будь-яких ресурсів з сайту, в заголовку запиту сміливо вказуємо лексему сполуки setRequestProperty ( "Connection", "close"), тобто після відповіді сервер розірве зв'язок. Метод setConnectTimeout встановлює тайм-аут з'єднання і підбирається експериментально (в моєму випадку п'ять секунд при з'єднанні 3G цілком вистачило). Що повертається методом getResponseCode значення HttpURLConnection.HTTP_OK визначає позитивний вердикт функції. Май на увазі: при використанні мобільного доступу до інтернету шанс зловити IOException і MalformedURLException досить високий. Це відбувається тому, що метод isConnected об'єкта NetworkInfo не завжди оперативно реагує на зміну стану мережі, і ми можемо прийти в isSiteAvail з відсутнім з'єднанням. Так що, якщо раптово все сайти виявляться недоступними, панікувати, звичайно, слід, але не відразу.

INFO

Об'єкт HttpURLConnection обробляє тільки ті посилання, які починаються з http: //, тобто протокол потрібно вказувати явно.

Функція refreshWidget ініціює оновлення віджета за допомогою трансляції (передачі) унікального широковещательного наміри FORCE_WIDGET_UPDATE, яке наш віджет буде відловлювати, так як він є широкомовною приймачем. Термін «широкомовність» означає глобальний характер обробки намірів - будь-яке інше додаток може обробити наш намір, так само як і ми можемо підписатися на обробку чужого. Щоб не було плутанини, наміри повинні бути унікальними. До речі, якщо наприклад, потрібно відкрити інтернет-посилання (один намір), а в системі встановлено кілька браузерів (кілька широкомовних приймачів) - з'явиться вікно з вибором віддає перевагу. У наступному розділі ми розглянемо цей механізм більш детально.

private void refreshWidget () {Intent i = new Intent (PingWidget.FORCE_WIDGET_UPDATE); sendBroadcast (i); }

Ближче до кінця створюємо вже знайоме тобі відкладене намір на повторний запуск сервісу через не менше знайомий менеджер сигналізацій. Тільки замість методу set будемо використовувати setRepeating, а точніше - setInexactRepeating. Останній допомагає в деякій мірі зменшити енерговитрати, збираючи для виконання близькі за часом сигналізації. Тому замість точного інтервалу ми передаємо константу AlarmManager.INTERVAL FIFTEEN_MINUTES для опитування сайтів приблизно через кожні 15 хв і AlarmManager.INTERVAL_HALF_HOUR (~ 30 хв) в разі відсутності з'єднання для нової спроби. Можливо, ти захочеш вказати інші константи об'єкта AlarmManager: INTERVAL_HOUR (годину), INTERVAL_HALF_DAY (12 год), INTERVAL_DAY (раз на добу). Зауважу, що ці інтервали очень_ умовні, і при необхідності дотримання більш точного розкладу слід використовувати метод setRepeating, але, як уже зазначалося, він більш ненажерливий. До слова, будити пристрій ми не станемо - використовуємо AlarmManager.ELAPSED_REALTIME, так як оновлення інформації для віджета при вимкненому екрані не тільки не потрібно, але і, ймовірно, викличе докірливий погляд колег з рубрики X-Mobile.

Добравшись до середини статті, ми провели всю підготовчу роботу і тепер можемо з чистою совістю приступити до головної теми нашого дослідження - створення віджета.

Віджет

В Android віджет реалізується у вигляді широковещательного приймача, що реагує на деякі події (строго кажучи - наміри (Intent)), для наповнення візуальної розмітки актуальними даними по таймеру, за допомогою сервісу, при натисканні і так далі. Розробка віджета починається з реєстрації його класу (PingWidget) в маніфесті проекту:

<Receiver android: name = ". PingWidget" android: label = "@ string / app_name"> <intent-filter> <action android: name = "android.appwidget.action.APPWIDGET_UPDATE" /> </ intent-filter> < intent-filter> <action android: name = "com.example.pinger.FORCE_WIDGET_UPDATE" /> </ intent-filter> <meta-data android: name = "android.appwidget.provider" android: resource = "@ xml / widget_provider "/> </ receiver>

Тут тег intent-filter містить мінімум одне стандартне дію - android.appwidget.action.APPWIDGET_UPDATE, що використовується для оновлення вмісту віджета (ще є DELETED, ENABLED і DISABLED, але вони не є обов'язковими). Так як оновлювати віджет ми будемо, по-перше, самостійно, по-друге, в різні моменти часу, додамо ще одну дію - com.example.pinger.FORCE_WIDGET_UPDATE для нашої задачі. Крім того, нам буде потрібно окремий XML-файл, що описує налаштування віджета (файл res \ xml \ widget_provider.xml):

<Appwidget-provider xmlns: android = "http://schemas.android.com/apk/res/android" android: initialLayout = "@ layout / widget" android: minHeight = "110dp" android: minWidth = "250dp" android : resizeMode = "none" android: label = "@ string / app_name" android: updatePeriodMillis = "86400000" android: previewImage = "@ drawable / widget_preview" />

Атрибути minHeight і minWidth визначають мінімально допустимі висоту і ширину віджету. Для розрахунку цих значень застосовується формула

min = 70 dp * (кількість осередків) - 30 dp.

Домашній екран в Android розділений віртуальної сіткою, що складається з осередків, розміри яких залежать від фізичних розмірів пристрою. Можна сказати, що ярлик програми на домашньому екрані відповідає одному осередку. Наш віджет буде мати розміри 4 х 2 або 250 dp x 110 dp (в апаратно-незалежних пікселях). Зміна розмірів віджету користувачем ми не плануємо, тому resizeMode встановлюємо в none.

Атрибут updatePeriodMillis задає мінімальний період між оновленнями віджета (в мілісекундах) системою, але нам зараз він нецікавий, так як віджет ми будемо оновлювати вручну, як тільки виникне така необхідність. Уяви, наш фоновий сервіс не запущений, а на екрані висить віджет (типовий стан пристрою після перезавантаження) - Android викличе процедуру його поновлення негайно, а вже потім через updatePeriodMillis мілісекунд. При першому оновленні просто запустимо наш сервіс, і як тільки він почне працювати, подальше оновлення інформації в віджеті буде ініціювати саме він. Тому зараз сміливо ставимо 86 400 000 (тобто раз на добу) і рухаємося далі.

Якщо ти хочеш, щоб у меню віджетів замість іконки програми красувалася симпатична картинка (див. Рис. 4), додай посилання на відповідний ресурс в форматі PNG в атрибуті previewImage. Про те, як швидко і просто отримати таке превью, читай врізку.

Про те, як швидко і просто отримати таке превью, читай врізку

Мал. 4. Меню віджетів

Атрибут initialLayout дозволяє вказати розмітку віджета в форматі XML. Так, ти не помилився, розмітка віджета багато в чому нагадує розмітку активності або діалогового вікна - ті ж мітки, кнопки, картинки, менеджери компонування та інше. Фрагмент розмітки нашого віджета представлений нижче (widget.xml):

<RelativeLayout xmlns: android = "http://schemas.android.com/apk/res/android" android: id = "@ + id / widget" ... android: alpha = "0.90" android: background = "@ drawable / widget "<ImageView android: id =" @ + id / im1 "android: src =" @ drawable / green "/> ... /> <TextView android: id =" @ + id / txt1 "android: text = "@ string / app_name" ... />

Наш віджет складається з невеликих картинок (im1, im2, im3, im4) типу ImageView і текстових міток (txt1, txt2, txt3, txt4) типу TextView. Для компонування використовуємо RelativeLayout, тобто всі компоненти візуально вирівняні один відносно одного. Зрозуміло, картинки і написи ми будемо міняти при відображенні віджета. Поле alpha задає непрозорість віджета в діапазоні від 0 (прозорий) до 1 (непрозорий), а ось background дозволяє задати картинку для фону з ефектами скла, відблисками і тінями (так, я прихильник скевоморфізм). Для генерації такої текстури можна скористатися онлайн-редакторами, яких в інтернеті дуже багато (див. Корисні посилання). До речі, для правильного масштабування віджета на різних екранах фон бажано перевести в формат NinePatch, про який розповість врізка.

Мал. 5. Віджет на смартфоні, а GitHub-то впав ...

Отже, від візуальної сторони віджета (див. Рис. 5) плавно переходимо до логіки його роботи (клас PingWidget.java):

public class PingWidget extends AppWidgetProvider {public static String FORCE_WIDGET_UPDATE = "com.example.pinger.FORCE_WIDGET_UPDATE"; @Override public void onUpdate (Context context, AppWidgetManager appWidgetManager, int [] appWidgetIds) {startService (context); } @Override public void onReceive (Context context, Intent intent) {super.onReceive (context, intent); if (FORCE_WIDGET_UPDATE.equals (intent.getAction ())) updateWidget (context); } Private void updateWidget (Context context) {AppWidgetManager appWidgetManager = AppWidgetManager.getInstance (context); ComponentName thisWidget = new ComponentName (context, PingWidget.class); int [] appWidgetIds = appWidgetManager.getAppWidgetIds (thisWidget); drawWidget (context, appWidgetManager, appWidgetIds); } ...}

NinePatch vs PNG

Зображення формату NinePatch (або розтягуються) - це файли формату PNG, в яких області, призначені для масштабування, позначені явно. Android SDK включає в себе візуальний редактор draw9patch, який знаходиться за адресою SDK / Tools / draw9patch.bat.

На рис. 7 ти можеш бачити область розтягування нашого віджета (Patches), позначену фіолетовим кольором. Який би розмір віджету не був, логотип твого улюбленого журналу, а також закруглені куточки спотворюватися не будуть. Ця область задається за допомогою лінійок зліва і зверху від зображення. Зверни увагу на рамку товщиною в один піксель з чорними смугами - саме ця інформація буде додана до вихідного зображення.

Мал. 7. Розмір - це головне!

На рис. 8 темним кольори показана область для контенту (Content). Тут ми бачим, что всі наші картинки и текстові Мітки будут мати Невеликий відступ від рамки віджета. Цю красу визначають лінійки справа і знизу від зображення.

Мал. 8. Область для контенту не поступається

Результат роботи зберігається в форматі PNG c додаванням цифри 9 перед розширенням файлу (наприклад, widget.9.png). При вказівці посилання на графічний ресурс вказувати дев'ятку не потрібно (тобто android: background = "@ drawable / widget").

Клас AppWidgetProvider, що є широкомовною приймачем, надає нам зручні обробники життєвого циклу віджета: onUpdate і onReceive (є й інші - onDeleted, onDisabled, onEnabled, але ми їх не розглядаємо). Перший викликається при оновленні інтерфейсу міні-програми з періодичністю updatePeriodMillis (див. Вище), як і домовилися - просто запускаємо сервіс (як варіант, можна використовувати вже отримані, але, можливо, неактуальні дані - все залежить від завдання). Другий, onReceive, спрацьовує при отриманні певної дії - FORCE_WIDGET_UPDATE, зареєстрованого нами раніше в маніфесті проекту. Саме цей код і відповідає за маніпуляції з картинками і текстовими полями в UI віджета. Функція updateWidget спочатку запитує об'єкт класу AppWidgetManager, який застосовується для поновлення віджетів і надає інформацію про них. Зокрема, нас цікавлять ідентифікатори всіх віджетів (масив appWidgetIds), адже їх може бути кілька і оновити потрібно кожен з них:

private void drawWidget (Context context, AppWidgetManager appWidgetManager, int [] appWidgetIds) {final int N = appWidgetIds.length; for (int i = 0; i <N; i ++) {int appWidgetId = appWidgetIds [i]; RemoteViews views = new RemoteViews (context.getPackageName (), R.layout.widget); // Оновлюємо перший рядок String [] data = pf.getData (1); views.setTextViewText (R.id.txt1, data [0]); views.setImageViewResource (R.id.im1, getPicture (data [1])); ... appWidgetManager.updateAppWidget (appWidgetId, views); }} Private int getPicture (String type) {switch (type) {case "1": return R.drawable.green; case "0": return R.drawable.red; default: return R.drawable.gray; }}

В єдиному циклі функції drawWidget відбувається ітерація по всім працюючим в даний момент віджетів. Клас RemoteViews використовується в якості компонента для доступу до розмітки, розміщеної всередині процесу іншої програми (мені одному це нагадує Inject?). Якби ми працювали з активністю або фрагментом, то могли б використовувати цілком звичайне findViewById. Але наш віджет працює в рамках домашнього екрану, тому для отримання доступу до розмітки необхідно в конструкторі RemoteViews вказати назву пакета (context.getPackageName ()) і ресурс з розміткою (R.layout.widget). Використовуючи views.setTextViewText і views.setImageViewResource, змінюємо напис і картинку (допоміжна функція getPicture повертає посилання на відповідний ресурс). Як тільки ми внесли правки по всіх рядках, фіксуємо їх в віджеті, викликаючи updateAppWidget.

вместо Висновки

Ми виконали велику роботу, і наш віджет працює як годинник - адмін напевно буде задоволений. Але ось одне питання так і залишився заданим: як зупинити періодичні запуски сервісу, якщо він більше не потрібен? Поспішаю тебе порадувати, в коді програми (заглянь на dvd.xakep.ru або мій сайт) зупинка вже реалізована за допомогою спеціального прапора setStopServiceFlag, що спрацьовує в момент натискання кнопки «Назад» у головній активності додатки (на кнопку «Додому» не поширюється). Обов'язково вивчи це питання - це і буде твоїм домашнім завданням.

Прев'ю для віджета

В Play Market живе мегакорисних додаток для розробників віджетів - Widget Preview, яке дозволяє вбудувати в себе будь-який зареєстрований в системі віджет, після чого робить його знімок. Результат можна зберегти в файл або відправити поштою.

Результат можна зберегти в файл або відправити поштою

Мал. 6. Так якого кольору плаття?

WWW

Генератори кнопок:

Гайд по створенню віджетів від Google: http://goo.gl/4DtqUs


Ping?
SQLite?
D (TAG_LOG, "Сеанс PingService працює ..."); loadSites (); // Читання списку сайтів boolean isConnected = isConnected (); // Є з'єднання?
Get (i); if (! site.equalsIgnoreCase ( "")) {// Сайт доступний?
Так - flag = 1, інакше flag = 0 int flag = isSiteAvail (site)?
Cancel (pi); // Скидаємо попередню сигналізацію // Зв'язок є?
Так - повтор пинга через 15 хв, інакше - перевірка зв'язку через 30 хв long updateFreq = isConnected?
Мені одному це нагадує Inject?
Але ось одне питання так і залишився заданим: як зупинити періодичні запуски сервісу, якщо він більше не потрібен?
6. Так якого кольору плаття?