Содержание
Очень обширный класс задач требует применения многопоточности: сложные вычисления, доступ в сеть или к другим устройствам, отслеживание каких либо изменений данных. Выполнять их нужно в отдельном потоке, но при этом необходимо учесть, что изменять элементы интерфейса можно только из основного потока программы. Это создает некоторые сложности, которые мы запросто преодолеем за пару статей
Сегодня рассмотрим очень гибкий и мощный подход — создание наследника 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, семафоры.