Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование мобильных устройств. Ч.1. Учебно-методическое пособие

.pdf
Скачиваний:
0
Добавлен:
09.08.2026
Размер:
462 Кб
Скачать

mutex.withLock {

// Код, который требует эксклюзивного доступа к ресурсу

}

}

}

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

import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Semaphore

class SemaphoreExample {

private val semaphore = Semaphore(10) // Ограничение до 10 потоков

suspend fun accessResource() {

semaphore.acquire() // Запрашиваем разрешение на доступ try {

//Код, который требует доступа к ресурсу

}finally {

semaphore.release() // Освобождаем разрешение

}

}

}

В приведенных выше примерах Mutex и Semaphore используются для управления доступом к общему ресурсу. Mutex обеспечивает эксклюзивный доступ для одного потока, а Semaphore ограничивает количество потоков, которые могут одновременно получить доступ к ресурсу. Эти механизмы очень полезны для предотвращения состязательных условий и обеспечения безопасности при работе с общими ресурсами в многопоточных приложениях.

31

6. Создание приложения «Компас»

Целью занятия является создание приложения «Компас». На экране отображается циферблат компаса, вращение циферблата осуществляется в зависимости от работы датчика местоположения.

Создайте новый проект в Android Studio, используя язык программирования Kotlin. Откройте макет (layout) для вашей активности, где будет размещен компас.

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

«SensorManager» это класс, предоставляющий доступ к различным датчикам на устройстве. Он используется для регистрации слушателей датчиков и получения данных от них.

«SensorEventListener» это интерфейс обратного вызова, который предоставляет методы для обработки изменений в значениях датчиков, таких как изменения точности и обновления данных.

«Sensor.TYPE_MAGNETIC_FIELD» это константа, представляющая магнитный датчик. Его можно использовать для получения данных о магнитном поле устройства.

Используйте «SensorManager.getRotationMatrix» и «SensorManager.getOrientation» для определения ориентации устройства на основе данных от датчика магнитного поля и акселерометра.

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

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

val magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

Далее используем данные от датчика магнитного поля для определения ориентации устройства в пространстве.

val rotationMatrix = FloatArray(9) val orientationValues = FloatArray(3)

SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)

SensorManager.getOrientation(rotationMatrix, orientationValues)

// orientationValues[0] содержит угол азимута (направление к северу)

Создаем графический элемент, представляющий компас, и обновляем его положение в соответствии с полученными данными об угле азимута.

val compassImageView: ImageView = findViewById(R.id.compassImageView) compassImageView.rotation = - Math.toDegrees(orientationValues[0].toDouble()).toFloat()

32

Регистрируем слушателей для датчиков и обрабатываем изменения значений.

val sensorEventListener = object : SensorEventListener {

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { // Обработка изменения точности датчика

}

override fun onSensorChanged(event: SensorEvent) { // Обработка изменений значений датчика

if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { // Обработка данных магнитного датчика

}

}

}

sensorManager.registerListener(sensorEventListener, magneticFieldSensor, SensorManager.SENSOR_DELAY_NORMAL)

6.1.Дополнительные теоретические сведения

Впроцессе выполнения лабораторной работы будут полезны некоторые теоретические сведения, представленные ниже.

6.1.1. Работа с датчиками ориентации

Датчик ориентации – это устройство, состоящее из комбинации магнитного датчика (компаса) и акселерометра. Он используется для определения ориентации устройства в пространстве.

Акселерометр – датчик, который измеряет ускорение устройства по трем осям: X, Y и Z. Этот датчик позволяет определить наклон устройства.

Магнитный датчик – датчик, который измеряет магнитное поле вокруг устройства. Он помогает определить направление, указывающее на север.

Для определения ориентации устройства используется матрица поворота. Эта матрица строится на основе данных, получаемых от акселерометра и магнитного датчика. Полученные данные акселерометра позволяют вычислить угол наклона устройства относительно осей X и Z. А данные магнитного датчика помогают определить угол поворота устройства относительно оси Y.

Для работы с датчиками в Android используется класс «SensorManager». В данном примере мы регистрируем слушателя для датчиков типа

`TYPE_ACCELEROMETER` и `TYPE_MAGNETIC_FIELD`.

Данные, полученные с этих датчиков, используются для расчета матрицы поворота с помощью метода `getRotationMatrix()`. Затем с использованием этой матрицы вычисляются углы ориентации устройства по осям X, Y и Z с помощью метода `getOrientation()`.

import android.content.Context import android.hardware.Sensor

33

import android.hardware.SensorEvent

import android.hardware.SensorEventListener import android.hardware.SensorManager import android.os.Bundle

import android.support.v7.app.AppCompatActivity import android.widget.TextView

class MainActivity : AppCompatActivity(), SensorEventListener {

private lateinit var sensorManager: SensorManager private var accelerometer: Sensor? = null

private var magnetometer: Sensor? = null

private var accelerometerReading = FloatArray(3) private var magnetometerReading = FloatArray(3) private var rotationMatrix = FloatArray(9)

private var orientationAngles = FloatArray(3)

private lateinit var xyView: TextView private lateinit var xzView: TextView private lateinit var zyView: TextView

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)

