Статьи

Android Full App, часть 8. Создание виджета приложения для главного экрана

Это восьмая часть серии «Android Full Tutorial» . Полное приложение предназначено, чтобы обеспечить легкий способ выполнять поиск фильмов / актеров через Интернет. В первой части серии ( «Основной интерфейс действий» ) мы создали проект Eclipse и настроили базовый интерфейс для основной деятельности приложения. Во второй части ( «Использование HTTP API» ) мы использовали клиентскую библиотеку Apache HTTP для использования внешнего HTTP API и интеграции возможностей поиска API в наше приложение. В третьей части ( «Анализ XML-ответа» ) мы увидели, как анализировать XML-ответ, используя встроенные в Android возможности синтаксического анализа XML. В четвертой части ( «Выполнение запроса API асинхронно из основного действия» ) мы связали вместе службы HTTP-ретривера и XML-парсера, чтобы выполнить запрос поиска API из основного действия нашего приложения. Запрос был выполнен асинхронно в фоновом потоке, чтобы избежать блокировки основного потока пользовательского интерфейса. В пятой части ( «Запуск новых действий с намерениями» ) мы увидели, как запустить новое действие и как перенести данные из одного действия в другое. В шестой части ( «Настроенное представление списка для представления данных» ) мы создали настраиваемое представление списка, чтобы обеспечить лучшее визуальное представление данных. В седьмом ( «Использование меню параметров и настраиваемых диалогов для взаимодействия с пользователем» ) мы создали меню параметров и настраиваемые диалоговые окна, чтобы облегчить взаимодействие с пользователем. В этой части мы собираемся создать AppWidget для домашнего экрана пользователя и через него предоставить обновления, связанные с приложением.

Начиная с версии 1.5, Android SDK включает в себя платформу AppWidget — платформу, которая позволяет разработчикам писать «виджеты», которые люди могут перетаскивать на домашний экран и взаимодействовать с ними. Использование виджетов очень удобно, поскольку позволяет пользователю добавлять свои любимые приложения на домашний экран и взаимодействовать с ними быстро и без необходимости запуска всего приложения. Прежде чем продолжить, предлагаю взглянуть на статью «Представление виджетов домашнего экрана и каркас AppWidget», опубликованную в официальном блоге разработчиков Android . Пример виджета показан на следующем изображении. Это тот, который построен для целей статьи, и он дает обновления «Слово дня». Исходный код для этого можно найти здесь .

Для нашего приложения мы собираемся создать виджет, который периодически предоставляет обновления последнего фильма, созданного в базе данных TMDb . Как я уже упоминал ранее, мы использовали очень классный TMDb API для поиска фильмов / актеров и различных других связанных функций.

Первый шаг к созданию виджета приложения — предоставить для него объявление, а также некоторые метаданные XML, которые его описывают. Это делается путем добавления специального файла XML в папку «res / xml» нашего проекта. Через этот файл мы предоставляем информацию о размерах виджета, интервале его обновления и т. Д. Соответствующий класс называется AppWidgetProviderInfo, а его поля соответствуют полям в теге XML, которые мы увидим ниже. Для высоты и ширины виджета Google рекомендует использовать специальную формулу:

Минимальный размер в dip = (Количество ячеек * 74dip) — 2dip

Поскольку мы хотим, чтобы наш виджет занимал 2 ячейки по ширине и 1 ячейку по высоте, размеры в dip будут 146 и 72 соответственно. Интервал обновления определяется в миллисекундах, и в демонстрационных целях мы используем только 10 секунд (10000 миллисекунд). Тем не менее, обратите внимание, что короткие интервалы не рекомендуется. В частности, обновление чаще, чем каждый час, может быстро израсходовать заряд батареи и пропускную способность

ОБНОВЛЕНИЕ: по этой самой причине, чтобы избежать разрядки батареи, Google изменил свой API после версии 1.6, так что частота обновления не может быть менее 30 минут. Если вы хотите добиться более частых обновлений, необходимо использовать механизм тревоги, чтобы отправлять намеренную трансляцию вашему получателю виджета. Вы можете найти пример такого подхода в посте « Виджет приложения с помощью Alarm Manager ».

И последнее, но не менее важное: необходимо использовать макет, чтобы мы могли обрабатывать способ отображения виджета. Это будет сделано путем ссылки на другой файл XML с именем «widget_layout.xml».

Файл объявления виджета XML называется «movie_search_widget.xml» и имеет следующий вид:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="146dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="10000"
    android:initialLayout="@layout/widget_layout"
/>

