Когда мы запускаем приложение на 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.