xyView = findViewById(R.id.xyValue) xzView = findViewById(R.id.xzValue) zyView = findViewById(R.id.zyValue)

sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)

magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

}

override fun onStart() { super.onStart()

accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL) }

magnetometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL) }

34

}

override fun onStop() { super.onStop()

sensorManager.unregisterListener(this)

}

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { // Не используется

}

override fun onSensorChanged(event: SensorEvent?) { if (event == null) return

if (event.sensor == accelerometer) {

System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)

}else if (event.sensor == magnetometer) { System.arraycopy(event.values, 0, magnetometerReading, 0,

magnetometerReading.size)

}

updateOrientationAngles()

updateUI()

}

private fun updateOrientationAngles() { SensorManager.getRotationMatrix(rotationMatrix, null,

accelerometerReading, magnetometerReading) SensorManager.getOrientation(rotationMatrix, orientationAngles)

}

private fun updateUI() {

xyView.text = "XY: ${Math.toDegrees(orientationAngles[0].toDouble())}°"

xzView.text = "XZ: ${Math.toDegrees(orientationAngles[1].toDouble())}°"

zyView.text = "ZY: ${Math.toDegrees(orientationAngles[2].toDouble())}°"

}

}

В этом примере происходит регистрация слушателей для датчиков акселерометра и магнитного поля в методе `onStart()`, а затем приостановка регистрации в методе `onStop()` для оптимального использования ресурсов

35

устройства. Когда данные с датчиков изменяются, вызывается метод `onSensorChanged()`, в котором обновляются углы ориентации устройства и обновляется интерфейс пользователя.

6.1.2. Класс «AsyncTask»

«AsyncTask» предоставляет удобный способ выполнения операций в фоновом режиме и обновления пользовательского интерфейса. Он используется для выполнения относительно небольших задач, которые не требуют создания отдельного потока или Handler'а.

Основные преимущества AsyncTask:

Удобство использования: AsyncTask предоставляет простой интерфейс для выполнения задач в фоновом режиме без необходимости создания нового потока или Handler'а.

Легкость взаимодействия с UI-потоком: AsyncTask автоматически обрабатывает обновление пользовательского интерфейса из фонового потока, что делает его идеальным для выполнения операций, которые требуют обновления интерфейса после завершения.

Обработка сложных операций в фоне: AsyncTask позволяет выполнять длительные операции, такие как загрузка данных из Интернета, обработка файлов или выполнение вычислений, не блокируя основной поток

приложения. Ограничения AsyncTask:

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

Не поддерживает выполнение параллельно: AsyncTask выполняет задачи последовательно в одном фоновом потоке, поэтому он не подходит для выполнения нескольких задач параллельно.

Привязан к жизненному циклу Activity: AsyncTask связан с жизненным циклом Activity, поэтому его выполнение может быть прервано при изменении состояния активности (например, при повороте экрана).

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

такие как Executor или Kotlin Coroutines.

6.1.3. Жизненный цикл Activity

Жизненный цикл Activity определяет, как Activity взаимодействует с пользователем и системой на разных этапах своего существования. Понимание

36

этого цикла помогает эффективно управлять ресурсами и сохранять состояние приложения.

Модель жизненного цикла Activity:

1)«onCreate(Bundle)»: Метод вызывается при создании Activity. Здесь обычно происходит инициализация пользовательского интерфейса и подготовка к работе;

2)«onStart()»: Activity становится видимым для пользователя. На этом этапе Activity еще не в фокусе, но уже готова к взаимодействию;

3)«onResume()»: Activity становится активной и в фокусе. Это наиболее подходящее место для запуска анимаций, аудио или других операций, которые должны происходить, когда пользователь взаимодействует с

Activity;

4)«onPause()»: Activity теряет фокус, но остается видимой. В этом методе часто выполняют сохранение данных в базе данных или других хранилищах, чтобы гарантировать сохранение информации перед потенциальным завершением Activity;

5)«onStop()»: Activity больше не видима для пользователя. Этот метод может использоваться для освобождения ресурсов или остановки фоновых процессов;

6)«onDestroy()»: Activity уничтожается и освобождает все связанные с ним

ресурсы. Этот метод вызывается перед завершением Activity. Сохранение состояния Activity:

1)«onSaveInstanceState(Bundle)»: Вызывается перед тем, как Activity будет уничтожена. Используется для сохранения состояния Activity, например, информации о текущем состоянии пользовательского интерфейса или других важных данных;

2)«onRestoreInstanceState(Bundle)»: Вызывается после пересоздания Activity

из сохраненного состояния. Используется для восстановления ранее сохраненного состояния.

Рекомендации по использованию методов:

1)onCreate(): Используется для инициализации пользовательского интерфейса и подготовки Activity к работе;

2)onResume(): Используется для запуска анимаций, аудио и других операций, которые должны происходить, когда пользователь взаимодействует с Activity;

