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

Сегодня рассмотрим очень гибкий и мощный подход — создание наследника AsyncTask.

Постановка задачи

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

Шаг 0: не слишком много теории относительно AsyncTask

Как уже было сказано, AsyncTask — очень мощный класс. Он разделяет выполнение порученной ему задачи на 3 этапа: подготовка, выполнение каких-либо вычислений с возможность оповещения о прогрессе, завершение. Этим этапам соответствует 4 метода:

  • onPreExecute() — выполняется сразу после запуска задачи и синхронизирован с главным потоком, то есть в этом методе вы можете менять параметры элементов интерфейса
  • doInBackground(Params …) — в нем происходит вся работа, которая может требовать много времени. Из него нельзя менять параметры элементов интерфейса. Но! Из него можно вызвать метод publishProgress(Progress …), который передает параметр в метод в синхронизированный с основным потоком метод который называется:
  • onProgressUpdate(Progress ….)  в этом методе можно проводить обновления UI использую параметры переданные из doInBackground()
  • onPostExecute(Result) — Вызывается последним, имеет доступ к UI, ему передается результат выполнения doInBackgroud()

Из всего этого богатства методов обязателен к перегрузке только doInBackground().

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

AsyncTask < [тип входного параметра - Params],

[тип параметра прогресса - Progress],
[тип результат - Result] >

Чтобы запустить асинхронную задачу на выполнение используем подобный код:

new MyAsyncTaskChild().execute(arg0, arg1, ..., argn);

Обратите внимание, что метод execute() можно вызвать у одного и того же объекта только один раз, попробуете еще раз — получите исключение.

Шаг 1: описание интерфейса приложения

Описание активности будет иметь следующий вид:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:stretchColumns="0,1,2">

<TableRow
android:id="@+id/images">
<ImageView
android:id="@+id/image_1"
android:layout_height="60px"
android:layout_width="60px"
android:layout_margin="10sp"/>
<ImageView
android:id="@+id/image_2"
android:layout_height="60px"
android:layout_width="60px"
android:layout_margin="10sp"/>
<ImageView
android:id="@+id/image_3"
android:layout_height="60px"
android:layout_width="60px"
android:layout_margin="10sp"/>
</TableRow>

</TableLayout>

<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:id="@+id/loading_progress"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_margin="5sp"
android:max="3"
android:progress="0"/>

<Button
android:id="@+id/laod_button"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="Начать загрузку"
android:onClick="startLoading"/>

</LinearLayout>

Сказать тут что-то новое сложно, поэтому без лишних слов перейдем к…

Шаг 2: описание бизнес логики приложения. Реализация наследника AsyncTask

Мы опишем класс AsyncImageLoader, унаследованный от AsyncTask, как вложенный в класс нашей активности. Это сделано потому, что он использует поля активности, что в нашем случае упрощает его реализацию. Приведу листинг, потом мы его обсудим более детально:

package by.idev.android.async;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TableRow;
import android.widget.Toast;
public class IdevAsyncAndThreadsActivity extends Activity {
//Ссылки на изображения для загрузки
public static final String[] imagesUrls = {
"http://idev.by/blog/wp-content/uploads/2011/08/part1.png",
"http://idev.by/blog/wp-content/uploads/2011/08/part2.png",
"http://idev.by/blog/wp-content/uploads/2011/08/part3.png"
};
private ArrayList imageViews = new ArrayList();
private ProgressBar progressBar;
private AsyncImagesLoader loader;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setUpViews();
}
public void startLoading(View view) {
if (loader == null ||
loader.getStatus().equals(AsyncTask.Status.FINISHED)) {
loader = new AsyncImagesLoader();
loader.execute(imagesUrls);
progressBar.setProgress(0);
} else {
Toast.makeText(IdevAsyncAndThreadsActivity.this, "Ждите завершения загрузки", Toast.LENGTH_SHORT)
.show();
}
}
private void setUpViews() {
TableRow imagesRow = (TableRow)findViewById(R.id.images);
for (int i = 0; i < imagesRow.getChildCount(); i++) {
imageViews.add((ImageView) imagesRow.getChildAt(i));
}
progressBar = (ProgressBar)findViewById(R.id.loading_progress);
}
//AsyncTask параметризуется:
//входной параметр - строки, представляют url
//параметр прогресса - пара вида: позиция виджета+изоборажение для него
//параметр результат - Void, он нам не нужен
private class AsyncImagesLoader extends AsyncTask&<String, Pair<Integer, Bitmap>, Void>; {
@Override
protected Void doInBackground(String... urls) {
for (int i = 0; i < urls.length; i++) {
try {
Bitmap image = getImageByUrl(urls[i]);
//Добавим засыпание потока, чтобы симиритовать долгую загрузку
Thread.sleep(400);
Pair pair = new Pair(i, image);
publishProgress(pair);
} catch (IOException e) {
Toast.makeText(IdevAsyncAndThreadsActivity.this,
"Не возможно загрузить изображение",
Toast.LENGTH_SHORT).show();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Pair ... progress) {
int position = progress[0].first;
int currentProgress = position+1;
Bitmap image = progress[0].second;
progressBar.setProgress(currentProgress);
imageViews.get(position).setImageBitmap(image);
}
@Override
protected void onPostExecute(Void result) {
Toast.makeText(IdevAsyncAndThreadsActivity.this, "Загрузка завершена", Toast.LENGTH_SHORT)
.show();
}
private Bitmap getImageByUrl(String url) throws IOException,
MalformedURLException {
//Вот так можно получить изображение по url
Bitmap image = BitmapFactory.depreStream((InputStream)new URL(url).getContent());
return image;
}
}
}

Так как наше приложение требует доступа в интернет, то не забудем попросить об этом в манифесте, добавив туда вот такую строку:


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

Теперь важные методы по порядку:

    public void startLoading(View view) {

if (loader == null ||
loader.getStatus().equals(AsyncTask.Status.FINISHED)) {
loader = new AsyncImagesLoader();
loader.execute(imagesUrls);
progressBar.setProgress(0);
} else {
Toast.makeText(IdevAsyncAndThreadsActivity.this, "Ждите завершения загрузки", Toast.LENGTH_SHORT)
.show();
}
}

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

@Override

@Override
protected Void doInBackground(String... urls) {
for (int i = 0; i < urls.length; i++) {
try {
Bitmap image = getImageByUrl(urls[i]);
//Добавим засыпание потока, чтобы симиритовать долгую загрузку
Thread.sleep(400);
Pair pair = new Pair(i, image);
publishProgress(pair);
} catch (IOException e) {
Toast.makeText(IdevAsyncAndThreadsActivity.this,
"Не возможно загрузить изображение",
Toast.LENGTH_SHORT).show();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}

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

Обращу ваше внимание еще раз: метод не работает с UI напрямую, все, что мы хотим передать интерфейсу, передается методом publishProgress().

		@Override

protected void onProgressUpdate(Pair ... progress) {
int position = progress[0].first;
int currentProgress = position+1;
Bitmap image = progress[0].second;
progressBar.setProgress(currentProgress);
imageViews.get(position).setImageBitmap(image);
}

Добавляет изображение, полученное из метода doInBackgroud(), соответствующему виджету и обращается к прогрессбару (для красоты :) )

		@Override

protected void onPostExecute(Void result) {
Toast.makeText(IdevAsyncAndThreadsActivity.this, "Загрузка завершена", Toast.LENGTH_SHORT)
.show();
}

Делает «тост», сообщая нам тем самым что асинхронная работа закончена.

		private Bitmap getImageByUrl(String url) throws IOException,

MalformedURLException {
//Вот так можно получить изображение по url
Bitmap image = BitmapFactory.depreStream((InputStream)new URL(url).getContent());
return image;
}

Одна из многих возможных методик получения изображения из сети. Использует стандартный класс BitmapFactory, который может «выдирать» изображения из самых разных источников.

Вместо заключения

Как видите, все на так уж и сложно, в следующий статьях мы рассмотрим другие подходы к асинхронному выполнению, коих еще не менее двух.

Отмечу, что приведенный код несовершенен (я бы сам себе за него руки оторвал, да кто же тогда будет писать статьи? :) ): при смене ориентации экрана картинки будут потеряны, а наша асинхронная задача не выполнит свою работу. Если у вас возникнет желание, пишите мне и я напишу как этого можно избежать.

Исходный код: IdevAsyncAndThreads

Happy Coding! :)

Похожие статьи

  • Вышла новая версия Android NDK r8e.

  • Вызов Java-методов из C/C++ кода при помощи JNI на Android.

  • Учимся возбуждать…. Java-исключений из нативного кода на C/C++ через JNI в Android.

  • SQL-скрипты, SimpleCursorAdapter и ViewBinder — полезные приемы работы с Sqlite в Android.

  • Многопоточность в iOS. Введение в GCD, часть 4, семафоры.