Loaders. Используем AsyncTaskLoader

| 11.03.2015

Мы уже рассматривали ранее несколько способов создания асинхронной работы/загрузки данных. В Андроид-разработке есть еще один очень эффективный инструмент — Loaders или «загрузчики». Пожалуй, тут название говорит само за себя: Loader используется, как правило, для загрузки каких-либо данных. При этом работой загрузчиков из класса Активити или фрагмента позволяет управлять так называемый LoaderManager. Он используется один на весь класс Активити, но позволяет работать с несколькими загрузчиками.

Согласно документации, загрузчик отслеживает источник данных, и если они изменились, то вновь их загружает (обновляет). Конечно же, сам загрузчик не коннектится постоянно к БД или к внешнему серверу, чтобы определить изменились данные или нет. Обычно для этой цели создают так называемого наблюдателя (например, по типу BroadcastReceiver), который «подписан» на определенные события и в случае обновления данных «сообщает» об этом загрузчику с помощью метода onContentChanged().

Рассмотрим один подкласс загрузчика — AsyncTaskLoader на примере простого приложения. Чтобы упростить код, мы не будем создавать «наблюдателя». В качестве триггера для новой загрузки данных мы будем использовать нажатие на кнопке, а сами данные будут представлять собой случайно сгенерированную строку из первоначального строкового параметра. «Загруженные» данные будут отображаться в TextView. Поняв суть, вы уже сможете создать свой класс загрузчика с нужным источником данных.

1. Добавим несколько строковых ресурсов — res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Loader Example</string>
    <string name="hello_world">Hello world!</string>
    <string name="start_button">Start</string>

</resources>

2. Создадим layout — res/layout/activity_main.xml

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:id="@+id/resultTxt"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="startLoad"
        android:text="start"
        android:layout_below="@+id/resultTxt"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>

Добавьте значения отступов в res/values/dimens.xml

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
</resources>

3. Создадим класс загрузчика, который наследуется от AsyncTaskLoader. Наш загрузчик будет возвращать строку, поэтому укажем <String>

package ru.androiddocs.loaderexample;


import android.content.Context;
import android.os.Bundle;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;

import java.util.Random;

public class RandomLoader extends AsyncTaskLoader<String> {

    public static final String LOG_TAG = "my_tag";
    public static final String ARG_WORD = "word";
    public static final int RANDOM_STRING_LENGTH = 100;
    private String mWord;

    public RandomLoader(Context context, Bundle args) {
        super(context);
        if (args != null)
            mWord = args.getString(ARG_WORD);
    }

    @Override
    public String loadInBackground() {
        if (mWord == null) {
            return null;
        }
        Log.d(LOG_TAG, "loadInBackground");
        return generateString(mWord);
    }

    @Override
    public void forceLoad() {
        Log.d(LOG_TAG, "forceLoad");
        super.forceLoad();
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        Log.d(LOG_TAG, "onStartLoading");
        forceLoad();
    }

    @Override
    protected void onStopLoading() {
        super.onStopLoading();
        Log.d(LOG_TAG, "onStopLoading");
    }

    @Override
    public void deliverResult(String data) {
        Log.d(LOG_TAG, "deliverResult");
        super.deliverResult(data);
    }


    private String generateString(String characters)
    {
        Random rand = new Random();
        char[] text = new char[RANDOM_STRING_LENGTH];
        for (int i = 0; i < RANDOM_STRING_LENGTH; i++) {
            text[i] = characters.charAt(rand.nextInt(characters.length()));
        }
        return new String(text);
    }
}

Переменная mWord будет хранить базовую строку, на основе которой будет генериться случайная строка. Это параметр передается при создании загрузчика в конструктор с использованием объекта Bundle. Константа RANDOM_STRING_LENGTH задает максимальную длину новой случайной строки.

Наследуясь от AsyncTaskLoader мы переопределяем несколько методов:

loadInBackground() — метод, в котором собственно и должна быть создана вся работа по загрузке данных.
onStartLoading() — срабатывает при запуске загрузчика (но это еще не означает загрузку данных)
onStopLoading() — срабатывает при остановке загрузчика
deliverResult() — получает и возвращает итоговый результат работы загрузчика
forceLoad() — «принудительная» загрузка новых данных

Для получения данных в методе loadInBackground() мы вызываем наш вспомогательный метод generateString(), который генерит случайную строку.

4. Создадим MainActivity.java. Класс реализует интерфейс LoaderManager.LoaderCallbacks, то есть мы добавляем несколько callback-методов, позволяющих нам «участвовать» в жизненном цикле загрузчика и взаимодействовать с LoaderManager.

package ru.androiddocs.loaderexample;

import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;

import android.view.View;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity
        implements LoaderManager.LoaderCallbacks<String> {

    public static final String LOG_TAG = "my_tag";
    private TextView mResultTxt;
    private Bundle mBundle;
    public static final int LOADER_RANDOM_ID = 1;
    private Loader<String> mLoader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mResultTxt = (TextView) findViewById(R.id.resultTxt);

        mBundle = new Bundle();
        mBundle.putString(RandomLoader.ARG_WORD, "test");
        mLoader = getSupportLoaderManager().initLoader(LOADER_RANDOM_ID, mBundle, this);
        //getLoaderManager().initLoader(LOADER_RANDOM_ID, mBundle, this);
    }

    public Loader<String> onCreateLoader(int id, Bundle args) {
        Loader<String> mLoader = null;
        // условие можно убрать, если вы используете только один загрузчик
        if (id == LOADER_RANDOM_ID) {
            mLoader = new RandomLoader(this, args);
            Log.d(LOG_TAG, "onCreateLoader");
        }
        return mLoader;
    }

    public void onLoadFinished(Loader<String> loader, String data) {
        Log.d(LOG_TAG, "onLoadFinished");
        mResultTxt.setText(data);
    }

    public void onLoaderReset(Loader<String> loader) {
        Log.d(LOG_TAG, "onLoaderReset");
    }

    public void startLoad(View v) {
        Log.d(LOG_TAG, "startLoad");
        mLoader.onContentChanged();
    }
}

onCreateLoader() — вызывается при инициализации загрузчика. Если загрузчик с таким id уже был создан, то метод не вызовется. Внутри мы определяем, какой id нам был передан, чтобы создать нужный загрузчик. Если загрузчик только один, то условие можно убрать.

onLoadFinished() — вызывается по окончанию загрузки. В метод приходят загруженные данные, а также объект загрузчика. Полученную строку мы добавляем в TextView.

onLoaderReset() — вызывается при «сбросе» состояния загрузчика. Здесь данные обнуляются, и нам нужно удалить все имеющиеся ссылки на них.

Метод startLoad() — это обработчик нажатия на клик по кнопке. Это «наш» метод, который выполняет роль триггера для запуска новой загрузки данных. Мы используем onContentChanged(), чтобы сигнализировать загрузчику об изменении данных.

Как происходит первый запуск загрузчика? Мы это делаем в методе onCreate(), инициализируя загрузчик:

mLoader = getSupportLoaderManager().initLoader(LOADER_RANDOM_ID, mBundle, this);

При инициализации мы передаем в параметрах:

id — идентификатор загрузчика (у нас константа 1)
Bundle — объект, содержащий передаваемые аргументы (мы передаем базовую строку)
this — это указатель на callback-объект (в нашем случае — это само Активити)

Обратите внимание, что в примере я использую Support Library. Без этой библиотеки инициализация будет такой:

getLoaderManager().initLoader(LOADER_RANDOM_ID, mBundle, this);

Ну теперь запускаем приложение и играемся.

Первый запуск (до нажатия на кнопку).

Loaders. Используем AsyncTaskLoader

Loaders. Используем AsyncTaskLoader

При нажатии на кнопку текст обновляется, но в логах мы уже не видим повторного создания загрузчика (он уже был создан ранее).

Loaders. Используем AsyncTaskLoader

Полезные ссылки:

Loaders
LoaderManager.LoaderCallbacks

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*