Статьи

Основы Android: загрузка данных с помощью служб

Содержание учебника все еще неназванного приложения «TutList», которое мы создавали вместе, становится устаревшим. Данные были такими же уже более месяца. Пришло время вдохнуть жизнь в приложение, предоставив ему средство для чтения свежих данных учебника Mobiletuts на лету.

В нашем нынешнем виде наше приложение читает список названий учебных пособий и ссылок из базы данных. По сути, это правильный способ разработки приложения. Однако нам нужно добавить компонент, который получает новый контент с веб-сайта Mobiletuts и сохраняет новые заголовки и ссылки в базе данных приложения. Этот компонент загрузит необработанный RSS-канал с сайта, проанализирует его по названию и ссылке, а затем сохранит эти данные в базе данных. База данных будет изменена, чтобы запретить повторяющиеся ссылки. Наконец, опция обновления будет помещена в фрагмент списка, чтобы пользователь мог вручную принудительно обновить базу данных.
Как и в других уроках этой серии, темп будет быстрее, чем в некоторых наших уроках для начинающих; вам, возможно, придется просмотреть некоторые другие учебники по Android на этом сайте или даже в справочнике по Android SDK, если вы не знакомы с какими-либо базовыми концепциями и классами Android, обсуждаемыми в этом учебном пособии. Окончательный пример кода, прилагаемый к этому учебному пособию, доступен для загрузки с открытым исходным кодом с хостинга кодов Google .

Этот учебник предполагает, что вы начнете с того места, где остановился наш последний учебник «Основы Android: правильная загрузка данных» . Вы можете скачать этот код и работать оттуда, или вы можете скачать код для этого урока и следовать. В любом случае, будьте готовы, загрузив один или другой проект и импортировав его в Eclipse.

Один из способов справиться с фоновой обработкой, такой как загрузка и анализ учебного канала Mobiletuts, — реализовать для этой цели службу Android. Сервис позволяет вам компоновать задачу загрузки из любого конкретного действия или фрагмента. Позже это позволит ему легко выполнить операцию, не запуская ее вообще.

Итак, давайте создадим сервис. Начните с создания нового пакета, как пакет данных. Мы назвали наш com.mamlambo.tutorial.tutlist.service. В этом пакете добавьте новый класс с именем TutListDownloaderService и попросите его расширить класс Service (android.app.Service). Поскольку служба на самом деле не запускается в новом потоке или процессе с хоста (какой бы другой процесс или задача ее не запускала, в нашем случае это Activity), нам также необходимо создать класс, чтобы гарантировать, что фоновая работа выполняется асинхронно. Для этой цели мы могли бы использовать дизайн Thread / Handler или просто использовать встроенный класс AsyncTask, поставляемый с Android SDK, чтобы упростить эту задачу.

Мы будем использовать AsyncTask для нашего приложения. Создайте закрытый внутренний класс, который расширяет AsyncTask для использования только классом Service. Мы вскоре доберемся до конкретных деталей реализации, но вот пустая реализация класса Service с внутренним AsyncTask:

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
public class TutListDownloaderService extends Service {
 
    private static final String DEBUG_TAG = «TutListDownloaderService»;
    private DownloaderTask tutorialDownloader;
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TBD
        return Service.START_FLAG_REDELIVERY;
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    private class DownloaderTask extends AsyncTask<URL, Void, Boolean> {
 
        private static final String DEBUG_TAG = «TutListDownloaderService$DownloaderTask»;
 
        @Override
        protected Boolean doInBackground(URL… params) {
            // TBD
        }
 
        private boolean xmlParse(URL downloadPath) {
            // TBD
        }
 
    }
 
}

В файле манифеста Android теперь необходимо добавить новую запись для регистрации службы. Как и другие записи, запись тега <service> проста. В этом случае вам нужно только указать свое имя свойства. Запись должна быть размещена с тегом <application>.

1
2
<service
    android:name=».service.TutListDownloaderService»></service>