Посмотрим теперь, как выглядит его описание макета («/res/layout/widget_layout.xml»):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    style="@style/WidgetBackground">
    
    <TextView
        android:id="@+id/app_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dip"
        android:layout_marginBottom="1dip"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:ellipsize="end"
        style="@style/Text.WordTitle" />
    
    <TextView
        android:id="@+id/movie_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/app_name"
        android:paddingRight="5dip"
        android:paddingBottom="4dip"
        android:includeFontPadding="false"
        android:lineSpacingMultiplier="0.9"
        android:maxLines="4"
        android:fadingEdge="vertical"
        style="@style/Text.Movie" />
    
</RelativeLayout>

Макет довольно прост. Мы используем RelativeLayout , где позиции детей могут быть описаны по отношению друг к другу или к родителю, а также пару TextView . Обратите внимание, что используемый стиль фактически определен в другом файле («res / values ​​/ styles.xml»), чтобы собрать все атрибуты, связанные со стилем, в одном месте. Декларации стиля следующие:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="WidgetBackground">
        <item name="android:background">@drawable/widget_bg</item>
    </style>
    
    <style name="Text">
    </style>
    
    <style name="Text.Title">
        <item name="android:textSize">16sp</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textColor">@android:color/black</item>
    </style>
    
    <style name="Text.Movie">
        <item name="android:textSize">13sp</item>
        <item name="android:textColor">@android:color/black</item>
    </style>
     
</resources>

Следующим шагом является регистрация специального BroadcastReceiver в файле AndroidManifest.xml . Этот получатель будет обрабатывать любые обновления виджетов приложения, которые будут запущены системой, когда придет время. Давайте посмотрим соответствующий фрагмент файла манифеста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<application android:icon="@drawable/icon" android:label="@string/app_name">
...    
        <!-- Broadcast Receiver that will process AppWidget updates -->
        <receiver android:name=".widget.MovieSearchWidget" android:label="@string/widget_name">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider" android:resource="@xml/movie_search_widget" />
        </receiver>
        
        <!-- Service to perform web API queries -->
        <service android:name=".widget.MovieSearchWidget$UpdateService" />       
...
</application>

Класс получателя — это com.javacodegeeks.android.apps.moviesearchapp.widget.MovieSearchWidget, который фактически использует внутренний класс обслуживания («UpdateService») для выполнения обновлений. Обратите внимание, что действия, выполняемые этим получателем, имеют вид APPWIDGET_UPDATE , который отправляется, когда наступает время обновления AppWidget. В разделе метаданных определяется местоположение файла XML объявления виджета.

Теперь давайте напишем класс, который будет получать запросы AppWidget и предоставлять обновления приложения. Для этого мы расширяем класс AppWidgetProvider , который, в свою очередь, расширяет класс BroadcastReceiver . На самом деле AppWidgetProvider — это всего лишь удобный класс, помогающий реализовать провайдера AppWidget, и все его функциональные возможности могут быть достигнуты обычным BroadcastReceiver.

Существует пять основных методов, которые могут быть переопределены для обработки различных запросов действий:

  • onEnabled : вызывается при создании первого виджета приложения. Глобальная инициализация должна происходить здесь, если таковая имеется.
  • onDisabled : вызывается при удалении последнего виджета приложения, обработанного этим определением. Глобальная очистка должна проходить здесь, если таковая имеется.
  • onUpdate : вызывается, когда виджету приложения необходимо обновить свой вид, что может быть, когда пользователь впервые создает виджет. Это наиболее часто используемый метод.
  • onDeleted : вызывается при удалении одного или нескольких экземпляров этого виджета приложения. Очистка для конкретных случаев должна происходить здесь.
  • onReceive : обрабатывает действия BroadcastReceiver и отправляет запросы вышеуказанным методам.

Вот наша реализация:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.javacodegeeks.android.apps.moviesearchapp.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.widget.RemoteViews;
import com.javacodegeeks.android.apps.moviesearchapp.R;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.MovieSeeker;
public class MovieSearchWidget extends AppWidgetProvider {
   
   private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
   
