Когда мы запускаем приложение на Android, система создает поток, который называется основным потоком приложения или UI-поток. Этот поток обрабатывает все изменения и события пользовательского интерфейса. Однако для вспомогательных операций, таких как отправка или загрузка файла, продолжительные вычисления и т.д., мы можем создавать дополнительные потоки.
Для создания новых потоков нам доcтупен стандартный функционал класса Thread из базовой библиотеки Java из пакета
java.util.concurrent, которые особой трудности не представляют. Тем не менее трудности могут возникнуть при обновлении визуального интерфейса из потока.
Например, создадим простейшее приложение с использованием потоков. Определим следующую разметку интерфейса в файле activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="22sp"
app:layout_constraintBottom_toTopOf="@id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Запустить поток"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Здесь определена кнопка для запуска фонового потока, а также текстовое поле для отображения некоторых данных, которые будут генерироваться в запущенном потоке.
Далее определим в классе MainActivity следующий код:
package com.example.threadapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Определяем объект Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
// получаем текущее время
Calendar c = Calendar.getInstance();
int hours = c.get(Calendar.HOUR_OF_DAY);
int minutes = c.get(Calendar.MINUTE);
int seconds = c.get(Calendar.SECOND);
String time = hours + ":" + minutes + ":" + seconds;
// отображаем в текстовом поле
textView.setText(time);
}
};
// Определяем объект Thread - новый поток
Thread thread = new Thread(runnable);
// Запускаем поток
thread.start();
}
});
}
}
Итак, здесь к кнопке прикреплен обработчик нажатия, который запускает новый поток. Создавать и запускать поток в Java можно различными способами. В данном случае сами действия, которые выполняются в потоке, определяются в методе run() объекта Runnable:
Runnable runnable = new Runnable() {
@Override
public void run() {
// получаем текущее время
Calendar c = Calendar.getInstance();
int hours = c.get(Calendar.HOUR_OF_DAY);
int minutes = c.get(Calendar.MINUTE);
int seconds = c.get(Calendar.SECOND);
String time = hours + ":" + minutes + ":" + seconds;
// отображаем в текстовом поле
textView.setText(time);
}
};
Для примера получаем текущее время и пытаемся отобразить его в элементе TextView.
Далее определяем объект потока - объект Thread, который принимает объект Runnable. И с помощью метода start() запускаем поток:
// Определяем объект Thread - новый поток Thread thread = new Thread(runnable); // Запускаем поток thread.start();
Вроде ничего сложного. Но если мы запустим приложение и нажмем на кнопку, то мы столкнемся с ошибкой:
Поскольку изменять состояние визуальных элементов, обращаться к ним мы можем только в основном потоке приложения или UI-потоке.
Для решения этой проблемы - взаимодействия во вторичных потоках с элементами графического интерфейса класс View() определяет метод post():
boolean post (Runnable action)
В качестве параметра он принимает задачу, которую надо выполнить, и возвращает логическое значение - true, если задача Runnable
успешно помещена в очередь сообщение, или false, если не удалось разместить в очереди
Также у класса View есть аналогичный метод:
postDelayed():boolean postDelayed (Runnable action, long millsec)
Он также запускает задачу, только через определенный промежуток времени в миллисекундах, который указывается во втором параметре.
Так, изменим код MainActivity следующим образом
package com.example.threadapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Определяем объект Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
// получаем текущее время
Calendar c = Calendar.getInstance();
int hours = c.get(Calendar.HOUR_OF_DAY);
int minutes = c.get(Calendar.MINUTE);
int seconds = c.get(Calendar.SECOND);
String time = hours + ":" + minutes + ":" + seconds;
// отображаем в текстовом поле
textView.post(new Runnable() {
public void run() {
textView.setText(time);
}
});
}
};
// Определяем объект Thread - новый поток
Thread thread = new Thread(runnable);
// Запускаем поток
thread.start();
}
});
}
}
Теперь для обновления TextView применяется метод post:
textView.post(new Runnable() {
public void run() {
textView.setText(time);
}
});
То есть здесь в методе run() передаемого в метод post() объекта Runnable мы можем обращаться к элементам визуального интерфейса и
взаимодействовать с ними.
Подобным образом можно работать и с другими виджетами, которые наследуются от класса View.