Мы собираемся использовать атрибут строки UNIQUE базы данных SQLite, чтобы предотвратить вставку повторяющихся записей. Давайте сделаем это изменение сейчас.
В классе TutListDatabase измените строку CREATE_TABLE_TUTORIALS следующим образом:

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
private static final String CREATE_TABLE_TUTORIALS = «CREATE TABLE «
        + TABLE_TUTORIALS + » (» + ID
        + » integer PRIMARY KEY AUTOINCREMENT, » + COL_TITLE
        + » text NOT NULL, » + COL_URL + » text UNIQUE NOT NULL);»;
[/code
 
Since we have fundamentally changed how the database functions, you should now update the database version to 2 by editing the DB_VERSION constant.
<h2><span>Step 4:
 
Our previous implementation of the insert() method within the TutListProvider class used the SQLiteDatabase class method called insert().
[code language=»java»]
@Override
public Uri insert(Uri uri, ContentValues values) {
    int uriType = sURIMatcher.match(uri);
    if (uriType != TUTORIALS) {
        throw new IllegalArgumentException(«Invalid URI for insert»);
    }
    SQLiteDatabase sqlDB = mDB.getWritableDatabase();
    try {
        long newID = sqlDB.insertOrThrow(TutListDatabase.TABLE_TUTORIALS, null,
                values);
        if (newID > 0) {
            Uri newUri = ContentUris.withAppendedId(uri, newID);
            getContext().getContentResolver().notifyChange(uri, null);
            return newUri;
        } else {
            throw new SQLException(«Failed to insert row into » + uri);
        }
    } catch (SQLiteConstraintException e) {
        Log.i(DEBUG_TAG, «Ignoring constraint failure.»);
    }
    return null;
}

Вся фоновая обработка загрузки и анализа ядра происходит во внутреннем классе DownloaderTask, в частности, в методе обратного вызова doInBackground (). Давайте посмотрим на метод doInBackground ():

01
02
03
04
05
06
07
08
09
10
@Override
protected Boolean doInBackground(URL… params) {
    boolean succeeded = false;
    URL downloadPath = params[0];
 
    if (downloadPath != null) {
        succeeded = xmlParse(downloadPath);
    }
    return succeeded;
}

Мы действуем только по одному параметру. Это может быть расширено для перебора всех URL-адресов, загрузки и анализа многих файлов XML. Однако это приложение относится только к одному каналу. Мы передаем URL вспомогательному методу xmlParse (), где будет выполняться весь анализ.

Если вы думаете, что мы пропустили загрузку, мы этого не сделали. Мы будем использовать класс XmlPullParser для разбора XML. Требуется InputStream. Так как класс URL может предоставить InputStream через его метод openStream (), давайте просто сделаем это:

1
2
3
4
XmlPullParser tutorials;
 
tutorials = XmlPullParserFactory.newInstance().newPullParser();
tutorials.setInput(downloadPath.openStream(), null);

Интересующие нас части формата RSS — это теги элементов, а внутри них теги ссылок и заголовков. Мы сделаем это с двумя циклами. Вот разбор:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (eventType != XmlPullParser.END_DOCUMENT) {
    if (eventType == XmlPullParser.START_TAG) {
        String tagName = tutorials.getName();
        if (tagName.equals(«item»)) {
            // inner loop looking for link and title
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if (eventType == XmlPullParser.START_TAG) {
                    if (tutorials.getName().equals(«link»)) {
                    } else if (tutorials.getName().equals(
                            «title»)) {
                    }
                } else if (eventType == XmlPullParser.END_TAG) {
                    if (tutorials.getName().equals(«item»)) {
                        // save the data, and then continue with
                        // the outer loop
                        break;
                    }
                }
                eventType = tutorials.next();
            }
        }
    }
    eventType = tutorials.next();
}

Теперь данные должны быть добавлены в базу данных через контент-провайдера приложения.