   @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // To prevent any ANR timeouts, we perform the update in a service
        context.startService(new Intent(context, UpdateService.class));
    }
    public static class UpdateService extends Service {
       
       private MovieSeeker movieSeeker = new MovieSeeker();
       
        @Override
        public void onStart(Intent intent, int startId) {
            // Build the widget update for today
            RemoteViews updateViews = buildUpdate(this);
            // Push update for this widget to the home screen
            ComponentName thisWidget = new ComponentName(this, MovieSearchWidget.class);
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            manager.updateAppWidget(thisWidget, updateViews);
        }
        
      public RemoteViews buildUpdate(Context context) {
           
         Movie movie = movieSeeker.findLatest();              
         
         String imdbUrl = IMDB_BASE_URL + movie.imdbId;
         // Build an update that holds the updated widget contents
         RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
         updateViews.setTextViewText(R.id.app_name, getString(R.string.app_name));
         updateViews.setTextViewText(R.id.movie_name, movie.name);
         
         Intent intent = new Intent();
         intent.setAction("android.intent.action.VIEW");
         intent.addCategory("android.intent.category.BROWSABLE");
         intent.setData(Uri.parse(imdbUrl));
         
         PendingIntent pendingIntent =
            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
           
         updateViews.setOnClickPendingIntent(R.id.movie_name, pendingIntent);
         
         return updateViews;
      }
        @Override
        public IBinder onBind(Intent intent) {
            // We don't need to bind to this service
            return null;
        }
        
    }
}

Как уже упоминалось, мы переопределяем метод onUpdate, а внутри него просто запускаем новый сервис , который фактически будет предоставлять обновления. Это делается для выполнения трудоемких действий (открытие сетевых подключений, загрузка данных и т. Д.) В другом потоке, что позволяет избежать тайм-аутов ANR . В нашем сервисе мы реализуем метод onStart . Обратите внимание, что этот метод устарел, и следует использовать onStartCommand . Поскольку я использую Android 1.5 SDK, я собираюсь придерживаться метода onStart .

Для нашего приложения я использую новый метод из класса MovieSeeker , который был усовершенствован для того, чтобы также предоставлять последний фильм (мы увидим это позже). Затем мы создаем объект RemoteViews, который будет описывать представление, которое будет отображаться в другом процессе (то есть на домашнем экране). В его конструкторе мы предоставляем имя пакета (взятое соответствующим контекстом ) и идентификатор макета, который использует представление (в нашем случае «widget_layout»). Обратите внимание, что мы не можем напрямую манипулировать какими-либо дочерними представлениями макета, например, устанавливая текст представления или регистрируя слушателей. По этой причине мы собираемся использовать метод setTextViewText для предоставления текста TextView и метод setOnClickPendingIntent для предоставления обработчика для событий щелчка.

Мы хотим запустить браузер и указывать на страницу IMDB фильма каждый раз, когда пользователь нажимает на виджет. Чтобы достичь этого, мы сначала создаем Намерение действия ACTION_VIEW и URL страницы в качестве данных. Это намерение затем инкапсулируется внутри PendingIntent, и именно это ожидающее намерение используется в качестве обработчика щелчка.

Когда объект RemoteViews готов, мы создаем объект ComponentName, который функционирует как идентификатор нашего BroadcastReceiver. Затем мы берем ссылку на AppWidgetManager и используем его для отправки обновлений в наш виджет приложения на главном экране с помощью метода updateAppWidget .

Перед запуском приложения, давайте посмотрим, как получить последний фильм. Напомним, что класс MovieSeeker используется для поиска фильмов. Мы добавили метод с именем «findLatest» в этот класс, который использует вызов API Movie.getLatest API TMDb. Поскольку ответ на этот вызов не соответствует формату существующих ответов на поиск фильмов , нам необходимо создать дополнительный обработчик XML, который называется «SingleMovieHandler». Вы можете найти все изменения в проекте, доступные для скачивания в конце этой статьи.

Теперь запустите соответствующую конфигурацию проекта Eclipse. Эмулятор, вероятно, доставит вас прямо к самому приложению. Скорее, нажмите кнопку назад, чтобы вернуться на домашний экран. Проверьте статью о том, как добавлять и удалять виджеты приложения . Для эмулятора, вы в основном должны нажать на пустую область и не отпускать кнопку мыши. Затем появится следующее диалоговое окно:

Выберите «Виджеты», а затем выберите «MovieSearchAppWidget»:

Когда виджет будет вставлен на домашний экран, будет запущен соответствующий метод обновления и будет загружен последний фильм. Вот так будет выглядеть домашний экран:

Наконец, если дважды щелкнуть название фильма, отложенное намерение будет запущено, и браузер Android запустится, направленный на страницу IMDB фильмов (обратите внимание, что некоторые фильмы не будут иметь связанных идентификатора IMDB и страницы. В этом случае браузер будет перенести вас на испорченную страницу IMDB).

Вот и все. AppWidget для вашего приложения.

Статьи по Теме :