3)onPause(): Используется для сохранения данных перед потенциальным завершением Activity.

37

7. Создание Базы данных

Целью занятия является создание базы данных студентов (имя, вес, рост, возраст – сгенерированы случайно). Вывести из базы данных все записи, отсортированные по возрасту, в таблицу (TableLayout).

Для работы с базой данных SQLite в Kotlin используется класс `SQLiteOpenHelper`. В файле `build.gradle` добавьте зависимость для поддержки работы с базой данных SQLite.

implementation "androidx.sqlite:sqlite-ktx:2.2.0"

Создайте новый проект в Android Studio, используя язык программирования Kotlin. Объявите необходимые зависимости в файле `build.gradle` для работы с базой данных SQLite.

Определите класс `DatabaseHandler`, который расширяет `SQLiteOpenHelper`. В классе реализуйте методы для создания базы данных и выполнения запросов.

class DatabaseHandler(context: Context) :

SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

// Реализация методов создания базы данных и выполнения запросов

}

Создайте класс модели `Student` с полями, представляющими информацию о студенте (имя, вес, рост, возраст).

data class Student(val name: String, val weight: Float, val height: Float, val age: Int)

Используйте класс `Random` для генерации случайных значений и вставки записей в базу данных.

val random = Random()

val student = Student("Имя", random.nextFloat() * 100, random.nextFloat() * 200, random.nextInt(18, 30))

databaseHandler.addStudent(student)

В макете разместите `TableLayout`, предназначенную для отображения данных. Используйте методы `addView` и `TextView` для добавления строк и ячеек в `TableLayout`.

val tableLayout: TableLayout = findViewById(R.id.tableLayout)

//Получите список студентов, отсортированный по возрасту val sortedStudents = databaseHandler.getAllStudentsSortedByAge()

//Добавьте строки и ячейки в TableLayout

for (student in sortedStudents) { val row = TableRow(this)

val nameCell = TextView(this) val weightCell = TextView(this) val heightCell = TextView(this)

38

val ageCell = TextView(this)

nameCell.text = student.name weightCell.text = student.weight.toString() heightCell.text = student.height.toString() ageCell.text = student.age.toString()

row.addView(nameCell)

row.addView(weightCell)

row.addView(heightCell)

row.addView(ageCell)

tableLayout.addView(row)

}

Определите схему базы данных, включая название базы данных, версию и структуру таблицы.

companion object {

const val DATABASE_VERSION = 1

const val DATABASE_NAME = "StudentsDB" const val TABLE_NAME = "students"

const val KEY_NAME = "name" const val KEY_WEIGHT = "weight" const val KEY_HEIGHT = "height" const val KEY_AGE = "age"

}

Используйте метод `insert` для добавления записи в базу данных.

fun addStudent(student: Student) { val db = this.writableDatabase

val values = ContentValues().apply { put(KEY_NAME, student.name) put(KEY_WEIGHT, student.weight) put(KEY_HEIGHT, student.height) put(KEY_AGE, student.age)

}

db.insert(TABLE_NAME, null, values) db.close()

}

Используйте запрос `SELECT *` для получения всех записей из таблицы.

fun getAllStudents(): List<Student> {

39

val studentList = mutableListOf<Student>()

val selectQuery = "SELECT * FROM $TABLE_NAME" val cursor = readableDatabase.rawQuery(selectQuery, null) if (cursor.moveToFirst()) {

do {

val name = cursor.getString(cursor.getColumnIndex(KEY_NAME)) val weight = cursor.getFloat(cursor.getColumnIndex(KEY_WEIGHT)) val height = cursor.getFloat(cursor.getColumnIndex(KEY_HEIGHT)) val age = cursor.getInt(cursor.getColumnIndex(KEY_AGE))

val student = Student(name, weight, height, age) studentList.add(student)

} while (cursor.moveToNext())

}

cursor.close() return studentList

}

Используйте `ORDER BY` для сортировки записей по определенному полю.

fun getAllStudentsSortedByAge(): List<Student> { val studentList = mutableListOf<Student>()

val selectQuery = "SELECT * FROM $TABLE_NAME ORDER BY $KEY_AGE"

val cursor = readableDatabase.rawQuery(selectQuery, null) if (cursor.moveToFirst()) {

do {

val name = cursor.getString(cursor.getColumnIndex(KEY_NAME)) val weight = cursor.getFloat(cursor.getColumnIndex(KEY_WEIGHT)) val height = cursor.getFloat(cursor.getColumnIndex(KEY_HEIGHT)) val age = cursor.getInt(cursor.getColumnIndex(KEY_AGE))

val student = Student(name, weight, height, age) studentList.add(student)

} while (cursor.moveToNext())

}

cursor.close() return studentList

}

Для изменения структуры уже существующей таблицы в базе данных используется Оператор SQL «ALTER TABLE», общий синтаксис которого приведен ниже.

ALTER TABLE table_name

ADD column_name datatype;

40

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]