Добавить данные через контент-провайдера просто. Вот код для добавления новой записи:

01
02
03
04
05
06
07
08
09
10
ContentValues tutorialData = new ContentValues();
tutorialData.put(
        TutListDatabase.COL_URL,
        «http://some url»);
tutorialData.put(
        TutListDatabase.COL_TITLE,
        «Some Title»);
getContentResolver().insert(
        TutListProvider.CONTENT_URI,
        tutorialData);

По сути, вы добавляете набор значений содержимого в определенный URI в качестве новой записи. Нам не нужно ничего делать с результатами. Кроме того, элемент управления списком уже прослушивает изменения в базе данных, поэтому после добавления данных список обновляется. Как это мило?

Наконец, давайте добавим механизм, позволяющий пользователю самостоятельно обновлять данные. Для этого мы будем использовать меню параметров класса TutListFragment. Мы могли бы использовать новую панель действий, если бы мы только ориентировались на Android 3.0, но мы бы предпочли, чтобы наше приложение работало на различных версиях платформы. Добавление элемента меню к фрагменту приводит к тому, что этот элемент меню добавляется к любым другим элементам меню, которые имеют действие и другие содержащиеся в нем фрагменты. Таким образом, не имеет значения, под какой деятельностью существует этот фрагмент, пункт меню будет там.

Вот новые методы, добавляемые в класс TutListFragment для управления меню параметров с параметром обновления:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
private int refreshMenuId;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    Intent intent = new Intent(getActivity().getApplicationContext(),
            TutListDownloaderService.class);
    intent.setData(Uri
            .parse(«http://feeds.feedburner.com/MobileTuts?format=xml»));
    inflater.inflate(R.menu.options_menu, menu);
    MenuItem refresh = menu.findItem(R.id.refresh_option_item);
    refresh.setIntent(intent);
    refreshMenuId = refresh.getItemId();
}
 
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == refreshMenuId) {
        getActivity().startService(item.getIntent());
    }
    return true;
}

Помимо создания и настройки меню параметров, вы знаете, как запустить службу. Это верно! Это похоже на запуск Activity, за исключением использования метода startService () вместо метода startActivity (). Согласованность это хорошо, да?

И вот определение XML-ресурса меню options_menu.xml (на которое есть ссылка в приведенном выше коде):

1
2
3
4
5
6
7
8
<?xml version=»1.0″ encoding=»utf-8″?>
<menu
    xmlns:android=»http://schemas.android.com/apk/res/android»>
    <item
        android:id=»@+id/refresh_option_item»
        android:icon=»@drawable/ic_menu_refresh»
        android:title=»Refresh»></item>
</menu>

Мы также добавили типичный значок обновления к ресурсам. Фактически мы позаимствовали наши ресурсы из ресурсов платформы, входящих в Android SDK. Он находится под лицензией Creative Commons.

Теперь, когда вы запустите приложение, вы увидите, что у него есть меню:

Android SDK Figure

Наконец, после нажатия кнопки обновления вы увидите, что новые элементы появляются внизу списка:

Android SDK Figure

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

Мы не затронули ни одну из этих тем. Слишком далеко. 🙂

Что конкретно вы хотели бы обсудить в будущем уроке? Дайте нам знать!

Вы узнали о нескольких новых темах в этом руководстве, добавив службу фоновой загрузки в приложение «TutList». Вы узнали, как создавать и использовать Сервис Android. Вы узнали, как создавать и использовать объект AsyncTask. Вы узнали, как анализировать XML из URL действующей сети. Наконец, вы узнали, как создавать и использовать простое меню параметров в сочетании с фрагментами.
Как всегда, мы с нетерпением ждем ваших отзывов.

Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» и « Самс научи себя разрабатывать приложения для Android за 24 часа» . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .

Купить Android-разработку беспроводных приложений, 2-е издание Купить Sam's Teach Yourself для Android-разработки приложений в течение 24 часов Код Мамламбо в Код-Каньоне