книги хакеры / журнал хакер / 196_Optimized
.pdf
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|
|
w Click |
to |
ХАКЕР 05 /196/ 2015 |
||||||||
|
|
|
|
|
m |
|||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
Пингер для андроида
2
особого смысла нет. Функция проверки подключения к сети |
|
|
|
выглядит следующим образом: |
|
|
|
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, который содержит информацию о теку- |
Рис. 2. Pref.xml тру- |
||
щем соединении. Если подключение установлено, метод |
дится |
||
isConnected вернет true. |
|
|
|
SQLITE?NO...SHAREDPREFERENCES! |
Рис. 3. GUI такой GUI |
||
|
|
||
Как ты сам понимаешь, нам необходимо где-то хранить спи- |
|
|
|
сок сайтов и их (не)доступность. Сначала я хотел вновь ис- |
|
|
|
пользовать базу данных SQLite, но решил не повторяться |
|
|
|
и рассказать о другой полезной технологии Android — общих |
|
|
|
настройках (Shared Preferences). Общие настройки — до- |
|
|
|
вольно простой механизм, основанный на парах «ключ — |
|
|
|
значение» и предназначенный для сохранения примитивных |
|
|
|
данных приложения (число, строка, булево значение). С точ- |
|
|
|
ки зрения Android настройки хранятся в виде обычного XML- |
DVD.XAKEP.RU |
||
файла внутри приватной директории приложения (data/data/ |
|||
имя_пакета/shared_pref/). |
На сайте ты найдешь |
||
Для наших целей напишем небольшой класс (PingPref) |
|||
для сохранения и чтения данных: |
полный код приложения. |
||
public class PingPref { |
|
|
|
private static fnal String PREF = "pref"; |
|
|
|
private static fnal String pre_ = "site"; |
|
|
|
private static fnal String _url = "_url"; |
|
|
|
private static fnal 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)};
}
}
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
|
|
|
|
||
w Click |
to |
|
|
|
|
|
m |
|||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
В конструкторе класса нужно вызвать метод getSharedPreferences в контексте приложения, указав имя файла общих настроек и режим доступа. Константа
Context.MODE_PRIVATE указывает на то,
что файл настроек будет доступен только внутри приложения (Google настоятельно рекомендует использовать только это значение). Каждый сайт будем хранить в двух ключах: siteN_url (ссылка) и siteN_ status (доступность). В качестве последней используем число: 1 — сайт жив, 0 — сайт ушел (читай: «его ушли»), –1 — статус не определен (например, в случае отсутствия доступа к сети). Сеттеры (put. String, put.Int) и геттеры со значениями по умолчанию (get.String, get.Int) в по-
яснениях не нуждаются. Содержимое файла pref.xml в работе представлено на рис. 2.
GUI
Здесь все просто — несколько полей вво-
да (EditText) да кнопка (Button). Вся эта красота представлена на рис. 3. Обработчик кнопки (см. Main.java) считывает содержимое полей (применяется коллекция ArrayList) и записывает их в файл общих
3настроек, используя метод setData описанного выше класса PingPref:
ArrayList<String> sites = new ArrayList<String>(4);
pf = new PingPref(this);
...
public void bPing_click(View v){
fll ites( ;
for (int i = 0; i < sites.size(); i++)
pf.setData(i+1, sites.get(i), -1);
startservice();
}
public void fll ites(
sites.clear();
sites.add(ed1.getText().toString());
...
}
public void startservice(){
Intent i = new Intent(this,
PingService.class);
this.startService(i);
}
В качестве первоначального статуса доступности сайта, как и условились, устанавливаем –1. В заключение происходит запуск фонового сервиса startService, подробнее о котором поговорим далее.
ФОНОВЫЙСЕРВИС
Начиная с Android 3.0 (версия API 11), любой функционал, связанный с сетевой активностью, должен обязательно выполняться во вторичном потоке. Любая попытка подобной работы в главном (графическом) потоке приложения приведет к выбросу исключения NetworkOnMainThreadException и не-
медленному завершению программы. Это правило относится и к фоновому сервису, так как он тоже фактически выполняется в главном потоке. Как ты уже, наверное, догадался (а если нет — срочно покупай мартовский «Хакер»), мы будем использовать IntentService. Данный фоновый сервис берет на себя всю работу по созданию вторичного потока, позволяя нам сосредоточиться непосредственно на функционале.
Для начала зарегистрируем сервис в манифесте:
<service android:name=".PingService" >
</service>
Основная работа сервиса кипит и бурлит внутри onHandleIntent, текст которого приведен ниже:
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|
|
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|
|
|
|
|
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
|
|
|
|
|
||
|
D |
|
|
|
|
|
|
|
i |
r |
|
|
|
|
|
|
|
|
P |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
|
||
|
|
|
|
|
NOW! |
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
Кодинг |
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
w Click |
to 112 |
|
|
|
m |
|
|
|
|
|
||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
.c |
|
|
|
|
|
|
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|
|
|
|
|
|
|
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
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, " еан |
ing ervice ра отает..."); |
|
|||||||
|
|
|
|
|
|
|
|
|
loadSites(); // тен е |
|
ка айто |
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
boolean isConnected = isConnected(); // |
т |
ое |
нен е? |
||||||
|
|
|
|
|
|
|
|
|
if (!isConnected) { |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
setSitesFail(); // |
та |
|
е |
айто |
а |
- |
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG_LOG, " ое |
нен е от |
т т |
ет "); |
|
|||
|
|
|
|
|
|
|
|
|
} else |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
for (int i = 0; i < sites.size(); i++){ |
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
String site = sites.get(i); |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
if (!site.equalsIgnoreCase("")) { |
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
// айт о т ен? |
а |
ag= |
на е |
ag=0 |
|
||
|
|
|
|
|
|
|
|
|
|
|
int ag = is ite vail(site ? 1 : 0; |
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
pf.setData(i+1 site |
ag ; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(Main.TAG_LOG, site + ": |
ag=" + |
|
ag ; |
|
|||
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
refreshWidget(); // |
но |
е |
ет |
|
|
|
|
//о ае от о енное на ерен е
Intent si = new Intent(this, PingService.class);
PendingIntent pi = PendingIntent.getService(this, 0, si,
PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi); // |
ра ае |
ре |
|
на |
а |
|
|||
/* |
е т ? |
а |
о тор |
н а |
ере |
н |
на е |
|
|
|
ро ерка |
|
ере |
0 |
н */ |
|
|
|
|
long updateFre |
= is onnected ? |
larmManager.IN |
R |
L_FIF N_ |
|||||
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, " еан |
ing ervice |
а ер ен"); |
|
}
Функция loadSites заполняет коллекцию ArrayList<String>
адресами сайтов, сохраненных ранее в общих настройках. Далее происходит проверка на соединение с сетью: если 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; |
WWW |
|
} |
|
|
catch (MalformedURLException e) {} |
|
|
catch (IOException e) {} |
|
Генераторы кнопок: |
return false; |
goo.gl/rai1bl |
|
} |
|
goo.gl/PjjLld |
Так как мы не собираемся в дальнейшем запрашивать |
Гайд по созданию видже- |
|
какие-либо ресурсы с сайта, в заголовке запроса смело ука- |
тов от Google: |
|
зываем лексему соединения setRequestProperty("Connec- |
goo.gl/4DtqUs |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
ХАКЕР 05 /196/ 2015 |
|
|
|
|
|
|||||
w Click |
to |
|
|
|
|
|
m |
|||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
tion", "close"), то есть после ответа сервер разорвет связь. Метод setConnectTimeout устанавливает тайм-аут соединения и подбирается экспериментально (в моем случае пять секунд при соединении 3G вполне хватило). Возвращаемое мето-
дом getResponseCode значение HttpURLConnection.HTTP_OK
определяет положительный вердикт функции. Имей в виду: при использовании мобильного доступа к интернету шанс словить IOException и MalformedURLException весьма вы-
сок. Это происходит потому, что метод isConnected объекта NetworkInfo не всегда оперативно реагирует на изменение состояния сети, и мы можем прийти в isSiteAvail с отсутствующим соединением. Так что, если внезапно все сайты окажутся недоступными, паниковать, конечно, следует, но не сразу.
Функция 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-flter>
<action android:name="android.appwidget
.action.APPWIDGET_UPDATE" />
/intent-flter>
intent-flter>
<action android:name="com.example.pinger.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
|
||
P |
|
|
|
|
|
NOW! |
o |
|
|||
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
Пингер для андроида |
|
w Click |
to ХАКЕР 05 /196/ 2015 |
||||||||||
|
|
|
|
|
|
m |
|
||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
. |
|
|
|
|
|
.c |
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
|
||
|
|
|
|
-xcha |
|
|
|
|
|
FORCE_WIDGET_UPDATE" />
/intent-flter>
<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 = 0 dp * (ко |
е т о |
еек |
0 dp. |
Домашний экран в Android разделен виртуальной сеткой, состоящей из ячеек, размеры которых зависят от физических размеров устройства. Можно сказать, что ярлык приложения на домашнем экране соответствует одной ячейке. Наш виджет будет иметь размеры 4 × 2 или 250 dp × 110 dp (в аппаратно-не- зависимых пикселях). Изменение размеров виджета пользователем мы не планируем, поэтому resizeMode устанавливаем в none.
Атрибут updatePeriodMillis задает минимальный период между обновлениями виджета (в миллисекундах) системой, но нам сейчас он неинтересен, так как виджет мы будем обновлять вручную, как только возникнет такая необходимость. Представь, наш фоновый сервис не запущен, а на рабочем экране висит виджет (типичное состояние устройства после перезагрузки) — Android вызовет процедуру его обновления незамедлительно, а уже потом через updatePeriodMillis миллисекунд. При первом обновлении просто запустим наш сервис, и как только он начнет работать, дальнейшее обновление информации в виджете будет инициировать именно он. Поэтому сейчас смело ставим 86 400 000 (то есть раз в сутки) и двигаемся дальше.
Если ты хочешь, чтобы в меню виджетов вместо иконки приложения красовалась симпатичная картинка (см. рис. 4), добавь ссылку на соответствующий ресурс в формате PNG
ватрибуте previewImage. О том, как быстро и просто получить такое превью, читай врезку.
Атрибут 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"
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
|
|
|
|
|
||
w Click |
to |
|
|
|
|
|
m |
|||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
4 5
Рис. 4. Меню виджетов |
|
android:src="@drawable/green" /> |
|
... /> |
|
Рис. 5. Виджет |
|
<TextView |
на смартфоне, |
|
android:id="@+id/txt1" |
а GitHub-то упал... |
|
android:text="@string/app_name" |
|
.../> |
|
|
|
Наш виджет состоит из нескольких небольших картинок |
|
(im1, im2, im3, im4) типа ImageView и нескольких текстовых |
|
|
меток (txt1, txt2, txt3, txt4) типа TextView. Для компоновки ис- |
|
|
пользуем RelativeLayout, то есть все компоненты визуально |
|
|
выровнены друг относительно друга. Само собой разумеется, |
|
|
картинки и надписи мы будем менять при отрисовке виджета. |
|
|
Поле alpha задает непрозрачность виджета в диапазоне от 0 |
|
|
(прозрачный) до 1 (непрозрачный), а вот background позво- |
|
|
ляет задать картинку для фона с эффектами стекла, бликами |
|
|
и тенями (да, я сторонник скевоморфизма (и при этом Android- |
|
|
юзер. — Прим. ред.)). Для генерации такой текстуры можно |
|
|
воспользоваться онлайн-редакторами, которых в интернете |
|
|
очень много (см. полезные ссылки). Кстати, для правильного |
|
|
масштабирования виджета на разных экранах фон желательно |
|
|
перевести в формат NinePatch, о котором расскажет очеред- |
|
|
ная врезка. |
|
|
|
Итак, от визуальной стороны виджета (см. рис. 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);