Министерство по развитию информационных технологий и коммуникаций Республики Узбекистан
ТАШКЕНТСКИЙ УНИВЕРСИТЕТ ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ ИМЕНИ МУХАММАДА АЛ-ХОРАЗМИЙ
Бабомурадов О.Ж., Дощанова М.Ю.
“РАЗРАБОТКА МОБИЛЬНЫХ ПРИЛОЖЕНИЙ”
УЧЕБНИК
для студентов высших учебных заведений по направлениям
5330600 – “Программный инжиниринг”,
5330500 – “Компьютерный инжиниринг (Мультимедиа технологиялари)”
Ташкент – 2021
Авторы: О.Ж.Бабомурадов, М.Ю.Дощанова.Разработка мобильных приложений.ТУИТ. 344 с. Ташкент, 2021.
В учебнике рассмотрены вопросы разработки мобильных приложений для мобильных устройств под управлением операционной системы Android и IOS. Приведеныбазовые сведения о платформе Android. Описаны программные средства необходимые для разработки Android и iOS приложений. Рассмотрены основные компоненты приложений, использование базовых виджетов, разработка мобильных приложений с использованием языков программирования Java,XML и Swift, создание пользовательских приложений, создание приложений для загрузки изображений, использование меню, подменю и контент провайдеров, приложений для отправки и обмена сообщениями, даны работы с базами данных, подключение к серверу, использование сервисов GoogleMaps, определение местоположения пользователя и публикация приложения в интернете.
В учебнике также имеются контрольные вопросы, глоссарий и список использованной литературы.
Учебник предназначен для студентов высших учебных заведенийпо направлениям образования 5330600 – “Программный инжиниринг” и5330500 – “Компьютерный инжиниринг (Мультимедиа технологиялари)”, а также для тех кто занимается программированием для мобильных устройств.
Напечатано по решению учебно – методического совета ТУИТ (протокол № от «____» ________2021 г.)
Рецензенты: |
|
А.Ахатов |
- Заместитель директора Джизакского филиала Национального университета Узбекистана, д.т.н., профессор; |
Б.Б.Муминов |
- Заведующий кафедрой «Основы информатики» Ташкентского университета информационных технологий имени Мухаммада ал-Хоразмий, д.т.н. профессор. |
Ташкентский университет информационных технологий
имени Мухаммада ал-Хоразмий
Большое стремление каждого человека достигнуть максимального комфорта в каждой из сфер жизни, затронуло и международную паутину Интернет. Пользователь, желая всегда оставаться в сети, использует в качестве коммуникатора телефон.
Это обусловило появление мобильного Интернета. При пребывании вне дома, либо во время путешествий и командировок можно вместо ноутбука подключаться с помощью планшета либо аналогичного вида техники. Эффективность и функциональность «мини» компьютеров не была бы доведена до столь высокого уровня без специализированных приложений.
Разработка мобильных приложений, которая проводится исключительно специалистами сферы, рассчитана на определенное предназначение. Некоторые программы позволяют повсеместно осуществлять соединение с сетью, другие указывают маршрут, третьи оказывают помощь в поиске магазина либо требуемого товара. Есть приложения, которые осуществляют заказ еды на дом. Утилиты легли в основу повсеместного обмена данными и информацией, что позволяет экономить драгоценное время и ресурсы каждого.
Учебник посвящен основам разработки мобильных приложений под Android и iOSв интегрированных средах разработкиAndroidStudio, XCode c использованием языков программирования Java и Swift. Эти средства доступны и универсальны по своим характеристикам. Они обладают широкими возможностями и достаточно просты в освоении. Работа в этих программных средствах с помощью этой книги станет намного проще, легче и гораздо интереснее. Например, в разработке мобильных приложений задействованы сразу несколько языков программирования и разметки (Java, Xml, Sql, Swift).
Первая глава этого учебника посвящена основам разработки мобильных приложений и жизненный цикл мобильных приложений, а также понятия разработки мобильных приложений и среды разработки мобильных приложений. Рассматриваются языки программирования мобильных приложений Javaи Dart для операционных систем Аndroid и iOS. Подробно описываются жизненный цикл мобильных приложений, управление жизненным циклом, состояние активностей, процессы и потоки приложений.
Вторая глава посвящена видаммобильных операционных систем, платформе и архитектуре мобильных операционных систем, языки соответствующие платформе мобильных операционных систем. Подробно описывается платформа Андроид и его основы, основные виды Android-приложений, архитектура мобильных приложений и их основные компоненты, установка и настройка инструментальных средств для разработки мобильных приложений, межплатформенные языки разработки мобильных приложений.
Третья глава учебника направлена на разработкумобильных приложений на языке Java для операционной системы Аndroid. Рассматриваются основные конструкцииязыка программированияJavа, типы данных, специальные классы и функции, классы и объекты, конструкторы, инициализаторы, а также способы разработки мобильных приложений на языке Javа.
Всем известно, какую роль играет база данных в современных приложениях. Решение задачи создание глубоко продуманной и правильной базы данных шаг к совершенству в проектировании динамических объектов.
В четвёртой главе учебника приводится материалы по базам данных и создание базы данных, создание запросов, контент провайдеры и их использование,обмен сообщениями, сетевое программирование в мобильных приложениях, определение местоположения пользователя,работа с сервером, создание элемента для определения геолокации, который и определяет один из принципов мобильности устройства. А также приведены материалы связанные с обменом данных.
Мобильные датчики намного улучшены чем стационарные компьютеры и ноутбуки. В мобильных устройствах имеются сенсорное управление, распознавание жестов, лиц, пальцев и т.д., которые реализуются с помощью сенсоров и датчиков. В пятой главе рассматриваются сенсорные возможности Android,отличительные особенности смартфонов, сенсорное управление, распознавание жестов, создание набора жестов, виды итипы датчиков, работа с датчиками,определение датчиков, сенсоры и принципы их использования в мобильных приложениях.
Последняя глава посвящена разработке приложений на языке Swift для ОСiOS. Приведены материалы по архитектуре и принципам проектированияв программной средеXCode. Рассматриваются основные принципы работы на языке Swift, переменные и константы,типы данных, условные конструкции, тернарные операторы, циклы, функции, классы и объекты, статические свойства и методы, структуры, наследование, полиморфизм, коллекции, массивы, множества, словари, сабскрипты. После прохождения нескольких уроков изученные функции объединяются в одно полноценное приложение. Завершает книгу итоговый проект, в котором с помощью всех полученных навыков создается мобильное приложение.
Развернутое оглавление книги поможет легко ориентироваться в расположении описания изученных функций.
1. ОСНОВЫ РАЗРАБОТКИ И ЖИЗНЕННЫЙ ЦИКЛ МОБИЛЬНЫХ ПРИЛОЖЕНИЙ
1.1. Среды программирования для разработки мобильных приложений
1.1.1. Программные средства разработки мобильных приложений
На сегодняшний день существует множество инструментов разработки мобильных приложений для создания мобильных приложений. Они отличаются друг от друга интерфейсом, используемым языком программирования, встроенными библиотеками, эмулятором, видом распространения среды и т.д.
Самыми распространенными средствами разработки мобильных приложений являются следующие:JDK, NetBeans IDE, Eclipse IDE, Android Studio, Intel XDK, IntelliJ IDEA, JDeveloper, BlueJ, Geany,Marmalade SDK и др.
Java Development Kit (JDK) является одним из трех основных технологий, используемых в программировании на языке Java. К ним также относятся JVM (Java Virtual Machine) и JRE (Java Runtime Environment). JVM отвечает за исполнение Java-программы. JRE представляет собой пакет инструментов для запуска Java-кода, создает и запускает JVM. JRE может использоваться, как отдельный компонент для простого запуска Java-программ или быть частью JDK.JDK позволяет разработчикам создавать программы, которые могут выполняться и запускаться посредством JVM и JRE. JDK представляет собой пакет инструментов для разработки программного обеспечения. JDK требуется JRE, потому что запуск программ является неотъемлемой частью их разработки.КаждыйJDK содержит компилятор Java.
КомпиляторJava — это программа, способная принимать исходные файлы с расширением .java, которые являются обычным текстом и превращать их в исполняемые файлы с расширением .class.
NetBeans IDE - эта интегрированная среда разработки с открытым кодом для разработчиков программного обеспечения. NetBeans IDE предоставляет все средства для создания приложений рабочей среды, корпоративных, мобильных и веб-приложений на языках Java, C/C++, а также на других динамических языках. NetBeans IDE может работать на разных платформах, как Windows, Linux, Solaris и Mac.
NetBeans IDE отличается простотой установки и удобством использования и не требует дополнительной настройки. NetBeans IDE содержит все, что нужно для разработки плагинов и приложений на основе NetBeans Platform.NetBeans Platform — платформа для разработки модульных настольных Swing-приложений. Приложения могут динамически загружать другие модули. Любое приложение может включить модуль Обновления, чтобы позволить пользователям загружать обновления для программ и модулей в работающее приложение.
Eclipse - это интегрированная среда разработки (IDE) для разработки приложений с использованием языка программирования Java и других языков программирования, таких как C / C ++, Python, PERL, Ruby и т. д.Платформа Eclipse обеспечивает основу для Eclipse IDE, состоит из плагинов и предназначена для расширения с помощью дополнительных плагинов. Eclipse может использоваться для разработки многофункциональных клиентских приложений, интегрированных сред разработки и других инструментов.
Eclipse может использоваться в качестве IDE для любого языка программирования, для которого доступен плагин.Проект Java Development Tools (JDT) предоставляет плагин, который позволяет использовать Eclipse в качестве Java IDE, PyDev - это плагин, который позволяет использовать Eclipse в качестве Python IDE, C / C ++ Development Tools (CDT) - это плагин -в этом позволяет использовать Eclipse для разработки приложений с использованием C / C ++, плагин Eclipse Scala позволяет Eclipse использовать IDE для разработки приложений Scala, а PHPeclipse представляет собой плагин для затмения, который предоставляет полный инструмент разработки для PHP.
JDeveloper –интегрированная среда разработки программного обеспечения. Предоставляет возможность для разработки на языках программирования Java, JavaScript, BPEL, PHP, SQL, PL/SQL и на языках разметки HTML, XML. JDeveloper покрывает весь жизненный цикл разработки программного обеспечения от проектирования, кодирования, отладки, оптимизации, профилирования и развёртывания.
BlueJ – среда разработки программного обеспечения на языке Java, созданная в основном для использования в обучении, но также подходящая для разработки небольших программ.BlueJ была разработана для поддержки обучения объектно-ориентированному программированию и её дизайн отличается от других сред разработки. Главный экран показывает структуру классов разрабатываемого приложения в графическом виде (на UML-подобной диаграмме), а объекты можно создавать и тестировать интерактивно. Подобная интерактивность совместно с ясным, простым интерфейсом пользователя позволяет легко экспериментировать с разрабатываемыми объектами. Концепции объектно-ориентированной разработки (классы, объекты, сообщение через вызов методов) интерактивны и наглядно представлены в интерфейсе программы.
Geany – свободная среда разработки программного обеспечения, написанная с использованием библиотеки GTK2. Geany не включает в свой состав компилятор. Для создания исполняемого кода используется GNU Compiler Collection или любой другой компилятор. Доступна для следующих операционных систем: BSD, Linux, Mac OS X, Solaris и Windows.
IntelliJ IDEA –интегрированная среда разработки программного обеспечения для языков программирования Java, JavaScript, Python, разработанная компанией JetBrains.Дизайн среды ориентирован на продуктивность работы программистов, позволяя сконцентрироваться на функциональных задачах.
В Android IDE реализован полный цикл разработки редактирование-компиляция-выполнение. Автодополнение кода, проверка ошибок в реальном времени, навигация по коду и запуск вашего приложения в одно касание, возможна разработка приложений для Андроида прямо на устройствах с ОС Андроид:
v андроид-планшет с клавиатурой может стать полноценным местом разработки;
v можно просматривать и редактировать код прямо на смартфоне;
v поддерживает разработку с использованием Java/Xml и C/C++;
v полностью совместима с проектами Eclipse;
v поддерживает профессиональную разработку приложений;
Android Studio — среда разработки под операционную систему Андроид, на базе IntelliJ IDEA. Программное обеспечение вышло в 2013 году и развивается до сих пор. В каждой новой версии Android Studio разработчик увеличивает функционал, оптимизирует процессы и другое.В комплекте Android Studio есть эмулятор, проверяющий корректную работу уже написанных утилит, приложений на разных конфигурациях. Возможности Android Studio:
v позволяет редактировать приложение в реальном времени, отображая его поведение одновременно на устройствах с различными диагоналями экранов;
v доступно мгновенное переключение на различные типы верстки и размеры экранов;
v раздел с подсказками и советами по оптимизации с тематическими разделами;
v средство взаимодействия с бета-тестерами;
v позволяет ускорить процесс разработки приложений, сделав его более продуктивным.
В Android SDK приложение состоит из 4 компонентов:
1. Activity – основная единица графического интерфейса (аналог окна или экранной формы).
2. ContentProviders управляет распределенным множеством данных приложения. Например, контент-провайдер в системе Android, управляющий информацией о контактах пользователя:
• данные могут храниться в файловой системе, в базе данных SQLite, в сети;
• позволяет другим приложениям при наличии у них соответствующих прав делать запросы или даже менять данные.
3. Intents– системные сообщения, позволяющие приложениям обмениваться информацией между собой и с операционной системой:
• поступление телефонного звонка;
• приход sms-сообщения;
• вставлена SD-карта;
• запущена новая активность;
• Intents – рекомендованный способ взаимодействия компонентов приложения.
4. Приложения, не имеющие GUI и выполняющиеся в фоновом режиме. Примеры сервисов:
• проверка электронной почты;
• получение гео-информации.
Intel XDK - это среда разработки HTML5, которая поможет вам создавать приложения для Интернета с использованием HTML5 и распространять их.Он позволяет быстросоздавать HTML5-приложения для устройств, работающих под управлением iOS, Android и Windows. Возможности Intel XDK:
v позволяет легко разрабатывать кроссплатформенные приложения;
v включает в себя инструменты для создания, отладки и сборки ПО, а также эмулятор устройств;
v поддерживает разработку для Android, Apple iOS, Microsoft Windows 8;
v языки разработкиHTML5 и JavaScript.
Intel XDK позволяет разрабатывать на любой платформе, потому что компиляция выполняется в облаке. Intel XDK не ограничен мобильными платформами. Intel XDK поставляется с редактором скобок с открытым исходным кодом от Adobe. В состав XDK также входит графический редактор, которого очень не хватает на всех мобильных платформах HTML5. Использованиекомпонентов HTML принесет пользу от редактора WYSIWYG. Intel XDK также поддерживает такие платформы, как Bootstrap и jQuery Mobile . Эти компоненты пользовательского интерфейса позволяют быстро создать интерфейс приложения.
Marmalade SDK – это набор инструментов для разработки кросс-платформенных приложений. Состоит из набора библиотек, образцов, инструментов и документаций необходимых для разработки, тестирования и развертывания приложений для мобильных устройств. Предназначендля создания игр и приложений на мобильные устройства.
В Marmalade SDK есть эмулятор и можно тестировать приложение выбрав определенную модель устройства и разрешение экрана. Поддерживает взаимодействие с камерой, микрофоном, акселерометром, GPS модулем. Также Marmalade SDK позволяет использовать С/С++ код для создания мобильных приложений. Разработчики могут использовать одну базу кода на максимальном количестве платформ, что означает выход на большыепроекты и больший потенциал. Marmalade позволяет делиться, интегрировать и снова использовать существующие наработки, технологии или сторонние инструменты, предоставляя высокоэффективную инфраструктуру для разработки игр, которая открыта, гибка и одинаково подходит для больших и малых проектов.
1.2. Языкыпрограммирования для разработки мобильных приложений
1.2.1. Язык Java для операционной системы Android
Java – объектно-ориентированный язык программирования, разработанный компанией Sun Microsystems. Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой виртуальной Java-машине вне зависимости от компьютерной архитектуры. Дата официального выпуска – 23 мая 1995 года.
Программы на Java транслируются в байт-код, выполняемый виртуальной машиной Java (JVM) – программой, обрабатывающей байтовый код и передающей инструкции оборудованию как интерпретатор.
Достоинством подобного способа выполнения программ является полная независимость байт-кода от операционной системы и оборудования, что позволяет выполнять Java-приложения на любом устройстве, для которого существует соответствующая виртуальная машина. Другой важной особенностью технологии Java является гибкая система безопасности, в рамках которой исполнение программы полностью контролируется виртуальной машиной. Любые операции, которые превышают установленные полномочия программы (например, попытка несанкционированного доступа к данным или соединения с другим компьютером), вызывают немедленное прерывание.
Часто к недостаткам концепции виртуальной машины относят снижение производительности. Ряд усовершенствований несколько увеличил скорость выполнения программ на Java:
· применение технологии трансляции байт-кода в машинный код непосредственно во время работы программы (JIT-технология) с возможностью сохранения версий класса в машинном коде,
· широкое использование платформенно-ориентированного кода (native-код) в стандартных библиотеках,
· аппаратные средства, обеспечивающие ускоренную обработку байт-кода (например, технология Jazelle, поддерживаемая некоторыми процессорами фирмы ARM).
Внутри Java существуют несколько основных семейств технологий:
· Java SE — Java Standard Edition, основное издание Java, содержит компиляторы, API, Java Runtime Environment; подходит для создания пользовательских приложений, в первую очередь — для настольных систем.
· Java EE — Java Enterprise Edition, представляет собой набор спецификаций для создания программного обеспечения уровня предприятия.
· Java ME — Java Micro Edition, создана для использования в устройствах, ограниченных по вычислительной мощности, например, в мобильных телефонах, КПК, встроенных системах;
· JavaFX — технология, являющаяся следующим шагом в эволюции Java как Rich Client Platform; предназначена для создания графических интерфейсов корпоративных приложений и бизнеса.
· Java Card — технология предоставляет безопасную среду для приложений, работающих на смарт-картах и других устройствах с очень ограниченным объёмом памяти и возможностями обработки.
Компанией Microsoft была разработана собственная реализация JVM (MSJVM), включавшаяся в состав различных операционных систем, начиная с Windows 98.
Microsoft JVM имела существенные отличия от Sun Java, во многом ломающие основополагающую концепцию переносимости программ между разными платформами:
· отсутствие поддержки программного интерфейса вызова удаленных методов (RMI);
· отсутствие поддержки технологии JNI;
· наличие нестандартных расширений, таких, как средства интеграции Java и DCOM, работающих только на платформе Windows.
Язык Java активно используется для создания мобильных приложений под операционную систему Android. При этом программы компилируются в нестандартный байт-код, для использования их виртуальной машиной Dalvik. Для такой компиляции используется дополнительный инструмент, а именно Software Development Kit, разработанный компанией Google.
Разработку приложений можно вести в среде Android Studio, NetBeans, в среде Eclipse, используя при этом плагин Android Development Tools (ADT) или в IntelliJ IDEA. Версия JDK при этом должна быть 5.0 или выше.
8 декабря 2014 года Android Studio признана компанией Google официальной средой разработки под ОС Android.
Некоторые платформы предлагают аппаратную поддержку выполнения для Java. К примеру, микроконтроллеры, выполняющие код Java на аппаратном обеспечении вместо программной JVM, а также основанные на ARM процессоры, которые поддерживают выполнение байткода Java через опцию Jazelle. Основные возможности:
· автоматическое управление памятью;
· расширенные возможности обработки исключительных ситуаций;
· богатый набор средств фильтрации ввода-вывода;
· набор стандартных коллекций: массив, список, стек и т. п.;
· наличие простых средств создания сетевых приложений (в том числе с использованием протокола RMI);
· наличие классов, позволяющих выполнять HTTP-запросы и обрабатывать ответы;
· встроенные в язык средства создания многопоточных приложений;
· унифицированный доступ к базам данных:
· на уровне отдельных SQL-запросов — на основе JDBC, SQLJ;
· на уровне концепции объектов, обладающих способностью к хранению в базе данных — на основе Java Data Objects (англ.) и Java Persistence API;
· поддержка обобщений (начиная с версии 1.5);
параллельное выполнение программ
1.2.2. Язык Dart для операционной системы Android
Dart - язык программирования, созданный Google. Dart позиционируется в качестве замены/альтернативы JavaScript. Один из разработчиков языка Марк Миллер написал, что JavaScript имеет фундаментальные изъяны, которые невозможно исправить, поэтому и был создан Dart.
10 октября 2011 была проведена официальная презентация языка Google Dart.
Dart представляет язык программирования общего назначения от компании Google, который предназначен прежде всего для разработки веб-приложений (как на стороне клиента, так и на стороне сервера) и мобильных приложений. Это значит, что одну и ту же программу на Dart можно компилировать под различные платформы - Windows, Android, iOS.
Dart - объектно-ориентированный язык. Все значения, которые используются в программе на Dart, представляют объекты.
В своем развитии Dart испытал влияние более ранних языков, таких как Smalltak, Java, JavaScript. Его синтаксис похож на синтаксис других языков.
Dart быстро развивается и текущая версия - 2.12.Для работы с Dart необходимо установить Dart SDK. Для этого нужно загрузить zip-архив с SDK с адреса https://dart.dev/tools/sdk/archive и распаковать его на жестком диске.На странице загрузки есть пакеты для Windows, Linux, MacOS. Также доступны различные сборки для разработчиков.
Любое приложение на языке Dart должно иметь функцию, которая называется main. Эта функция имеет тип void и не принимает никаких параметров, поэтому после названия функции идут пустые скобки.
Тело функции помещается в фигурные скобки. Чтобы каждый раз при запуске программы не надо было вводить полный путь до утилиты dart.exe, можно добавить путь к утилите в переменные среды.
С помощью утилиты dart.exe можно запустить программу, однако можно создать исполняемый файл, чтобы в любое время его можно было запускать без обращения к dart.exe и переносить на другой компьютер с той же операциионной системой. Для этого в SDK есть другая утилита - dart2native.exe, которая позволяет скомпилировать нативный исполняемый файл программы. В качестве параметра она принимает исходный файл, который надо скомилировать. А после флага -o можно указать путь и название файла, который будет скомпилирован.
1.3. Жизненный цикл мобильных приложений
1.3.1. Жизненный цикл приложения
Активность – окно, несущее графический интерфейс пользователя. Окно активности обычно занимает весь экран устройства, однако вполне возможно создавать полупрозрачные или плавающие диалоговые окна. Мобильные приложения обычно являются многооконными, т. е. содержат несколько активностей, по одной на каждое окно. Одна из активностей определяется как "главная", и именно ее пользователь видит при первом запуске приложения.
Каждый экран приложения является наследником класса Activity. Для создания активности необходимо создать класс-наследник класса Activity напрямую или через любого его потомка. В этом классе необходимо реализовать все методы, вызываемые системой для управления жизненным циклом активности. Таких методов семь:
onCreate() |
- метод, вызываемый системой при создании активности. В реализации метода необходимо инициализировать основные компоненты активности и в большинстве случаев вызвать метод setContentView() для подключения соответствующего XML-файла компоновки (layoutfile). После метода onCreate() всегда вызывается метод onStart(). |
onRestart() |
- метод, вызываемый системой при необходимости запустить приостановленную активность. После этого метода всегда вызывается метод onStart(). |
onStart() |
- метод, вызываемый системой непосредственно перед тем, как активность станет видимой для пользователя. После этого метода вызывается onResume(). |
onResume() |
- метод, вызываемый системой непосредственно перед тем, как активность начнет взаимодействовать с пользователем. После этого метода всегда вызывается onPause(). |
onPause() |
- метод, вызываемый системой при потере активностью фокуса. В этом методе необходимо фиксировать все изменения, которые должны быть сохранены за пределами текущей сессии. После этого метода вызывается onResume(), если активность вернется на передний план, или onStop(), если активность будет скрыта от пользователя. |
onStop() |
- метод, вызываемый системой, когда активность становится невидимой для пользователя. После этого метода вызывается либо onRestart(), если активность возвращается к взаимодействию с пользователем, либо onDestroy(), если активность уничтожается. |
onDestroy() |
- метод, вызываемый системой перед уничтожением активности. Этот метод вызывается либо когда активность завершается, либо когда система уничтожает активность, чтобы освободить ресурсы. Можно различать эти два сценария с помощью метода isFinishing(). Это последний вызов, который может принять активность. |
После завершения метода onStart() вызывается метод onRestoreInstanceState, который восстанавливаеть сохраненное состояние из объекта Bundle, который передается в качестве параметра. Но следует учитывать, что этот метод вызывается только тогда, когда Bundle не равен null и содержит ранее сохраненное состояние. Припервом запуске приложения объект Bundle будет иметь значение null, поэтому и метод onRestoreInstanceState не будет вызываться.
МетодonSaveInstanceState
Метод onSaveInstanceState вызывается после метода onPause(), до вызова onStop(). В onSaveInstanceState производится сохранение состояния приложения в передаваемый в качестве параметра объект Bundle.
Рис. 1.1.Жизненный цикл активности
Переходмежду состояниями activity можно выразить следующей схемой:
Рис. 1.2.Переход между состояниями activity
Расмотрим несколько ситуаций. Если работаем с Activity и затем переключаемся на другое приложение, либо нажимаем на кнопку Home, то у Activity вызывается следующая цепочка методов: onPause -> onStop. Activity оказывается в состоянии Stopped. Если пользователь решит вернуться к Activity, то вызывается следующая цепочка методов: onRestart -> onStart -> onResume.
Другая ситуация, если пользователь нажимает на кнопку Back (Назад), то вызывается следующая цепочка onPause -> onStop -> onDestroy. В результате Activity уничтожается. Если вдруг захотим вернуться к Activity через диспетчер задач или заново открыв приложение, то activity будет заново пересоздаваться через методы onCreate -> onStart -> onResume
Запускприложения:
onCreate() → onStart() → onResume()
Нажата кнопку Назад для выхода из приложения:
onPause() → onStop() → onDestroy()
Нажата кнопка Домой:
onPause() → onStop()
После нажатия кнопки Домой, когда приложение запущено из списка недавно открытых приложений или через значок:
onRestart() → onStart() → onResume()
Когда запускается другое приложение из области уведомлений или открывается приложение Настройки:
onPause() → onStop()
Нажата кнопка Назад в другом приложении или в Настройках и ваше приложение стало снова видимым:
onRestart() → onStart() → onResume()
Открывается диалоговое окно:
onPause()
Диалоговое окно закрывается:
onResume()
Кто-то звонит на телефон:
onPause() → onResume()
Пользователь отвечает на звонок:
onPause()
Разговор окончен:
onResume()
Экран телефона гаснет:
onPause() → onStop()
Экран снова включён:
onRestart() → onStart() → onResume()
При повороте активность проходит через цепочку различных состояний. Порядокследующий.
onPause()->onStop()->onDestroy()->onCreate()->onStart()->onResume()
Порядоквызова методов:
после onCreate() - onStart()
после onRestart() - onStart()
после onStart() - onResume() или onStop()
после onResume() - onPause()
после onPause() - onResume() или onStop()
после onStop() - onRestart() или onDestroy()
после onDestroy() – ничего
1.3.2. Управление жизненным циклом
Можно управлять этими событиями жизненного цикла, переопределив соответствующие методы. Для этого возьмем из прошлой главы класс MainActivity и изменим его следующим образом:
Для логгирования событий здесь используется класс android.util.Log. В примере обрабатываются все ключевые методы жизненного цикла. Вся обработка сведена к вызову метода Log.d(), в который передается TAG - случайное строковое значение и строка, которая выводится в консоли logcat внизу Android Studio в окне Android Monitor, выполняя роль отладочной информации. Если эта консоль по умолчанию скрыта, то можно перейти к ней через пункт меню View -> Tool Windows -> Android Monitor. И при запуске приложения сможно увидеть в окне logcat отладочную информацию, которая определяется в методах жизненного цикла activity: |
Рис. 1.3. Окно logcat
При создании экранов графического интерфейса пользователя наследуется класс Activity и используются представления (View) для взаимодействия с пользователем.
Каждая Активность – это экран, который приложение может показывать пользователям. Чем сложнее создаваемое приложение, тем больше экранов (Активностей) потребуется. При создании приложения потребуется, как минимум, начальный (главный) экран, который обеспечивает основу пользовательского интерфейса приложения. При необходимости этот интерфейс дополняется второстепенными Активностями, предназначенными для ввода информации, ее вывода и предоставления дополнительных возможностей. Запуск (или возврат из) новой Активности приводит к «перемещению» между экранами UI(UserInterfeys).
Большинство Активностей проектируются таким образом, чтобы использовать все экранное пространство, но можно также создавать полупрозрачные или плавающие диалоговые окна.
Для создания новой Активности наследуется класс Activity. Внутри реализации класса необходимо определить пользовательский интерфейс и реализовать требуемый функционал. Базовый каркас для новой Активности показан ниже:
package com.example.myapplication;
import android.app.Activity;
import android.os.Bundle;
public
class MyActivity extends Activity {
/** Вызывается при создании Активности */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
Базовый класс Activity представляет собой пустой экран, который не особенно полезен, поэтому первое, что вам нужно сделать, это создать пользовательский интерфейс с помощью Представлений (View) и разметки(Layout).
Представления (View) – это элементы UI, которые отображают информацию и обеспечивают взаимодействие с пользователем. Android предоставляет несколько классов разметки (Layout), называемых также View Groups, которые могут содержать внутри себя несколько Представлений, для создания пользовательского интерфейса приложения.
Чтобы назначить пользовательский интерфейс для Активности, внутри обработчика onCreate используется метод setContentView:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
setContentView(textView);
}
В этом примере в качестве UI для Активности выступает объект класса TextView.
При создании реальных приложений чаще применяется метод проектирования, использующий ресурсы приложения, отделенные от кода. Такой подход позволяет создавать приложения, обеспечивающие высокое качество реализации UI, не зависящее от условий работы программы: приложения предлагают удобный для пользователя языковый интерфейс (зависит от локализации), слабо зависят от разрешения и размеров экрана и т. д.).
Самое главное, что такая адаптация приложения к новым условиям не требует каких-либо изменений в коде приложения, нужно только обеспечить необходимые ресурсы (картинки, локализованные строки и т. п..). Это стандартный для Android подход показан ниже:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
Для использования Активности в приложении ее необходимо зарегистрировать в Манифесте путем добавления элемента <activity> внутри узла <application>, в противном случае ее невозможно будет использовать. Ниже показано, как создать элемент <activity> для Активности MyActivity:
<activity android:label="@string/app_name"
android:name=".MyActivity">
</activity>
В теге <activity> можно добавлять элементы <intent-filter> для указания Намерений (Intent), которые Активность будет отслеживать. Каждый «Фильтр Намерений» определяет одно или несколько действий (action) и категорий (category), которые поддерживаются Активностью. Важно знать, что Активность будет доступна из главного меню запуска приложений только в случае, если в Манифесте для нее указан <intent-filter> для действия MAIN и категории LAUNCHER, как показано на примере:
<activity
android:label="@string/app_name"
android:name=".MyActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Для создания приложений, правильно управляющих ресурсами предоставляющих пользователю удобный интерфейс, важно хорошее понимание жизненного цикла Активности. Это связано с тем, что приложения Android не могут контролировать свой жизненный цикл, ОС сама управляет всеми процессами и, как следствие, Активностями внутри них. При этом, состояние Активности помогает ОС определить приоритет родительского для этой Активности Приложения (Application). А приоритет Приложения влияет на то, с какой вероятности его работа (и работа дочерних Активностей) будет прервана системой.
Состояние каждой Активности определяется ее позицией в стеке (LIFO) Активностей, запущенных в данный момент. При запуске новой Активности представляемый ею экран помещается на вершину стека. Если пользователь нажимает кнопку «назад» или эта Активности закрывается каким-то другим образом, на вершину стека перемещается (и становится активной) нижележащая Активность. Данный процесс показан на диаграмме:
Рис 1.4. Состояние активности
На приоритет приложения влияет его самая приоритетная Активность. Когда диспетчер памяти ОС решает, какую программу закрыть для освобождения ресурсов, он учитывает информацию о положении Активности в стеке для определения приоритета приложения.
Активности могут находиться в одном из четырех возможных состояний:
· активное (аctive). Активность находится на переднем плане (на вершине стека) и имеет возможность взаимодействовать с пользователем. Android будет пытаться сохранить ее работоспособность любой ценой, при необходимости прерывая работу других Активностей, находящихся на более низких позициях в стеке для предоставления необходимых ресурсов. При выходе на передний план другой Активности работа данной Активности будет приостановлена или остановлена.
· приостановленное (рaused). Активность может быть видна на экране, но не может взаимодействовать с пользователем: в этот момент она приостановлена. Это случается, когда на переднем плане находятся полупрозрачные или плавающие (например, диалоговые) окна. Работа приостановленной Активности может быть прекращена, если ОС необходимо выделить ресурсы Активности переднего плана. Если Активность полностью исчезает с экрана, она останавливается.
· остановленное (stopped). Активность невидима, она находится в памяти, сохраняя информацию о своем состоянии. Такая Активность становится кандидатом на преждевременное закрытие, если системе потребуется память для чего-то другого. При остановке Активности разработчику важно сохранить данные и текущее состояние пользовательского интерфейса (состояние полей ввода, позицию курсора и т. д.). Если Активность завершает свою работу или закрывается, он становится неактивным.
· неактивное (inactive). Когда работа Активности завершена, и перед тем, как она будет запущена, данная Активности находится в неактивном состоянии. Такие Активности удаляются из стека и должны быть (пере)запущены, чтобы их можно было использовать.
Изменение состояния приложения – недетерминированный процесс и управляется исключительно менеджером памяти Android. При необходимости Android вначале закрывает приложения, содержащие неактивные Активности, затем остановленные и, в крайнем случае, приостановленные.
Для обеспечения полноценного интерфейса приложения, изменения его состояния должны быть незаметными для пользователя. Меняя свое состояние с приостановленного на остановленное или с неактивного на активное, Активность не должна внешне меняться. При остановке или приостановке работы Активности разработчик должен обеспечить сохранение состояния Активности, чтобы его можно было восстановить при выходе Активности на передний план. Для это в классе Activity имеются обработчики событий, переопределение которых позволяет разработчику отслеживать изменение состояний Активности.
Обработчики событий класса Activity позволяют отслеживать изменения состояний соответствующего объекта Activity во время всего жизненного цикла:
Рис. 1.5. Отслеживание изменения состояний активности
Ниже показан пример с заглушками для таких методов – обработчиков событий:
package com.example.myapplication;
import android.app.Activity;
import android.os.Bundle;
public class MyActivity extends Activity {
// Вызывается при создании Активности
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Инициализирует Активность.
}
// Вызывается после завершения метода onCreate
// Используется для восстановления состояния UI
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Восстановить состояние UI из объекта savedInstanceState.
// Данный объект также был передан методу onCreate.
}
// Вызывается перед тем, как Активность снова становится видимой
@Override
public void onRestart(){
super.onRestart();
// Восстановить состояние UI с учетом того,
// что данная Активность уже была видимой.
}
// Вызывается когда Активность стала видимой
@Override
public void onStart(){
super.onStart();
//Проделать необходимые действия для
// Активности, видимой на экране
}
// Должен вызываться в начале видимого состояния.
// На самом деле Android вызывает данный обработчик только
// для Активностей, восстановленных из неактивного состояния
@Override
public void onResume(){
super.onResume();
// Восстановить приостановленные обновления UI,
// потоки и процессы, «замороженные, когда
// Активность была в неактивном состоянии
}
// Вызывается перед выходом из активного состояния,
// позволяя сохранить состояние в объекте savedInstanceState
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Объект savedInstanceState будет в последующем
// передан методам onCreate и onRestoreInstanceState
super.onSaveInstanceState(savedInstanceState);
}
// Вызывается перед выходом из активного состояния
@Override
public void onPause(){
// «Заморозить» обновления UI, потоки или
// «трудоемкие» процессы, ненужные, когда Активность
// не на переднем плане
super.onPause();
}
// Вызывается перед выходом из видимого состояния
@Override
public void onStop(){
// «Заморозить» обновления UI, потоки или
// «трудоемкие» процессы, ненужные, когда Активность
// не на переднем плане.
// Сохранить все данные и изменения в UI, так как
// процесс может быть в любой момент убит системой
super.onStop();
}
// Вызывается перед уничтожением активности
@Override
public void onDestroy(){
// Освободить все ресурсы, включая работающие потоки,
// соединения с БД и т. д.
super.onDestroy();}
}
Когда компонент приложения запускается при отсутствии других работающих компонентов , система Android запускает новый процесс Linux для приложения с одним потоком выполнения. По умолчанию все компоненты одного приложения работают в одном процессе и потоке (называется «главным потоком»). Если компонент приложения запускается при наличии процесса для этого приложения (так как существует другой компонент из приложения), тогда компонент запускается в этом процессе и использует тот же поток выполнения. Однако можно организовать выполнение других компонентов приложения в отдельных процессах и создавать дополнительный поток для любого процесса.
В этом документе обсуждается работа процессов и потоков в приложении Android.
Процессы
По умолчанию все компоненты одного приложения работают в одном процессе. Однако, если необходимо контролировать, к какому процессу принадлежат определенный компонент, можно сделать это в файле манифеста.
Запись манифеста для каждого типа элементов компонента — <activity>, <service>, <receiver> и <provider> — поддерживает атрибут android:process, позволяющий задавать процесс, в котором следует выполнять этот компонент.
Можно установить этот атрибут так, чтобы каждый компонент выполнялся в собственном процессе, или так, чтобы только некоторые компоненты совместно использовали один процесс.
Можно также настроить процесс android:process так, чтобы компоненты разных приложений выполнялись в одном процессе, при условии что приложения совместно используют один идентификатор пользователя Linux и выполняют вход с одним сертификатом.
Элемент <application> также поддерживает атрибут android:process, позволяющий задать значение по умолчанию, которое применяется ко всем компонентам.
Android может остановить процесс в некоторой точке, когда не хватает памяти и она необходима другим процессам, которые обслуживают пользователя в данный момент. Работа компонентов приложения, работающих в этом процессе, последовательно останавливается. Процесс для этих компонентов запускается повторно, когда для них появляется работа.
Принимая решение о прерывании процессов, система Android взвешивает их относительную важность для пользователя. Например, более вероятно выключение процессов, содержащих действия, которые не отображаются на экране, по сравнению с процессом, содержащим видимые действия. Следовательно, решение о прерывании процесса зависит от состояния компонентов, работающих в этом процессе. Ниже обсуждаются правила, на основании которых принимается решение о выборе прерываемых процессов.
Жизненный цикл процесса
Система Android пытается сохранять процесс приложения как можно дольше, но в конечном счете вынуждена удалять старые процессы, чтобы восстановить память для новых или более важных процессов. Чтобы определить, какие процессы сохранить, а какие удалить, система помещает каждый процесс в «иерархию важности» на основе компонентов, выполняющихся в процессе, и состояния этих компонентов. Процессы с самым низким уровнем важности исключаются в первую очередь, затем исключаются процессы следующего уровня важности и т. д., насколько это необходимо для восстановления ресурсов системы.
В иерархии важности предусмотрено пять уровней. В следующем списке представлены различные типы процессов в порядке важности (первый процесс является наиболее важным и удаляется в последнюю очередь):
1. Процесс переднего плана
Процесс, необходимый для текущей деятельности пользователя. Процесс считается процессом переднего плана, если выполняется любое из следующих условий:
• он содержит действие Activity, с которым взаимодействует пользователь (вызван метод Activity onResume()).
• он содержит службу Service, связанную с действием, с которым взаимодействует пользователь.
• он содержит службу Service, которая выполняется "на переднем плане", — службу, которая называется startForeground().
• он содержит службуService, которая выполняет один из обратных вызовов жизненного цикла (onCreate(), onStart() или onDestroy()).
• он содержит ресивер BroadcastReceiver, который выполняет метод onReceive().
Обычно одновременно работает лишь несколько процессов переднего плана. Они уничтожаются только в крайнем случае, если памяти остается так мало, что они не могут продолжать совместную работу. Обычно в этот момент устройство достигло состояния разбиения памяти на страницы, поэтому для того, чтобы пользовательский интерфейс откликался на действия пользователя, необходимо удаление некоторых процессов на переднем плане.
2. Видимые процессы
Процессы, которые не содержат компонентов переднего плана, но могут влиять на отображение на экране. Процесс считается видимым, если выполняется любое из следующих условий:
• Он содержит действие Activity, которое не находится на переднем плане, но видно пользователю (вызван метод onPause()). Например, это может происходить, если действие на переднем плане запустило диалоговое окно, которое позволяет видеть предыдущее действие позади него.
• Он содержит службу Service, связанную с видимым действием или действием переднего плана.
Видимый процесс считается исключительно важным, его следует удалять только в случае, если требуется сохранить работу всех процессов переднего плана.
3. Служебный процесс
Процесс, который выполняет службу, запущенную с помощью метода startService(), и не попадает ни в одну из двух категорий более высокого уровня. Хотя служебные процессы не связаны непосредственно с тем, что видит пользователь, они обычно выполняют важные для пользователя действия (например, воспроизводят музыку в фоновом режиме или загружают данные в сеть), поэтому система сохраняет их выполнение, если памяти достаточно для их работы наряду со всеми видимыми процессами и процессами переднего плана.
4. Фоновый процесс
Процесс, содержащий действия, которые не видны пользователю в настоящее время (вызван метод onStop() действия). Эти процессы не оказывают непосредственного воздействия на работу пользователя, и система может удалить их в любой момент, чтобы освободить память для процессов переднего плана, видимых или служебных процессов. Обычно выполняется множество фоновых процессов, поэтому они хранятся в списке LRU (недавно использованные), чтобы процессы, содержащие самые недавние действия, которые видел пользователь, удалялись в последнюю очередь. Если для действия правильно реализованы методы жизненного цикла, и действие сохраняет текущее состояние, удаление процесса этого действия не оказывает видимого воздействия на работу пользователя, так как когда пользователь возвращается к этому действию, оно восстанавливает все элементы своего видимого состояния.
5. Пустой процесс
Процесс, не содержащий никаких компонентов активного приложения. Единственная причина сохранять процесс такого типа — это кэширование, которое улучшает время следующего запуска компонента в этом процессе. Система часто удаляет эти процессы для равномерного распределения всех системных ресурсов между кэшем процесса и кэшем базового ядра.
Система Android относит процесс к максимально высокому уровню на основе важности компонентов, активных в процессе в текущее время. Например, если процесс содержит служебное и видимое действие, процесс считается видимым, а не служебным процессом.
Кроме того, уровень процесса может быть повышен, поскольку имеются другие процессы, зависимые от него. Например, процесс, обслуживающий другой процесс, не может иметь уровень ниже уровня обслуживаемого процесса. Например, если поставщик контента в процессе A обслуживает клиента в процессе B или служебный процесс A связан с компонентом в процессе B, процесс A всегда считается не менее важным, чем процесс B.
Так как процесс, выполняющий службу, оценивается выше процесса с фоновыми действиям, действие, запускающее долговременную операцию, может запустить службу для этой операции, а не просто создать рабочий поток, особенно в случае, если операция продлится дольше действия. Например, действие, которое загружает изображение на веб-сайт, должно запустить службу для выполнения загрузки, так что загрузка может продолжаться в фоновом режиме даже после выхода пользователя из действия. Использование службы гарантирует, что операция будет иметь приоритет не ниже «служебного процесса», независимо от того, что происходит с действием. По этой же причине ресиверы должны использовать службы, а не просто ставить в поток операции, требующие много времени для выполнения.
Потоки
При запуске приложения система создает поток выполнения для приложения, который называется «главным». Этот поток очень важен, так как он отвечает за диспетчеризацию событий на виджеты соответствующего интерфейса пользователя, включая события графического представления. Он также является потоком, в котором приложение взаимодействует с компонентами из набора инструментов пользовательского интерфейса Android (компонентами из пакетов android.widget и android.view). По существу, главный поток — это то, что иногда называют потоком пользовательского интерфейса.
Система не создает отдельного потока для каждого экземпляра компонента. Все компоненты, которые выполняются в одном процессе, создают экземпляры в потоке пользовательского интерфейса, и системные вызовы каждого компонента отправляются из этого потока. Поэтому методы, которые отвечают на системные обратные вызовы (такие как метод onKeyDown() для сообщения о действиях пользователя или метод обратного вызова жизненного цикла), всегда выполняются в потоке пользовательского интерфейса процесса.
Например, когда пользователь нажимает кнопку на экране, поток пользовательского интерфейса вашего приложения отправляет событие нажатия в виджет, который, в свою очередь, устанавливает кнопку в нажатое состояние и отправляет запрос на аннулирование в очередь событий. Поток пользовательского интерфейса исключает запрос из очереди и уведомляет виджет, что он должен отобразиться повторно.
Когда приложение выполняет интенсивную работу в ответ на действия пользователя, эта одиночная модель потока может показывать плохую производительность, если приложение реализовано неправильно. То есть, если все происходит в потоке пользовательского интерфейса, выполнение долговременных операций, таких как сетевой доступ или запросы к базе данных, будет блокировать весь пользовательский интерфейс. Когда поток заблокирован, не могут обрабатываться никакие события, включая события изменения отображения. С точки зрения пользователя приложение выглядит зависшим.
Еслипоток пользовательского интерфейса заблокирован более нескольких секунд (в настоящее время около 5 секунд), отображается печально известное диалоговое окно «приложение не отвечает». После этого недовольный пользователь может выйти из вашего приложения и удалить его.
Кроме того, набор инструментов пользовательского интерфейса Android не является потокобезопасным. Поэтому, вы не должны работать с пользовательским интерфейсом из рабочего потока. Манипуляции с пользовательским интерфейсом необходимо выполнять из потока пользовательского интерфейса. Таким образом, существует только два правила однопоточной модели Android:
1. Не блокируйте поток пользовательского интерфейса
2. Не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса
1. Какие платформы используются для создания ОС мобильных приложений?
2. Абстрактные классы и их использование
3. Анонимные классы и их использование
4. Использование шаблонов и контейнеров
5. Зачем нужны абстрактные классы?
6. Какие платформы используются для создания ОС мобильных приложений?
7. Абстрактные классы и их использование
8. Анонимные классы и их использование
9. Использование шаблонов и контейнеров
10. Зачем нужны абстрактные классы?
2. РАБОТА С МОБИЛЬНЫМИ УСТРОЙСТВАМИ
2.1.Понятие мобильнойоперационной системы
2.1.1.Понятие ОС в мобилных устройствах
Жизнь современного человека практически немыслима без мобильных девайсов. Их качество в большей степени зависит от аппаратных характеристик, а вот удобство пользования в большей степени зависит от мобильной операционной системы. Поэтому очень важно подобрать не только хорошие аппаратные характеристики, нужно еще выбрать операционную систему, с которой удобно будет работать.
Мобилная операционная система – это системы предназначенные для управления мобильных устройств. Самые распространенные операционные системы для мобильных устройств:
• Symbian OS
• Windows Mobile
• Google Android
• Apple iOS
• BlackBerry OS
2.1.2. ОС Symbian
ОперационнаясистемаSymbian была самой популярной операционной системы для мобильных устройств благодаря поддержке фирмы Nokia. Важную роль также сыграло то, что система имеет небольшой размер, а также то, что графический интерфейс и ядро системы отделены друг от друга. Это позволило легко портировать ее для различных мобильных устройств. Позднее была добавлена многозадачность.
Каждый разработчик создавал свой дистрибутив этой операционной системы в зависимости от ограничений аппаратной платформы, под которую она разрабатывалась. Так появились версии Series 60, Series 80, Series 90, UIQ и MOAP. Каждая версия обладала своими особенностями, что делало необходимым под каждую версию разрабатывать свои приложения. Это было неудобно, поэтому после появления Windows Mobile, Android и iPhoneOS утратила свою популярность среди производителей мобильных девайсов. Так компании Sony Ericsson и Samsung объявили что не будут больше поддерживать эту операционную систему. На данный момент из крупных производителей мобильных девайсов только компания Nokia использует эту ОС для своих смартфонов.
Достоинства ОС Symbian: низкие требования к памяти и процессору, функция освобождения неиспользуемой памяти, стабильность, малое количество вирусов для этой платформы, быстро выходят новые версии и исправляются нестабильности, большое количество программ.
Недостатки ОС Symbian: для связи с ПК нужно устанавливать дополнительный софт, несовместимость программ для старых и новых версий.
Операционная система Windows Mobile разработана мировым лидером в производстве операционных систем – компанией Microsoft. Эта система использует такой же программный интерфейс, что и настольная версия. Это делает написание программ более простым, а пользователям нравится удобный и понятный интерфейс, знакомый им с настольной Windows. Windows Mobile является компонентной, многозадачной, много поточной и много платформенной операционной системой. Благодаря этому она сыскала широкое распространение на мобильных устройствах.
Достоинства ОС Windows Mobile: схожесть с настольной версией, удобная синхронизация, в комплекте идут офисные программы, многозадачность.
Недостатки ОС Windows Mobile: высокие требования к оборудованию, наличие большого числа вирусов, нестабильности в работе.
ОС Android — одна из самых молодых мобильных ОС, основанная на базе операционной системы Linux и разрабатываемая Open Handset Alliance (OHA) при поддержке Google. Исходный код находится в открытом доступе, благодаря чему любой разработчик может создать свою версию этой мобильной ОС. Разработчикам приложений выдвинуто небольшое количество ограничений, благодаря чему существует множество как платных, так и бесплатных приложений, которые можно удобно загрузить с Android Market.
Достоинства ОС Android: гибкость, открытые исходные коды, множество программ, высокое быстродействие, удобное взаимодействие с сервисами от Google, многозадачность.
Недостатки ОС Android: множество актуальных версий – для многих устройств новая версия входит слишком поздно или не появляется вовсе, поэтому разработчикам приходится разрабатывать приложения, ориентируясь на более старые версии, высокая предрасположенность к хакерским атакам из-за открытости кода, почти всегда требует доработок.
OС iPhone мобильная операционная система от компании Apple. Данная система получила распространение только на продуктах компании Apple. Применяется в iPhone, iPod, iPad а также телевизионной приставке AppleTV.
Достоинства OС iPhone: удобство пользования, качественная служба поддержки, регулярные обновления, устраняющие многие проблемы в работе, возможность купить в App Store множество различных программ.
Недостатки OС iPhone: необходимость джайлбрейка для установки неофициальных приложений, заблокированный характер ОС, отсутствие многозадачности, нет встроенного редактора документов.
Операционная система Palm появилась в 1996 году. Применялась в КПК. Была очень распространена из-за широких возможностей и удобства пользователей. К настоящему моменту практически не применялась, но в этом году разработчика поглотила компания HP. Благодаря этому появились надежды на воскрешение некогда популярной среди КПК операционной системы.
Достоинства ОС Palm: нетребовательна к ресурсам, очень удобный интерфейс пользователя, удобная синхронизация с ПК, надежность.
Недостатки ОС Palm: отсутствует полноценная многозадачность, не развиты мультимедийные функции, система не развивается.
Операционная система BlackBerry работает исключительно на устройствах, выпускаемых компанией Research In Motion Limited (RIM). Ориентирована на корпоративных пользователей. Свое название получила от смартфонов для которых создавалась, так как клавиатура смартфонов были похожи на ягоду ежевики. Смартфоны с этой операционной системой получили распространение в корпоративной среде, благодаря сложности перехвата сообщений.
Достоинства ОС BlackBerry: удобное пользование электронной почтой, легкая синхронизация с ПК, широкие возможности настроек безопасности.
Недостатки ОС BlackBerry: оптимизирована для вывода только текстовой информации, качество работы с графикой не очень хорошее, не очень удобный браузер.
Как видим, технические характеристики устройства отнюдь не главный параметр при выборе мобильного девайса.
2.2. Платформа и архитектура мобильных операционных систем
2.2.1. Платформа Андроид и его основа
Android – операционная система для мобильных устройств: смартфонов, планшетных компьютеров, КПК. В настоящее время именно Android является самой широко используемой операционной системой для мобильных устройств.
В 2003 году в городе Пало Альто Энди Рубин с Рич Майнер, Ник Сирс и Крис Уайтом основали компанию Android Inc. Поначалу в компании занимались проектированием мобильных гаджетов, которые на основе геолокационных данных автоматически подстраивались под нужды пользователей.
В августе 2005 года Android Inc. стала дочерней компанией Google. Энди Рубин, Рич Майнер и Крис Уайт остались в Android Inc. и начали работать над операционной системой, базирующейся на ядре Linux. В Google задумали реализовать мощнейшую платформу, пригодную к использованию на тысячах различных моделей телефонов. В связи с этим был создан Open Handset Alliance (OHA) - консорциум, состоящий из более 80 компаний, направляющий свои усилия на разработку открытых стандартов для мобильных устройств. Всостав OHA входяттакиегиганты, как Google, HTC, Sony, Dell, Intel, Motorola, Qualcomm, Texas Instruments, Samsung Electronics, LG Electronics, T-Mobile, Sprint Corporation, NVIDIA имногиедругие.
Первая версия Android была представлена 23 сентября 2008 года, версии было дано название Apple Pie. Далее так повелось, что название каждой очередной версии представляет какой-либо десерт, при этом первые буквы наименований в порядке версий соответствуют буквам латинского алфавита по порядку.
Платформа Android объединяет операционную систему, построенную на основе ядра ОС Linux, промежуточное программное обеспечение и встроенные мобильные приложения. Разработка и развитие мобильной платформы Android выполняется в рамках проекта AOSP (AndroidOpen Source Project) под управлением OHA (OpenHandset Alliance), руководит всем процессом поисковый гигант Google.
Android поддерживает фоновое выполнение задач; предоставляет богатую библиотеку элементов пользовательского интерфейса; поддерживает 2D и 3D графику, используя OpenGL стандарт; поддерживает доступ к файловой системе и встроенной базе данных SQLite.
С точки зрения архитектуры, система Android представляет собой полный программный стек, в котором можно выделить следующие уровни:
· базовый уровень (Linux Kernel) - уровень абстракции между аппаратным уровнем и программным стеком;
· набор библиотек и среда исполнения (Libraries & Android Runtime) обеспечивает важнейший базовый функционал для приложений, содержит виртуальную машину Dalvik и базовые библиотеки Java необходимые для запуска Android приложений;
· уровень каркаса приложений (Application Framework) обеспечивает разработчикам доступ к API, предоставляемым компонентами системы уровня библиотек;
· уровень приложений (Applications) - набор предустановленных базовых приложений.
Наглядное изображение архитектуры на рисунке 2.1.
В основании компонентной иерархии лежит ядро ОС Linux 2.6, служит промежуточным уровнем между аппаратным и программным обеспечением, обеспечивает функционирование системы, предоставляет системные службы ядра: управление памятью, энергосистемой и процессами, обеспечение безопасности, работа с сетью и драйверами.
Уровнем выше располагается набор библиотек и среда исполнения. Библиотеки реализуют следующие функции:
· предоставляют реализованные алгоритмы для вышележащих уровней;
· обеспечивает поддержку файловых форматов;
· осуществляет кодирование и декодирование информации (например, мультимедийные кодеки);
выполняет отрисовку графики и т.д
Рис. 2.1.Архитектура Android
Библиотеки реализованы на С/С++ и скомпилированы под конкретное аппаратное обеспечение устройства, вместе с которым они и поставляются производителем в предустановленном виде. Рассмотрим некоторые библиотеки:
Surface Manager – композитный менеджер окон. Поступающие команды отрисовки собираются в закадровый буфер, где они накапливаются, составляя некую композицию, а потом выводятся на экран. Это позволяет системе создавать интересные бесшовные эффекты, прозрачность окон и плавные переходы.
Media Framework – библиотеки, реализованные на базе PacketVideo OpenCORE. Используются для записи и воспроизведения аудио и видео контента, а также для вывода статических изображений. Поддерживаются форматы: MPEG4, H.264, MP3, AAC, AMR, JPG и PNG.
SQLite – легковесная и производительная реляционная СУБД, используется в Android в качестве основного движка для работы с базами данных.
3D библиотеки – используются для высокооптимизированной отрисовки 3D-графики, при возможности используют аппаратное ускорение. Библиотеки реализованы на основе API OpenGL|ES. OpenGL|ES (OpenGL for Embedded Systems) - подмножество графического программного интерфейса OpenGL, адаптированное для работы на встраиваемых системах.
FreeType – библиотека для работы с битовыми картами, для растеризации шрифтов и осуществления операций над ними.
LibWebCore – библиотеки браузерного движка WebKit, используемого также в известных браузерах Google Chrome и Apple Safari.
SGL (Skia Graphics Engine) – открытый движок для работы с 2D-графикой. Графическая библиотека является продуктом Google и часто используется в других программах.
SSL – библиотеки для поддержки одноименного криптографического протокола.
Libc – стандартная библиотека языка С, а именно ее BSD реализация, настроенная для работы на устройствах на базе Linux.
Среда исполнения включает в себя библиотеки ядра, обеспечивающие большую часть низкоуровневой функциональности, доступной библиотекам ядра языка Java, и виртуальную машину Dalvik, позволяющую запускать приложения. Каждое приложение запускается в своем экземпляре виртуальной машины, тем самым обеспечивается изоляция работающих приложений от ОС и друг от друга.
Для исполнения на виртуальной машине Dalvik Java-классы компилируются в исполняемые файлы с расширением .dex с помощью инструмента dx, входящего в состав AndroidSDK. DEX (Dalvik EXecutable) - формат исполняемых файлов для виртуальной машины Dalvik, оптимизированный для использования минимального объема памяти. При использовании IDE Eclipse и плагина ADT (AndroidDevelopmentTools) компиляция классов Java в формат .dex происходит автоматически.
АрхитектураAndroid Runtime такова, что работа программ осуществляется строго в рамках окружения виртуальной машины, что позволяет защитить ядро ОС от возможного вреда со стороны других ее составляющих. Поэтому код с ошибками или вредоносное ПО не смогут испортить Android и устройство на его базе, когда сработают.
На еще более высоком уровне располагается каркас приложений (Application Framework), архитектура которого позволяет любому приложению использовать уже реализованные возможности других приложений, к которым разрешен доступ. В состав каркаса входят следующие компоненты:
· богатый и расширяемый набор представлений (Views), который может быть использован для создания визуальных компонентов приложений, например, списков, текстовых полей, таблиц, кнопок или даже встроенного web-браузера;
· контент-провайдеры (Content Providers), управляющие данными, которые одни приложения открывают для других, чтобы те могли их использовать для своей работы;
· менеджер ресурсов (Resource Manager), обеспечивающий доступ к ресурсам без функциональности (не несущим кода), например, к строковым данным, графике, файлам и другим;
· менеджер оповещений (Notification Manager), позволяющий приложениям отображать собственные уведомления для пользователя в строке состояния;
· менеджер действий (Activity Manager), управляющий жизненными циклами приложений, сохраняющий историю работы с действиями, предоставляющий систему навигации по действиям;
· менеджер местоположения (Location Manager), позволяющий приложениям периодически получать обновленные данные о текущем географическом положении устройства.
Application Framework предоставляет в распоряжение приложений в ОС Android вспомогательный функционал, благодаря чему реализуется принцип многократного использования компонентов приложений и ОС. Естественно, в рамках политики безопасности.
Самый высокий и самый близкий к пользователю это – уровень приложений. Именно на этом уровне пользователь взаимодействует со своим устройством, управляемым ОС Android. Здесь представлен набор базовых приложений, который предустановлен на ОС Android. Например, браузер, почтовый клиент, программа для отправки SMS, карты, календарь, менеджер контактов и др. Список интегрированных приложений может меняться в зависимости от модели устройства и версии Android. К этому уровню также относятся все пользовательские приложения.
Разработчик обычно взаимодействует с двумя верхними уровнями архитектуры Android для создания новых приложений. Библиотеки, система исполнения и ядро Linux скрыты за каркасом приложений.
2.2.2. Основные виды Android-приложений
Приступая к разработке мобильных приложений хорошо бы иметь представление о том, какие виды приложений существуют. Если удастся определить к какому типу относится приложение, то становится понятнее на какие моменты в процессе его разработки необходимо обращать основное внимание. Можно выделить следующие виды приложений:
1) Приложения переднего плана выполняют свои функции только, когда видимы на экране, в противном же случае их выполнение приостанавливается. Такими приложениями являются, например, игры, текстовые редакторы, видеопроигрыватели. При разработке таких приложений необходимо очень внимательно изучить жизненный цикл активности, чтобы переключения в фоновый режим и обратно проходили гладко (бесшовно), т. е. при возвращении приложения на передний план было незаметно, что оно вообще куда-то пропадало. Для достижения этой гладкости необходимо следить за тем, чтобы при входе в фоновый режим приложение сохраняло свое состояние, а при выходе на передний план восстанавливало его. Еще один важный момент, на который обязательно надо обратить внимание при разработке приложений переднего плана, удобный и интуитивно понятный интерфейс.
2) Фоновые приложения после настройки не предполагают взаимодействия с пользователем, большую часть времени находятся и работают в скрытом состоянии. Примерами таких приложений могут служить, службы экранирования звонков, SMS-автоответчики. В большинстве своем фоновые приложения нацелены на отслеживание событий, порождаемых аппаратным обеспечением, системой или другими приложениями, работают незаметно. Можно создавать совершенно невидимые сервисы, но тогда они будут неуправляемыми. Минимум действий, которые необходимо позволить пользователю: санкционирование запуска сервиса, настройка, приостановка и прерывание его работы при необходимости.
3) Смешанные приложения большую часть времени работают в фоновом режиме, однако допускают взаимодействие с пользователем и после настройки. Обычно взаимодействие с пользователем сводится к уведомлению о каких-либо событиях. Примерами таких приложений могут служить мультимедиа-проигрыватели, программы для обмена текстовыми сообщениями (чаты), почтовые клиенты. Возможность реагировать на пользовательский ввод и при этом не терять работоспособности в фоновом режиме является характерной особенностью смешанных приложений. Такие приложения обычно содержат как видимые активности, так и скрытые (фоновые) сервисы, и при взаимодействии с пользователем должны учитывать свое текущее состояние. Возможно, потребуется обновлять графический интерфейс, если приложение находится на переднем плане, или же посылать пользователю уведомления из фонового режима, чтобы держать его в курсе происходящего. И эти особенности необходимо учитывать при разработке подобных приложений.
4) Виджеты - небольшие приложения, отображаемые в виде графического объекта на рабочем столе. Примерами могут служить, приложения для отображения динамической информации, такой как заряд батареи, прогноз погоды, дата и время. Разумеется, сложные приложения могут содержать элементы каждого из рассмотренных видов. Планируя разработку приложения, необходимо определить способ его использования, только после этого приступать к проектированию и непосредственно разработке.
2.2.3. Архитектура приложения, основные компоненты
АрхитектураAndroid приложений основана на идее многократного использования компонентов, которые являются основными строительными блоками. Каждый компонент является отдельной сущностью и помогает определить общее поведение приложения.
Система Android выстроена таким образом, что любое приложение может запускать необходимый компонент другого приложения. Например, если приложение предполагает использование камеры для создания фотографий, совершенно необязательно создавать в этом приложении активность для работы с камерой. Наверняка на устройстве уже есть приложение для получения фотографий с камеры, достаточно запустить соответствующую активность, сделать фотографию и вернуть ее в приложение, так что пользователь будет считать, что камера часть приложения, с которым он работает.
Когда система запускает компонент, она запускает процесс приложения, которому принадлежит компонент, если он еще не запущен, и создает экземпляры классов, необходимых компоненту. Поэтому в отличие от большинства других систем, в системе Android приложения не имеют единой точки входа (нет метода main(), например). В силу запуска каждого приложения в отдельном процессе и ограничений на доступ к файлам, приложение не может напрямую активировать компонент другого приложения. Таким образом для активации компонента другого приложения необходимо послать системе сообщение о намерении запустить определенный компонент, система активирует его.
Можно выделить четыре различных типа компонентов, каждый тип служит для достижения определенной цели и имеет свой особый жизненный цикл, который определяет способы создания и разрушения соответствующего компонента. Рассмотрим основные компоненты Android-приложений.
Активности (Activities).Активность - это видимая часть приложения (экран, окно, форма), отвечает за отображение графического интерфейса пользователя. При этом приложение может иметь несколько активностей, например, в приложении, предназначенном для работы с электронной почтой, одна активность может использоваться для отображения списка новых писем, другая активность - для написания, и еще одна - для чтения писем. Несмотря на то, что для пользователя приложение представляется единым целым, все активности приложения не зависят друг от друга. В связи с этим любая из этих активностей может быть запущена из другого приложения, имеющего доступ к активностям данного приложения. Например, приложение камеры может запустить активность, создающую новые письма, чтобы отправить только что сделанную фотографию адресату, указанному пользователем.
Сервисы (Services). Сервис - компонент, который работает в фоновом режиме, выполняет длительные по времени операции или работу для удаленных процессов. Сервис не предоставляет пользовательского интерфейса. Например, сервис может проигрывать музыку в фоновом режиме, пока пользователь использует другое приложение, может загружать данные из сети, не блокируя взаимодействие пользователя с активностью. Сервис может быть запущен другим компонентом и после этого работать самостоятельно, а может остаться связанным с этим компонентом и взаимодействовать с ним.
Контент-провайдеры (Content providers). Контент-провайдер управляет распределенным множеством данных приложения. Данные могут храниться в файловой системе, в базе данных SQLite, в сети, в любом другом доступном для приложения месте. Контент-провайдер позволяет другим приложениям при наличии у них соответствующих прав делать запросы или даже менять данные. Например, в системе Android есть контент-провайдер, который управляет информацией о контактах пользователя. В связи с этим, любое приложение с соответствующими правами может сделать запрос на чтение и запись информации какого-либо контакта. Контент-провайдер может быть также полезен для чтения и записи приватных данных приложения, не предназначенных для доступа извне.
Приемники широковещательных сообщений (Broadcast Receivers). Приемник - компонент, который реагирует на широковещательные извещения. Большинство таких извещений порождаются системой, например, извещение о том, что экран отключился или низкий заряд батареи. Приложения также могут инициировать широковещание, например, разослать другим приложениям сообщение о том, что некоторые данные загружены и доступны для использования. Хотя приемники не отображают пользовательского интерфейса, они могут создавать уведомление на панели состояний, чтобы предупредить пользователя о появлении сообщения. Такой приемник служит проводником к другим компонентам и предназначен для выполнения небольшого объема работ, например, он может запустить соответствующий событию сервис.
Все рассмотренные компоненты являются наследниками классов, определенных в AndroidSDK.
2.2.4. Установка и настройка инструментальных средств
Большинство приложений для OS Android написано на Java. Одной из самых популярных сред разработки является Eclipse с установленным плагином ADT и Android SDK. Раньше приходилось ставить все компоненты отдельно. Сейчас появилась версия среды Eclipse с уже настроенными дополнениями - ADT Bundle. Здесь есть минимум инструментов, необходимый для разработки приложений. С этой версией мы и будем работать. Однако в ней есть далеко не всё, поэтому, если при разработке какого-либо проекта вам потребуются инструменты, не входящие в ADT Bundle, вы можете скачать их с сайта разработчиков и дополнить свою среду.
Рис. 2.2.Сайт разработчика
Для того, чтобы скачать среду необходимо принять условия лицензионного соглашения и выбрать вашу версию Windows (32-bit или 64-bit).
После скачивания распакуйте архив в ту папку, где собираетесь работать (среда не требует специальной установки). После распаковки зайдите в папку и запустите Eclipse. Здесь возможна небольшая проблема: если у вас не установлен JDK, среда не запустится и потребует указать путь к папке с JDK или установить его. Скачать JDK можно с сайта Oracle.
Рис. 2.3.Сайт компании Oracle
Чтобы скачать JDK нужно сначала принять условия лицензионного соглашения, а затем выбрать нужную версию.
Рис. 2.4.Скачивание JDK
После скачивания запустите setup-файл и установите JDK.
Рис. 2.5.Установка JDK
После установки JDK среда должна запуститься. Далее вам необходимо выбрать (или создать новое) рабочее пространство, т.е. место, где будут находиться ваши проекты. Если поставить галочку, то это рабочее пространство будет выбираться по умолчанию, а противном случае это окно будет появляться при каждом запуске Eclipse.
Рис. 2.6.Выбор рабочего пространства
Затем появляется окно, в котором разработчики предлагают отправлять статистику для дальнейшего улучшения SDK. Вы можете согласиться или отказаться.
Рис. 2.7.Отправка статистики
Обратите внимание на значок Android SDK Manager, находящийся на панели инструментов (его также можно найти в меню Window). С его помощью вы сможете добавлять в свою среду новые инструменты.
Рис. 2.8.Android SDK Manager
Этапы создания приложений
Чтобы создать приложение, зайдите в меню File->New->Android Application Project.
Рис. 2.9.Создание приложения
В появившемся окне обязательно нужно прописать имя приложения, имя проекта, а также имя пакета (package). Лучше не оставлять его именем example, т.к. пакет с таким именем нельзя разместить в Google Play. Конечно, учебные приложения туда не загружают, однако, следует иметь это в виду на будущее.
Рис. 2.10.Наименование приложения
Minimum Required SDK - минимальная версия Android, которую будет поддерживать приложение. Чаще всего по умолчанию указывается версия 2.2, чтобы поддерживать как можно больше устройств. Если определенная функция вашего приложения работает только на более новых версиях Android, и это не является критическим для основного набора функций приложения, вы можете включить ее в качестве опции на версиях, которые поддерживают его.
Target SDK - версия Android, под которую будет написано ваше приложение; определяет максимальную версию Android, на которой вы тестировали приложение. Это нужно для режимов совместимости.
Compile With определяет, возможности какой версии Android будет использовать приложение.
Следующее окно можно пропустить без изменений. Здесь: Create custom launcher icon - создать значок приложения.Create activity - создать Activity (активность, деятельность). Markthisprojectaslibrary - создать проект, как библиотеку. Сейчас в этом нет необходимости, наше приложение в других проектах использоваться не будет. CreateProjectinWorkspace - создать проект в папке Workspace. В этой папке будут храниться все наши проекты.
Рис. 2.11.Конфигурация приложения
Следующий этап - создание иконки. Можете оставить стандартную или создать свою собственную. В нашем примере изменена цветовая гамма, форма, а также выбрана фигурка из клипарта.
Рис. 2.12.Создание иконки приложения
Большинство приложений на Android имеют свой экран (форму, окно), которое называется активностью или деятельностью (Activity). Следующее два окна создают пустую активность. В первом ничего пока менять не нужно. Во втором вы можете переименовать свою активность. Blank Activity - шаблон, предназначенный для мобильных телефонов. Fullscreen Activity - шаблон, позволяющий растянуть приложение на весь экран (без навигационной панели и статус-бара). Master/Detail Flow - шаблон, предназначенный для планшетных компьютеров.
Рис. 2.13.Создание активности и переименование активности
Итак, вы создали свой первый проект. Конечно, это всего лишь встроенное в среду приложение для проверки корректной установки инструментария, однако множество приложений создаются именно из него. Посмотрим на его структуру. Она показана в области слева. В первую очередь нас интересует файл активности. Он находится в папке src в вашем пакете. Он имеет расширение .java.
Рис. 2.14.Активность
В папке res в подпапке layout находится xml-файл, который является оболочкой нашей активности. Именно этот файл будет виден на экране устройства. C xml-файлами можно работать как в режиме графического редактора, так и непосредственно редактировать код.
Рис. 2.15.Xml-файл. Графический редактор
Рис. 2.16.Xml-файл
Для запуска приложения на эмуляторе нужно создать эмулятор устройства. Это можно сделать, нажав на кнопку на панели инструментов, изображающую смартфон. Если кнопки нет на панели, ее можно найти в меню Window.
Рис. 2.17.Запуск приложения
ОткроетсяAndroidVirtualDeviceManager. Пока в нем нет ни одного виртуального устройства.
Рис. 2.18.AndroidVirtualDeviceManager
Чтобы создать виртуальное устройство, нажмите кнопку New. Появится окно создания. Вам нужно назвать устройство и выбрать обязательные характеристики: Device - модель вашего устройства, и Target - версия Android. Также можно изменять дополнительные параметры: размер sd-карты, встроенной памяти и т.п.
Рис. 2.19.Создание AVD
Теперь можно запускать приложение. Для этого нужно нажать на кнопку Run (белый треугольник в зеленом кружочке) на панели инструментов. Проблемы с запуском можно отследить в консоли. Если приложение не запускается, попробуйте нажать на черный треугольник справа от кнопки Run, выбрать Run Configurations, затем во вкладке Target выбрать созданное устройство и запустить проект снова.
Рис. 2.20. Запуск приложения
Если все сделано правильно, должен запуститься эмулятор. Время запуска зависит от размера оперативной памяти на вашем компьютере. В дальнейшем эмулятор можно не закрывать, приложения будут запускаться в работающем.
Рис. 2.21.Запуск эмулятора
Рис. 2.22.Приложение «Hello world» и меню приложений
Если ваше приложение сразу не запустилось, его можно найти в меню приложений устройства.
2.3. Языки программирования соответствующие платформам мобильных операционных систем
Язык программирования — это набор формальных правил, по которым пишется код для приложений. Сейчас языков мобильной разработки много, а выбор конкретного метода зависит от ваших целей и возможностей, операционной системы, типа приложения и требований к нему.
2.3.1. Языки OCAndroid
Java
Java является самым популярным языком программирования по состоянию на июнь 2021 года. Для ОС Android язык Java считается самым основным. Естьбольшое и развитое сообщество разработчиков, которые выполняють техническую поддержку и помощь.
Преимущества:
· естественный код для Android. ОС частично тоже написана на Java, а ядро составляют Linux и собственная виртуальная машина Virtual Machine.
· универсальный — запускается на всех платформах.
· позволяет легко масштабировать и обновлять проекты за счет объектно-ориентированного кода. То есть, тут код легче читается, пишется и обновляется, что ускоряет все процессы.
· большое количество готовых инструментов, которые по умолчанию совместимы с Java, что тоже увеличивает скорость.
Недостатки:
· требует большого объема оперативной памяти.
· платные обновления для коммерческого использования.
Kotlin
Kotlin спроектирован и разработан компанией JetBrains, известной своей популярной IDE, IntelliJ IDEA. КомандаAndroid от Google объявила, что официально добавляет поддержку языка программирования Kotlin.
Kotlin был разработан для решения некоторых проблем на Java. СинтаксисKotlin проще, чище и приводит к меньшему использованию кода. Это поможет больше сосредоточиться на решении актуальной проблемы, а не на сложном синтаксисе. Кроме того, можно использовать Kotlin и Java вместе в одном проекте и это делает его мощным.
Преимущества:
· позволяет обходиться меньшим количеством кода, чем на Java. Чем меньше текста, тем меньше в нем ошибок.
· Kotlin взаимозаменяем с Java, поэтому разные части интерфейса могут быть написаны на разных языках, но при этом отлично работать. Это помогает создавать более эффективные и высокопроизводительные программы.
· безопасность, все синтаксические ошибки и баги, связанные с неправильным обращением к объектам, можно найти и исправить во время сборки. Это упрощает тестирование.
· программы Kotlin используют фреймворки и библиотеки Java.
Недостатки:
· скорость сборки программы часто колеблется от быстрой до очень медленной.
· пока не так сильно распространен среди разработчиков, поэтому могут возникнуть проблемы с поиском специалистов и решением нестандартных багов.
2.3.2. ЯзыкиOC iOS
Swift
Если вы хотите разработать для iOS, Swift может стать для вас подходящим языком. Внедренный в 2014 году и объявленный открытым исходным кодом в 2015 году, Swift быстро покоряет мобильных разработчиков. Он очень популярен, особенно среди новых начинающих разработчиков iOS.
Apple добавила некоторые замечательные функции для языка, такие как упрощенный синтаксис, возможность легко определять ошибки программиста и т.д. Огромные усилия Apple по продвижению Swift явно указывают на то, что она хочет, чтобы этот новый язык стал основным языком программирования для экосистемы приложений.
Преимущества:
· высокая скорость — доходит до уровня C++.
· простой для чтения. По логике он напоминает английский, а еще у него простой синтаксис и код.
· повышенная безопасность, если сравнивать с Objective С.
· упрощенный способ исправления ошибок в коде.
· стабильность за счет библиотек, которые автоматически связываются с обновленной версией и присоединяются к приложению.
· обеспечивает безопасное управление памятью.
Недостатки:
он развивается и меняется, поэтому работа может замедляться — нужно изучать и применять информацию об обновлениях.
мосты синхронизации с файлами Objective С тормозят сборку проекта.
Objective-C
Objective-C был оригинальным языком разработки для iOS. ЯзыкSwift является будущим развитием iOS, многие продвинутые проекты все еще полагаются на Objective-C. Переходот Objective-C к Swift ожидается несколько медленным, и вам может понадобиться оба из них для некоторых проектов.
Преимущества:
· существует много документации, которая упрощает работу.
· совместим со Swift.
Недостатки:
· невысокая производительность по сравнению со Swift.
· сложный синтаксис.
Rust
Rust начал создаваться в 2006 году разработчиком Грейдоном Хором, который хотел соединить в нем скорость C++ и надежность Haskell. В 2009-ом к нему присоединилась Mozilla. Сейчас Rust является одним из самых популярных среди разработчиков кроссплатформенных приложений.
Преимущества:
· безопасная синхронизация с памятью, не допускающая ошибок в сегментации и утечки данных.
· ошибки во время компиляции видны сразу + предлагаются варианты исправления.
· сопоставим по скорости с C++.
· надёжный API для организации сетевого взаимодействия с использованием библиотек.
· система выполняет сразу несколько вычислений одновременно и обеспечивает их взаимодействие друг с другом.
Недостатки:
· относительно новый и быстро развивается, поэтому нет подходящей литературы и выбора специалистов.
· строгий компилятор, который требует самостоятельного заполнения большого объема данных и замедляет процесс разработки.
JavaScript
JavaScript имеет давнюю историю, восходящую к началу Всемирной паутины. Очень популярный для фронтенда и бекенда язык, который позволяет веб-разработчикам делать все, начиная с улучшения пользовательского интерфейса своих веб-сайтов и заканчивая созданием полноценных веб-приложений.
Сегодня существует несколько инфраструктур JavaScript, специально предназначенных для мобильных платформ разработки, таких как Ionic 2 и React Native. С помощью этих фреймворков и библиотек очень легко разрабатывать кросс-платформенные мобильные приложения. Это означает, что вам нужно написать только одну версию приложения, и она будет работать на iOS или Android.
TypeScript
TypeScript - это надмножество JavaScript и обеспечивает лучшую безопасность, добавляя необязательную статическую типизацию. Он также обеспечивает лучшую поддержку для разработки крупномасштабных приложений. Разработанный и поддерживаемый Microsoft, TypeScript позволяет разработчикам писать кросс-платформенные мобильные приложения с использованием таких фреймворков, как NativeScript.
C#
C# - этоязыкWindowsMobile. Он очень похож на C ++ и Java. Microsoft применила некоторые функции Java для упрощения своей архитектуры, сохраняя при этом дизайн похожий на C ++. Он также имеет большое и активное сообщество разработчиков, которые всегда дружелюбны и полезны.
Преимущества:
· Windows уделяет особое внимание поддержке, регулярно выпускает обновления и выявляет баги, поэтому работать с C# можно комфортно и быстро.
· некоторые организации и индивидуальные разработчики могут пользоваться инструментами бесплатно.
· ответы практически на все вопросы, связанные с работой в C#, можно найти в интернете или профессиональных сообществах.
· большой набор инструментов и средств для работы C# позволяет пользоваться только одним языком.
· автоматический режим очистки памяти от объектов, которые не используются.
· корректно работает даже при переходе продукта на новую версию.
Недостатки:
· работает практически на всех ОС, но все же приоритет основан на платформе Windows.
· бесплатен только для небольших фирм, индивидуальных программистов, стартапов и учащихся. Крупной компании покупка лицензионной версии выйдет в крупную сумму.
С
C является вторым по популярности языком в индексе TIOBE, и, как и Java, его сообщество полно опытных разработчиков, которые могут предложить вам ценные советы о том, как писать код без ошибок.
Созданный Dennis Ritchie, работая в Bell Labs, C - широко распространенный и мощный язык, который позволяет вам напрямую манипулировать низкоуровневыми операциями компьютера. Если вы хотите использовать Android NDK (Native Development Kit), вам нужно будет ознакомиться с языком C.
Плюсы:
· Вместе с Java позволяет сократить код, что ускорит работоспособность программы.
Минусы:
· Непростой в освоении;
· Не позволяет полноценно создать приложение, а всего лишь подвязывать библиотеки к приложению.
C++
C++ - это расширение C, с более высокоуровневыми функциями и поддержкой объектно-ориентированного программирования. C++ также является любимым языком разработчиков Android NDK. C++ можно использовать для разработки приложений для Windows Mobile. C++ идет вместе с Java в области разработки программного обеспечения.
Плюсы:
· поддерживает объективно-ориентированное программирование, процедурное программирование и обобщенное.
· улучшает производительность при работе с Objective-C.
Минусы:
· не предназначен для полноценной разработки;
· непростой в изучении.
Python
Python - язык, который легко усвоить и легко читать. Создатели языка приложили дополнительные усилия, чтобы синтаксис был максимально простым и понятным. Это помогает начинающим разработчикам поддерживать высокий уровень производительности с первого дня. Если вам удобно писать код Python, вы можете использовать такие фреймворки, как Kivy, для разработки кросс-платформенных мобильных приложений.
Плюсы:
· подходит как для нативных, так и для веб-приложений;
· позволяет строить нативные интерфейсы;
· легко читаемый синтаксис;
· Прост в изучении.
Минусы:
· не официальный язык Android и не поддерживает его без фреймворка Kivy;
· не особо востребованный.
Ruby
Ruby - это объектно-ориентированный язык сценариев, созданный под влиянием Ada, C ++, Perl, Python и Lisp. RubyMotion - платформа для разработки нативных и кросс-платформенных мобильных приложений в Ruby.
1. Для чего предназначена мобильная ОС?
2. Опишите достоинства и недостатки ОС Symbian.
3. Опишите достоинства и недостатки ОС WindowsMobile.
4. Опишите достоинства и недостатки ОС Palm.
5. Опишите достоинства и недостатки ОС iPhoneOS.
6. Каково устройство платформы Android?
7. Что представляет собой Android SDK?
8. Назовите основные средства разработки под Android.
9. Перечислите достоинства и недостатки эмуляторов Android.
10. Какая версия платформы наиболее популярна в настоящее время?
11. Виды приложений.
12. Как можно изменять свойства приложений?
13. Какую архитектуру имеет приложения Андроид?
14. Опишите фоновые приложения.
15. Как работают смешанные приложения?
3. РАЗРАБОТКА ПРИЛОЖЕНИЙ НА ЯЗЫКЕПРОГРАММИРОВАНИЯ JAVA ДЛЯ ANDROID
3.1. Основные конструкции языка программированияJava
В языке Java только 8 примитивных (скалярных, простых) типов: boolean, byte, char, short, int, long, float, double. Существует также вспомогательный девятый примитивный тип — void, однако переменные и поля такого типа не могут быть объявлены в коде, а сам тип используется только для описания соответствующего ему класса, для использования при рефлексии.
Длины и диапазоны значений примитивных типов определяются стандартом, а не реализацией, и приведены в таблице. Тип char сделали двухбайтовым для удобства локализации (один из идеологических принципов Java): когда складывался стандарт, уже существовал Unicode-16, но не Unicode-32. Поскольку в результате не осталось однобайтового типа, добавили новый тип byte, причем в Java, в отличие от других языков, он не является беззнаковым. Типы float и double могут иметь специальные значения и «не число» (NaN). Для типа double они обозначаются Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN; для типа float — так же, но с приставкой Float вместо Double. Минимальные и максимальные значения, принимаемые типами float и double, тоже стандартизованы.
Тип |
Длина (в байтах) |
Диапазон или набор значений |
boolean |
1 в массивах, 4 в переменных |
true, false |
byte |
1 |
−128..127 |
char |
2 |
0..216−1, или 0..65535 |
short |
2 |
−215..215−1, или −32768..32767 |
int |
4 |
−231..231−1, или −2147483648..2147483647 |
long |
8 |
−263..263−1, или примерно −9.2·1018..9.2·1018 |
float |
4 |
-(2-2−23)·2127..(2-2−23)·2127,
или примерно −3.4·1038..3.4·1038, а также |
double |
8 |
-(2-2−52)·21023..(2-2−52)·21023,
или примерно −1.8·10308..1.8·10308, а также , |
Такая жёсткая стандартизация была необходима, чтобы сделать язык платформенно-независимым, что является одним из идеологических требований к Java. Тем не менее, одна небольшая проблема с платформенной независимостью всё же осталась. Некоторые процессоры используют для промежуточного хранения результатов 10-байтовые регистры или другими способами улучшают точность вычислений. Для того, чтобы сделать Java максимально совместимой между разными системами, в ранних версиях любые способы повышения точности вычислений были запрещены. Однако это приводило к снижению быстродействия. Выяснилось, что ухудшение точности ради платформенной независимости мало кому нужно, тем более если за это приходится платить замедлением работы программ. После многочисленных протестов этот запрет отменили, но добавили ключевое слово strictfp, запрещающее повышение точности. Преобразования при математических операциях в языке Java действуют следующие правила:
1. Если один операнд имеет тип double, другой тоже преобразуется к типу double.
2. Иначе, если один операнд имеет тип float, другой тоже преобразуется к типу float.
3. Иначе, если один операнд имеет тип long, другой тоже преобразуется к типу long.
4. Иначе оба операнда преобразуются к типу int.
Данный способ неявного преобразования встроенных типов полностью совпадает с преобразованием типов в C++.
В языке Java имеются только динамически создаваемые объекты. Причем переменные объектного типа и объекты в Java — совершенно разные сущности. Переменные объектного типа являются ссылками, то есть неявными указателями на динамически создаваемые объекты. Это подчёркивается синтаксисом описания переменных. Так, в Java нельзя писать:
double a[10][20];
Foo b(30);
анужно:
double[][] a = new double[10][20];
Foo b = new Foo(30);
При присваиваниях, передаче в подпрограммы и сравнениях объектные переменные ведут себя как указатели, то есть присваиваются, копируются и сравниваются адреса объектов. А при доступе с помощью объектной переменной к полям данных или методам объекта не требуется никаких специальных операций разыменовывания — этот доступ осуществляется так, как если бы объектная переменная была самим объектом.
Объектными являются переменные любого типа, кроме примитивного. Явных указателей в Java нет. В отличие от указателей C, C++ и других языков программирования, ссылки в Java в высокой степени безопасны благодаря жёстким ограничениям на их использование, в частности:
· нельзя преобразовывать объект типа int или любого другого примитивного типа в указатель или ссылку и наоборот.
· над ссылками запрещено выполнять операции ++, −−, +, − или любые другие арифметические операции.
· преобразование типов между ссылками жёстко регламентировано. За исключением ссылок на массивы, разрешено преобразовывать ссылки только между наследуемым типом и его наследником, причём преобразование наследуемого типа в наследующий должно быть явно задано и во время выполнения производится проверка его осмысленности. Преобразования ссылок на массивы разрешены лишь тогда, когда разрешены преобразования их базовых типов, а также нет конфликтов размерности.
· в Java нет операций взятия адреса (&) или взятия объекта по адресу (*). Амперсанд (&) означает всего лишь «побитовое и» (двойной амперсанд — «логическое и»). Однако для булевых типов одиночный амперсанд означает «логическое и», отличающееся от двойного тем, что цепь проверок не прекращается при получении в выражении значения false (напр. a == b && foo() == bar() не повлечёт вызовов foo() и bar() в случае, если a != b, тогда как использование & повлечёт в любом случае.
Благодаря таким специально введенным ограничениям в Java невозможно прямое манипулирование памятью на уровне физических адресов (хотя определено значение ссылки, не указывающей ни на что: null).
Если нужен указатель на примитивный тип, используются классы-обёртки примитивных типов: Boolean, Byte, Character, Short, Integer, Long, Float, Double.
Из-за того, что объектные переменные являются ссылочными, при присваивании не происходит копирования объекта. Так, если написать то произойдет копирование адреса из переменной foo в переменную bar. То есть foo и bar будут указывать на одну и ту же область памяти, то есть на один и тот же объект; попытка изменить поля объекта, на который ссылается переменная foo, будет менять объект, с которым связана переменная bar, и наоборот.
Foo foo, bar;
…
bar = foo;
,
Если же необходимо получить именно ещё одну копию исходного объекта, пользуются или методом (функцией-членом, в терминологии C++) clone(), создающим копию объекта, или (реже) копирующим конструктором (конструкторы в Java не могут быть виртуальными, поэтому экземпляр класса-потомка будет неправильно скопирован конструктором класса-предка; метод клонирования вызывает нужный конструктор и тем самым позволяет обойти это ограничение).
Метод clone() требует, чтобы класс реализовывал интерфейс Cloneable (об интерфейсах см. ниже). Если класс реализует интерфейс Cloneable, по умолчанию clone() копирует все поля (мелкая копия). Если требуется не копировать, а клонировать поля (а также их поля и так далее), надо переопределять метод clone(). Определение и использование метода clone() часто является нетривиальной задачей.
В языке Java невозможно явное удаление объекта из памяти — вместо этого реализована Сборка мусора. Традиционным приёмом, дающим сборщику мусора «намёк» на освобождение памяти, является присваивание переменной пустого значения null. Это, однако, не значит, что объект, заменённый значением null, будет непременно и немедленно удалён, но есть гарантия, что этот объект будет удалён именно в будущем. Данный приём всего лишь устраняет ссылку на объект, то есть отвязывает указатель от объекта в памяти. При этом следует учитывать, что объект не будет удален сборщиком мусора, пока на него указывает хотя бы одна ссылка из используемых переменных или объектов. Существуют также методы для инициации принудительной сборки мусора, но не гарантируется, что они будут вызваны исполняющей средой, и их не рекомендуется использовать для обычной работы.
3.1.2. Специальные классы и функции
Java не является процедурным языком: любая функция может существовать только внутри класса. Это подчёркивает терминология языка Java, где нет понятий «функция» или «функция-член» (англ. memberfunction), а только метод. В методы превратились и стандартные функции. Например, в Java нет функции sin(), а есть метод Math.sin() класса Math (содержащего, кроме sin(), методы cos(), exp(), sqrt(), abs() и многие другие). Конструкторы в Java не считаются методами. Деструкторов в Java не существует, а метод finalize() ни в коем случае нельзя считать аналогом деструктора.
Конструктор — это специальный метод, который вызывается при создании нового объекта. Не всегда удобно инициализировать все переменные класса при создании его экземпляра. Иногда проще, чтобы какие-то значения были бы созданы по умолчанию при создании объекта. По сути, конструктор нужен для автоматической инициализации переменных.
Конструктор инициализирует объект непосредственно во время создания. Имя конструктора совпадает с именем класса, включая регистр, а по синтаксису конструктор похож на метод без возвращаемого значения.
private int Cat(); // так выглядит метод по имени Cat
Cat(); // так выглядит конструктор класса Cat
В отличие от метода, конструктор никогда ничего не возвращает. Конструктор определяет действия, выполняемые при создании объекта класса, и является важной частью класса. Как правило, программисты стараются явно указать конструктор. Если явного конструктора нет, то Java автоматически создаст его для использования по умолчанию.
Создадим класс Box с конструктором, который просто установит начальные значения для коробки.
class Box {
int width; // ширинакоробки
int height; // высотакоробки
int depth; // глубинакоробки
// Конструктор
Box(int a, int b) {
width = a;
height = b;
depth = 10;
}
// вычисляемобъёмкоробки
int getVolume() {
returnwidth * height * depth;
}
}
Даже если конструктор специально не определён, виртуальная машина Java обязательно его создаст (пустым).
В Java (как и в C++) используются статические методы (англ. staticmethod — в теории программирования их также называют методами класса), которые задаются при помощи ключевого слова static. Статические поля (переменные класса) имеют тот же смысл, что и в C++: каждое такое поле является собственностью класса, поэтому для доступа к статическим полям не требуется создавать экземпляры соответствующего класса.
Например, математические функции, реализованные в классе Math, представляют собой как раз статические методы данного класса. Поэтому можно писать
double x = Math.sin(1);
вместо
Math m = new Math();
double x = m.sin(1);
Поскольку статические методы существуют независимо от объектов (экземпляров класса), они не имеют доступа к обычным (нестатическим) полям и методам данного класса. В частности, при реализации статического метода недопустимо использовать идентификатор this.
Ключевое слово final (финальный) имеет разные значения при описании поля, метода или класса.
1. Финальное поле класса инициализируется при описании или в конструкторе класса (а статическое поле — в статическом блоке инициализации). Впоследствии его значение не может быть изменено. Если статическое поле класса или переменная проинициализированы константным выражением, они рассматриваются компилятором как именованная константа; в таком случае их значение может быть использовано в операторах switch (для констант типа int), а также для условной компиляции (для констант типа boolean) при использовании с оператором if.
2. Значения локальных переменных, а также параметров метода, помеченных ключевым словом final, не могут быть изменены после присвоения. При этом их значения могут использоваться внутри анонимных классов.
3. Метод класса, отмеченный словом final, не может быть переопределён при наследовании.
4. Финальный класс не может иметь наследников.
В Java методы, не объявленные явно как static, final или private, являются виртуальными в терминологии C++: при вызове метода, по-разному определённого в базовом и наследующем классах, всегда производится проверка времени выполнения.
Абстрактным методом (модификатор abstract) в Java называется метод, для которого заданы параметры и тип возвращаемого значения, но не задано тело. Абстрактный метод определяется в классах-наследниках. Аналог абстрактного метода в C++ — чисто виртуальная функция (pure virtual function). Для того чтобы в классе можно было описывать абстрактные методы, сам класс тоже должен быть описан как абстрактный. Объекты абстрактного класса создавать нельзя.
Высшей степенью абстрактности в Java является интерфейс (interface). Все методы интерфейса абстрактны: описатель abstract даже не требуется. Интерфейс в Java не считается классом, хотя, по сути, является полностью абстрактным классом. Класс может наследовать/расширять (extends) другой класс или реализовывать (implements) интерфейс. Кроме того, интерфейс может наследовать/расширять другой интерфейс.
В Java класс не может наследовать более одного класса, зато может реализовывать несколько интерфейсов. Множественное наследование интерфейсов не запрещено, то есть один интерфейс может наследоваться от нескольких.
Интерфейсы можно использовать в качестве типов параметров методов. Нельзя создавать экземпляры интерфейсов.
В Java есть интерфейсы, которые не содержат методов для реализации, а специальным образом обрабатываются JVM:
· java.lang.Cloneable
· java.io.Serializable
· java.util.RandomAccess
· java.rmi.Remote
Java является объектно-ориентированным языком, поэтому такие понятия как "класс" и "объект" играют в нем ключевую роль. Любую программу на Java можно представить как набор взаимодействующих между собой объектов.
Шаблоном или описанием объекта является класс, а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке - наличие двух рук, двух ног, головы, туловища и т.д. Есть некоторый шаблон - этот шаблон можно назвать классом. Реально же существующий человек (фактически экземпляр данного класса) является объектом этого класса.
Класс определяется с помощью ключевого слова сlass:
classGraj{
}
В примере класс называется Graj. После названия класса идут фигурные скобки, между которыми помещается тело класса - то есть его поля и методы.
Любой объект может обладать двумя основными характеристиками: состояние - некоторые данные, которые хранит объект, и поведение - действия, которые может совершать объект.
Для хранения состояния объекта в классе применяются поля или переменные класса. Для определения поведения объекта в классе применяются методы. Например, класс Graj, который представляет человека, мог бы иметь следующее определение:
classGraj{
String
imya; // имя
intvozrast;
// возраст
voiddisplayInfo(){
System.out.printf("Imya:
%s \tVozrast: %d\n", imya, vozrast);
}
}
В классе Graj определены два поля: imya представляет имя человека, а vozrast - его возраст. И также определен метод displayInfo, который ничего не возвращает и просто выводит эти данные на консоль.
Теперь используем данный класс. Для этого определим следующую программу:
public class Program{
public static void main(String[] args) {
Grajkarl; }
}
class Graj{
String imya; // имя
int vozrast; // возраст
void displayInfo(){
System.out.printf("Imya: %s \tVozrast: %d\n", imya, vozrast); }
}
Как правило, классы определяются в разных файлах. В примере для простоты мы определяем два класса в одном файле. Стоит отметить, что в этом случае только один класс может иметь модификатор public (в примере это класс Program), а сам файл кода должен называться по имени этого класса, то есть в примере файл должен называться Program.java.
Класс представляет новый тип, поэтому мы можно определять переменные, которые представляют данный тип. Так, здесь в методе main определена переменная karl, которая представляет класс Graj. Но пока эта переменная не указывает ни на какой объект и по умолчанию она имеет значение null. По большому счету мы ее пока не можно использовать, поэтому вначале необходимо создать объект класса Graj.
Кроме обычных методов классы могут определять специальные методы, которые называются конструкторами. Конструкторы вызываются при создании нового объекта данного класса. Конструкторы выполняют инициализацию объекта.
Если в классе не определено ни одного конструктора, то для этого класса автоматически создается конструктор без параметров.
Выше определенный класс Graj не имеет никаких конструкторов. Поэтому для него автоматически создается конструктор по умолчанию, который мы можно использовать для создания объекта Graj. Вчастности, создадимодинобъект:
publicclassProgram{
publicstaticvoidmain(String[]
args) {
Grajkarl
= newGraj(); // создание объекта
karl.displayInfo();
//
изменяем имя и возраст
karl.imya
= "
Said
";
karl.vozrast
=
20
;
karl.displayInfo(); }
}
classGraj{
String
imya; // имя
intvozrast;
// возраст
voiddisplayInfo(){
System.out.printf("Imya:
%s \tVozrast: %d\n", imya, vozrast); }
}
Для создания объекта Graj используется выражение new Graj(). Оператор new выделяет память для объекта Graj. И затем вызывается конструктор по умолчанию, который не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Graj. А переменная karl получит ссылку на созданный объект.
Если конструктор не инициализирует значения переменных объекта, то они получают значения по умолчанию. Для переменных числовых типов это число 0, а для типа string и классов - это значение null (то есть фактически отсутствие значения).
После создания объекта
мы можно обратиться к переменным объекта Graj через переменную karl и
установить или получить их значения, например, karl.imya = "Said
".
В итоге мы увидим на консоли:
Imya: null Vozrast: 0
Imya: Karl Vozrast: 20
Если необходимо, чтобы при создании объекта производилась какая-то логика, например, чтобы поля класса получали какие-то определенные значения, то можно определить в классе свои конструкторы. Например:
publicclassProgram{
publicstaticvoidmain(String[]
args) {
Grajroman
= newGraj(); // вызов первого конструктора без
параметров
roman.displayInfo();
Grajkarl
= newGraj("Said"); // вызов второго конструктора с одним параметром
karl.displayInfo();
Graj
sam = newGraj("Suxrob", 22); // вызов третьего конструктора с двумя
параметрами
sam.displayInfo(); }
}
classGraj{
String
imya; // имя
intvozrast;
// возраст
Graj() {
imya
= "Undefined";
vozrast
= 18;
}
Graj(String
n) {
imya
= n;
vozrast
= 18;
}
Graj(String
n, inta) {
imya
= n;
vozrast
= a;
}
voiddisplayInfo(){
System.out.printf("Imya:
%s \tVozrast: %d\n", imya, vozrast); }
}
Теперь в классе
определено три коструктора, каждый из которых принимает различное количество
параметров и устанавливает значения полей класса.
Консольный вывод
программы:
Imya
:
Undefined
Vozrast
:
18
Imya:
Said Vozrast: 20
Imya:
Suxrob Vozrast: 22
3.2.3.
Ключевое слово
this
Ключевое слово
this представляет ссылку на текущий экземпляр класса. Через это ключевое слово
мы можно обращаться к переменным, методам объекта, а также вызывать его
конструкторы. Например
:
publicclassProgram{
publicstaticvoidmain(String[]
args) {
Graj
undef = newGraj();
undef.displayInfo();
Grajkarl
= newGraj("Karl");
karl.displayInfo();
Graj
sam = newGraj("Sam", 25);
sam.displayInfo(); }
}
classGraj{
String
imya; // имя
intvozrast;
// возраст
Graj() {
this("Undefined",
18);
}
Graj(String
imya) {
this(imya,
18);
}
Graj(String
imya, intvozrast) {
this.imya
= imya;
this.vozrast
= vozrast;
}
voiddisplayInfo(){
System.out.printf("Imya:
%s \tVozrast: %d\n", imya, vozrast); }
}
В третьем конструкторе параметры называются так же, как и поля класса. И чтобы разграничить поля и параметры, применяется ключевое слово this:
this.imya
= imya;
Так, в примере указываем, что значение параметра imya присваивается полю imya.
Кроме того, у нас три конструктора, которые выполняют идентичные действия: устанавливают поля imya и vozrast. Чтобы избежать повторов, с помощью this можно вызвать один из конструкторов класса и передать для его параметров необходимые значения:
Graj(String
imya){
this(imya,
18);
}
В итоге результат программы будет тот же, что и в предыдущем примере.
Кроме конструктора начальную инициализацию объекта вполне можно было проводить с помощью инициализатора объекта. Инициализатор выполняется до любого конструктора. То есть в инициализатор мы можно поместить код, общий для всех конструкторов:
publicclassProgram{
publicstaticvoidmain(String[]
args) {
Graj
undef = newGraj();
undef.displayInfo();
Grajkarl
= newGraj("Karl");
karl.displayInfo(); }
}
classGraj{
String
imya; // имя
intvozrast;
// возраст
/*начало
блока инициализатора*/
{
imya
= "Undefined";
vozrast
= 18;
}
/*конец
блока инициализатора*/
Graj(){ }
Graj(String
imya){
this.imya
= imya; }
Graj(String
imya, intvozrast){
this.imya
= imya;
this.vozrast
= vozrast; }
voiddisplayInfo(){
System.out.printf("Imya:
%s \tVozrast: %d\n", imya, vozrast); }
}
Консольный вывод:
Imya: Undefined Vozrast: 18
Imya: Karl Vozrast: 18
3.3. Разработка мобильных приложений на языке программирования Java
TextView
Для простого вывода текста на экран предназначен элемент TextView. Он просто отображает текст без возможности его редактирования. Некоторые его основные атрибуты:
• android:text: устанавливает текст элемента
• android:textSize: устанавливает высоту текста, в качестве единиц измерения для указания высоты используются sp
• android:background: задает фоновый цвет элемента в виде цвета в шестнадцатиричной записи или в виде цветового ресурса
• android:textColor: задает цвет текста
• android:textAllCaps: при значении true делает все символы в тексте заглавными
• android:textDirection: устанавливает направление текста. По умолчанию используется направление слева направо, но с помощью значения rtl можно установить направление справо налево
• android:textAlignment: задает выравнивание текста. Может принимать следующие значения:
o center: выравнивание по центру
o textStart: по левому краю
o textEnd: по правому краю
o viewStart: по левому краю
o viewEnd: по правому краю
• android:fontFamily: устанавливает тип шрифта. Может принимать следующие значения:
o monospace
o serif
o serif-monospace
o sans-serif
o sans-serif-condensed
o sans-serif-smallcaps
o sans-serif-light
o casual
o cursive
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.graphics.Typeface;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
TextView textView1 = new TextView(this);
// установка фонового цвета
textView1.setBackgroundColor(0xffe8eaf6);
// установка цвета текста
textView1.setTextColor(0xff5c6bc0);
// делаем все буквы заглавными
textView1.setAllCaps(true);
// устанавливаем вравнивание текста по центру
textView1.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
// устанавливаем текста
textView1.setText("Android Nougat 7");
// установка шрифта
textView1.setTypeface(Typeface.create("casual", Typeface.NORMAL));
// устанавливаем высоту текста
textView1.setTextSize(26);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// установка внешних отступов
layoutParams.setMargins(20,20,20,20);
// устанавливаем размеры
textView1.setLayoutParams(layoutParams);
linearLayout.addView(textView1);
setContentView(linearLayout); }
}
android:autoLink может принимать несколько значений:
• none: отключает все ссылки
• web: включает все веб-ссылки
• email: включает ссылки на электронные адреса
• phone: включает ссылки на номера телефонов
• map: включает ссылки на карту
• all: включает все вышеперечисленные ссылки
То есть при настройке android:autoLink="web" если в тексте есть упоминание адреса url, то этот адрес будет выделяться, а при нажатии на него будет осуществлен переход к веб-браузеру, который откроет страницу по этому адресу. С помощью прямой черты мы можно объединять условия, как в данном случае: android:autoLink="web|email"
EditText
Элемент EditText является подклассом класса TextView. Он также представляет текстовое поле, но теперь уже с возможностью ввода и редактирования текста. Таким образом, в EditText мы можно использовать все те же возможности, что и в TextView.
Из тех атрибутов, что не рассматривались в теме про TextView, следует отметить атрибут android:hint. Он позволяет задать текст, который будет отображаться в качестве подсказки, если элемент EditText пуст. Кроме того, мы можно использовать атрибут android:inputType, который позволяет задать клавиатуру для ввода. В частности, среди его значений можно выделить следующие:
• text: обычная клавиатура для ввода однострочного текста
• textMultiLine: многострочное текстовое поле
• textEmailAddress: обычная клавиатура, на которой присутствует символ @, ориентирована на ввод email
• textUri: обычная клавиатура, на которой присутствует символ /, ориентирована на ввод интернет-адресов
• textPassword: клавиатура для ввода пароля
• textCapWords: при вводе первый введенный символ слова представляет заглавную букву, остальные - строчные
• number: числовая клавиатура
• phone: клавиатура в стиле обычного телефона
• date: клавиатура для ввода даты
• time: клавиатура для ввода времени
datetime: клавиатура для ввода даты и времени
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(s); } });
}}
С помощью метода addTextChangedListener() здесь к элементу EditText добавляется слушатель ввода текста - объект TextWatcher. Для его использования нам надо реализовать три метода, но в реалности нам хватит реализации метода onTextChanged, который вызывается при изменении текста. Введенный текст передается в этот метод в качестве параметра CharSequence. В самом методе просто передаем этот текст в элемент TextView.
Button
Одним из часто используемых элементов являются кнопки, которые представлены классом android.widget.Button. Ключевой особенностью кнопок является возможность взаимодействия с пользователем через нажатия.
Некоторые ключевые атрибуты, которые можно задать у кнопок:
• text: задает текст на кнопке
• textColor: задает цвет текста на кнопке
• background: задает фоновый цвет кнопки
• textAllCaps: при значении true устанавливает текст в верхнем регистре. По умолчанию как раз и применяется значение true
• onClick: задает обработчик нажатия кнопки
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// Обработка нажатия кнопки
public void sendMessage(View view) {
TextView textView = (TextView) findViewById(R.id.textView);
EditText editText = (EditText) findViewById(R.id.editText);
textView.setText("Добро пожаловать, " + editText.getText());
}
}
При создании метода обработки нажатия следует учитывать следующие моменты:
• Метод должен объявляться с модификатором public
• Должен возвращать значение void
В качестве параметра принимать объект View. Этот объект View и представляет собой нажатую кнопку
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
textView = new TextView(this);
textView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(textView);
editText = new EditText(this);
editText.setHint("Введите имя");
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ));
linearLayout.addView(editText);
Button button = new Button(this);
button.setText("CLICK");
button.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT ));
linearLayout.addView(button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Обработка нажатия
textView.setText("Добро пожаловать, " + editText.getText());
}});
setContentView(linearLayout);
}}
Checkbox
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
mport android.widget.CheckBox;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onCheckboxClicked(View view) {
// Получаем флажок
CheckBox language = (CheckBox) view;
// Получаем, отмечен ли данный флажок
boolean checked = language.isChecked();
TextView selection = (TextView) findViewById(R.id.selection);
// Смотрим, какой именно из флажков отмечен
switch(view.getId()) {
case R.id.java:
if (checked){
selection.setText("Java"); }
break;
case R.id.javascript:
if (checked)
selection.setText("JavaScript");
break; } }
}
В качестве параметра в обработчик нажатия onCheckboxClicked передается нажатый флажок. С помощью метода isChecked() можно узнать, выделен ли флажок - в этом случае метод возвращает true.
public void onCheckboxClicked(View view) {
// Получаем флажок
CheckBox language = (CheckBox) view;
// Получаем, отмечен ли данный флажок
TextView selection = (TextView) findViewById(R.id.selection);
if(language.isChecked())
selection.setText(language.getText());
}
package com.example.layoutapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onCheckboxClicked(View view) {
// Получаем флажки
CheckBox java = (CheckBox) findViewById(R.id.java);
CheckBox javascript = (CheckBox) findViewById(R.id.javascript);
String selectedItems = "";
if(java.isChecked())
selectedItems +=java.getText() + ", ";
if(javascript.isChecked())
selectedItems +=javascript.getText();
TextView selection = (TextView) findViewById(R.id.selection);
selection.setText(selectedItems); }
}
1. Какие платформы используются для создания ОС мобильных приложений?
2. Абстрактные классы и их использование
3. Анонимные классы и их использование
4. Использование шаблонов и контейнеров
5. Зачем нужны абстрактные классы?
4. РАБОТА С БАЗАМИ ДАННЫХ В МОБИЛЬНЫХ ПРИЛОЖЕНИЯХ, РАБОТА С ГЕОЛОКАЦИЕЙ
4.1.1. Введение в базу данных в Android
Android использует для работы с базами данных известную библиотеку SQLite. SQLite зарекомендовала себя в качестве чрезвычайно надёжной системы баз данных, которая используется во многих бытовых электронных устройствах и программах, включая некоторые MP3-проигрыватели, iPhone, iPod Touch, Mozilla Firefox и др.
С помощью SQLite вы можете создавать для своего приложения независимые реляционные базы данных для хранения и управления сложными данными приложения. Android хранит базы данных в каталоге /data/data/<имявашегопакета>/databases на вашем устройстве (или эмуляторе). По умолчанию все базы данных приватные, доступ к ним могут получить только те приложения, которые их создали.
Те, кто имеет опыт работы со связкой PHP+MySQL, найдут много знакомых вещей и быстро разберутся с принципом работы.
Прежде всего нужно запомнить, что в Android уже есть готовый класс SQLiteOpenHelper. Помните, мы в своих проектах всегда наследовались от Activity (extends Activity). Точно так же надо поступить и при работе с базой данных.
Создадим новый проект как обычно. Теперь нужно создать новый класс, который будет работать с базой данных - добавлять, выбирать, удалять и прочие операции. Создаём новый класс и в диалоговом окне напротив текстового поля Superclass: нажимаем кнопку Browse.... Далее начинаем вводить первые символы класса, чтобы выбрать из списка нужный класс. Восьми символов оказалось вполне достаточно, чтобы в списке остался только один вариант. Нажимаем на кнопку OK для добавления класса в диалоговое окно и нажимаем на кнопку Finish, чтобы закончить с созданием класса.
Рис. 4.1. Создание нового класса
Появится следующая заготовка:
package ru.databasedemo;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class CatsDataBase extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase arg0) {
// TODO Auto-generated method stub
}
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
// TODO Auto-generated method stub
}
}
Укласса есть два обязательных метода onCreate() и onUpgrade().Название класса будет подчёркнуто красной волнистой чертой, требуя создать конструктор. Вручную напишем конструктор перед методами, а также добавим пару констант:
// константы для конструктора
private static final String DATABASE_NAME = "cat_database.db";
private static final int DATABASE_VERSION = 1;
public CatsDataBase(Context context) {
// TODO Auto-generated constructor stub
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
КонстантаDATABASE_NAME отвечает за имя файла, в котором будет храниться база данных приложения.
Вторая константа DATABASE_VERSION требует дополнительных объяснений. Она отвечает за номер версии базы. Принцип её работы схож с номером версий самого приложения. Когда мы видим, что вышла новая версия Chrome 23, то понимаем, что пора обновляться. Аналогично поступает и само приложение, когда замечает, что номер версии базы стал другим. Как только программа заметила обновление номера базы, она запускает метод onUpgrade(), который у нас сформировался автоматически. В этом методе необходимо разместить код, который должен сработать при обновлении базы.
Метод onCreate() - создаётся база данных с необходимыми данными для работы.
Мы указали имя файла для базы данных и её номер. Также нужно указать имя таблицы и имена колонок для таблицы. Дляначала создадим простую таблицу, которая будет содержать только имена котов.
public static final String UID = "_id";
public static final String CATIMYA = "catimya";
В примере создали две константы. Первая константа - это просто идентификатор. Он будет настроен таким образом, что будет автоматически увеличиваться на единицу при каждом добавлении нового имени. Вимени идентификатора используется символ подчёркивания. Но разработчики Google решили использовать именно такой синтаксис, в противном случае у вас не будут работать запросы к базе данных, основанные на курсорах.
Для создания таблицы в SQL используется команда CREATE TABLE. Для удобства вынесем команду в отдельную строку. Аналогично поступим с командой DROP TABLE. Сами команды помещаются в метод execSQL(). Полный листинг сейчас будет выглядеть следующим образом:
package ru.alexanderklimov.databasedemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class CatsDataBase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "cat_database.db";
private static final int DATABASE_VERSION = 1;
public static final String TABLE_NAME = "contact_table";
public static final String UID = "_id";
public static final String CATIMYA = "catimya";
private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + TABLE_NAME + " (" + UID + " INTEGER PRIMARY KEY AUTOINCREMENT," + CATIMYA + " VARCHAR(255));";
private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS "+ TABLE_NAME;
public CatsDataBase(Context context) {
// TODO Auto-generated constructor stub
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(SQL_CREATE_ENTRIES);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
Log.w("LOG_TAG", "Обновление базы данных с версии " + oldVersion
+ " до версии " + newVersion + ", которое удалит все старые данные");
// Удаляем предыдущую таблицу при апгрейде
db.execSQL(SQL_DELETE_ENTRIES);
// Создаём новый экземпляр таблицы
onCreate(db); }}
Для работы с базой данных и для создания запросов рассмотрим некоторые методы. Напишемследующий кодс использованием методаonCreate():
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Инициализируем наш класс-обёртку
CatsDataBase sqh = new CatsDataBase(this);
// База нам нужна для записи и чтения
SQLiteDatabase sqdb = sqh.getWritableDatabase();
// закрываем соединения с базой данных
sqdb.close();
sqh.close();
}
Запустите проект. На первый взгляд ничего не произошло, но на самом деле в каталоге /data/data/ru.alexanderklimov.ru.databasedemo/databases появился файл cat_database.db. Вы можете в этом сами убедиться, если в эмуляторе откроете перспективу DDMS на вкладке File Explorer и просмотрите структуру файлов.
Теперь нам нужно научиться. Существуют различные способы для добавления и извлечения данные из базы данных.
Первый способ вставки данных заключается в использовании класса ContentValues. Вот пример для кнопки, когда данные из текстового поля попадают в базу данных:
case R.id.buttonCVInsert:
// Метод 1: INSERT через класс CONTENTVALUE
ContentValues cv = new ContentValues();
cv.put(CatsDataBase.CATIMYA, txtData.getText().toString());
// вызываем метод вставки
sqdb.insert(CatsDataBase.TABLE_NAME, CatsDataBase.CATIMYA, cv);
txtData.setText("");
break;
Способ очень удобен, требует мало кода и легко читаем. Вы создаёте экземпляр класса, а затем с помощью метода put() записываете в нужную колонку нужные данные. После чего вызывается метод insert(), который помещает подготовленные данные в таблицу.
Если колонок несколько, то вызывайте метод put() несколько раз:
values.put(CatsDataBase.CATIMYA, txtData.getText().toString());
values.put(CatsDataBase.EMAIL, txtEmail.getText().toString());
values.put(CatsDataBase.PHONE, txtPhone.getText().toString());
У метода insert() три аргумента. В первом указывается имя таблицы, в которую будет вноситься записи. В третьем указывается объект ContentValues, созданный ранее. Второй аргумент используется для указания колонки. SQL не позволяет вставлять пустую запись, и если будет использоваться пустой ContentValue, то укажите во втором аргументе null во избежание ошибки.
Второй способ использует традиционный SQL-запрос INSERT INTO.... Вы формируете обычный запрос в виде строки, а затем передаёте её в метод execSQL(). Основное неудобство при этом способе - не запутаться в кавычках. Если что-то не вставляется, то смотрите логи сообщений.
case R.id.buttonSQLQuery:
// Метод 2: INSERT через SQL-запрос
String insertQuery = "INSERT INTO " +
CatsDataBase.TABLE_NAME +
" (" + CatsDataBase.CATIMYA + ") VALUES ('" + txtData.getText().toString() + "')";
sqdb.execSQL(insertQuery);
txtData.setText("");
break;
Считывать данные также можно двумя способами. В любом случае результат возвращается в виде объекта Cursor. Не путайте его с курсором мыши, который бегает у вас на экране.
У метода query() множество параметров. В первом параметре укажите имя таблицы, во втором - массив имён колонок, далее идут дополнительные условия. Пока везде оставим null. Далее через цикл while извлекаем данные и помещаем в логи. Можете снова запустить проект и проверить, какие данные вы уже занесли в базу:
case R.id.buttonQuery:
Cursor cursor = sqdb.query(CatsDataBase.TABLE_NAME, new String[] {
CatsDataBase._ID, CatsDataBase.CATIMYA },
null, // The columns for the WHERE clause
null, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
null // The sort order
);
while (cursor.moveToNext()) {
// GET COLUMN INDICES + VALUES OF THOSE COLUMNS
int id = cursor.getInt(cursor.getColumnIndex(CatsDataBase._ID));
String imya = cursor.getString(cursor
.getColumnIndex(CatsDataBase.CATIMYA));
Log.i("LOG_TAG", "ROW " + id + " HAS IMYA " + imya);
}
cursor.close();
break;
Второй способ использует сырой (raw) SQL-запрос. Сначала формируется строка запроса и скармливается методу rawQuery().
case R.id.buttonRawQuery:
// Метод 2: Сырой SQL-запрос
String query = "SELECT " + CatsDataBase._ID + ", "
+ CatsDataBase.CATIMYA + " FROM " + CatsDataBase.TABLE_NAME;
Cursor cursor2 = sqdb.rawQuery(query, null);
while (cursor2.moveToNext()) {
int id = cursor2.getInt(cursor2
.getColumnIndex(CatsDataBase._ID));
String imya = cursor2.getString(cursor2
.getColumnIndex(CatsDataBase.CATIMYA));
Log.i("LOG_TAG", "ROW " + id + " HAS IMYA " + imya);
}
cursor2.close();
break;
4.2.1. Контент провайдеры и их использование
Контент-провайдер управляет доступом к хранилищу данных. Для реализации провайдера в Android приложении должен быть создан набор классов в соответствии с манифестом приложения. Один из этих классов должен быть наследником класса ContentProvider, который обеспечивает интерфейс между контент-провайдером и другими приложениями.
Основное назначение этого компонента приложения заключается в предоставлении другим приложениям доступа к данным, однако ничто не мешает в приложении иметь активность, которая позволит пользователю запрашивать и изменять данные, находящиеся под управлением контент-провайдера.
В мобильных приложениях контент-провайдеры необходимы в следующих случаях:
· приложение предоставляет сложные данные или файлы другим приложениям;
· приложение позволяет пользователям копировать сложные данные в другие приложения;
· приложение предоставляет специальные варианты поиска, используя поисковую платформу (framework).
Если приложение требует использования контент-провайдера, необходимо выполнить несколько этапов для создания этого компонент.
Данные, с которыми работают контент-провайдеры, могут быть организованы двумя способами:
· Данные представлены файлом, например, фотографии, аудио или видео. В этом случае необходимо хранить данные в собственной области памяти приложения. В ответ на запрос от другого приложения, провайдер может возвращать ссылку на файл.
· Данные представлены некоторой структурой, например, таблица, массив. В этом случае необходимо хранить данные в табличной форме. Строка таблицы представляет собой некоторую сущность, например, сотрудник или товар. А столбец - некоторое свойство этой сущности, например, имя сотрудника или цена товара. В системе Android общий способ хранения подобных данных - база данных SQLite, но можно использовать любой способ постоянного хранения.
Для создание класса-наследника от классаContentProvider напрямую или через любого его потомка необходимо переопределить обязательные методы.
Созданный контент-провайдер управляет доступом к структурированным данным, выполняя обработку запросов от других приложений. Все запросы, в конечном итоге, вызывают объект ContentResolver, который в свою очередь вызывает подходящий метод объекта ContentProvider для получения доступа.
query() |
- метод, извлекающий данные из провайдера, в качестве аргументов получает таблицу, строки и столбцы, а также порядок сортировки результата, возвращает объект типа Cursor. |
insert() |
- метод, добавляющий новую строку, в качестве аргументов получает таблицу, и значения элементов строки, возвращает URI добавленной строки. |
update() |
- метод, обновляющий существующие строки, в качестве аргументов получает таблицу, строки для обновления и новые значения элементов строк, возвращает количество обновленных строк. |
delete() |
- метод, удаляющий строки, в качестве аргументов принимает таблицу и строки для удаления, возвращает количество удаленных строк. |
getType() |
- метод, возвращающий String в формате MIME, который описывает тип данных, соответствующий URI. |
onCreate() |
- метод, вызываемый системой, сразу после создания провайдера, включает инициализацию провайдера. Стоит отметить, что провайдер не создается до тех пор, пока объект ContentResolver не попытается получить к нему доступ. |
Все вышеперечисленные методы, кроме onCreate(), вызываются приложением-клиентом. И все эти методы имеют такую же сигнатуру, как одноименные методы класса ContentResolver.
Определение строки авторизации провайдера, URI для его строк и имен столбцов. Если от провайдера требуется управление намерениями, необходимо определить действия намерений, внешние данные и флаги. Также необходимо определить разрешения, которые необходимы приложениям для доступа к данным провайдера. Все эти значения необходимо определить как константы в отдельном классе, этот класс в последствии можно предоставить другим разработчикам.
Наше приложение может сохранять разнообразую информацию о пользователе, какие-то связанные данные в файлах или настройках. Однако ОС Android уже хранит ряд важной информации, связанной с пользователем, к которой имеем доступ и которую мы можно использовать. Это и списки контактов, и файлы сохраненных изображений и видеоматериалов, и какие-то отметки о звонках и т.д., то есть некоторый контент. А для доступа к этому контенту в OC Android определены провайдеры контента (content provider)
В Android имеются следующие встроенные провайдеры, определенные в пакете android.content:
• AlarmClock: управление будильником
• Browser: история браузера и закладки
• CalendarContract: каледарь и информаци о событиях
• CallLog: информация о звонках
• ContactsContract: контакты
• MediaStore: медиа-файлы
• SearchRecentSuggestions: подсказкипопоиску
• Settings: системные настройки
• UserDictionary: словарь слов, которые используются для быстрого набора
• VoicemailContract: записи голосовой почты
Контакты в Android обладают встроенным API, позволяющим производить чтения и изменение списка контактов. Все контакты хранятся в базе данных SQLite, однако они не представляют единой таблицы. Для контактов отведено три таблицы, связанных отношением один-ко-многим: таблица для хранения информации о людях, таблица их телефонов и и таблица адресов их электоронных почт. Но благодаря имеющемуся в Android API мы можно абстрагироваться от связей между таблицами.
Итак, для доступа к контактам нам надо установить соответствующие разрешения в файле манифеста приложения:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.eugene.sqliteapp" >
<uses-permission android:name="android.permission.READ_CONTACTS" android:maxSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Для вывода списка контактов определим следующую разметку интерфейса:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contact_list">
</TextView>
<ListView
android:id="@+id/contactList"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Для вывода списка контактов воспользуемся элементом ListView. И в классе activity получим контакты:
package com.example.eugene.sqliteapp;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.provider.ContactsContract;
import android.database.Cursor;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView contactsList = (ListView) findViewById(R.id.contactList);
ArrayList<String> contacts = new ArrayList<String>();
Cursor contactsCursor = getContentResolver()
.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while (contactsCursor.moveToNext()) {
// получаемкаждыйконтакт
String contact = contactsCursor.getString(
contactsCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
// добавляем контакт в список
contacts.add(contact);
}
// создаемадаптер
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, contacts);
// устанавливаемдляспискаадаптер
contactsList.setAdapter(adapter); }
}
Все контакты и сопутствующий функционал хранятся в специальных базах данных SQLite. Но нам не надо напрямую работать с ними. Мы можно воспользоваться объектом класса Cursor. Чтобы его получить, сначала вызывается метод getContentResolver(), который возвращает объект ContentResolver. Затем по цепочке вызывается метод query(). В этот метод передается ряд параметров, первый из которых представляет URI - ресурс, который мы хотим получить.
Для обращения к базе данных контактов используется константа ContactsContract.Contacts.CONTENT_URI.
Метод contactsCursor.moveToNext() позволяет последовательно перемещаться по записям контактов, считывая по одному контакту через вызов contactsCursor.getString().
Все полученные контакты добавляются в список, который потом выводится в ListView.
Добавление контактов представляет собой запрос на изменение списка контактов, то есть его запись. Поэтому нам надо установить соответствующее разрешение в файле манифеста. Возьмем проект из прошлой темы и добавим в файл манифеста после корневого тега manifest следующую строку
<uses-permission android:name="android.permission.WRITE_CONTACTS" android:maxSdkVersion="21" />
Чтобы вносить изменения в список контактов, следует установить разрешение android.permission.WRITE_CONTACTS
Для добавления контакта добавим новую activity. Назовем ее AddContactActivity. Определим в ее разметке текстовое поле и кнопку для добавления контакта:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<TextView android:text="Новыйконтакт:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/contactText"
android:layout_width="match_parent"
android:layout_height="45dip" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Добавить"
android:onClick="addContact"/>
</LinearLayout>
В коде activity пропишем обработчик addContact с добавлением контакта:
package com.example.eugene.sqliteapp;
import android.provider.ContactsContract;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.content.ContentValues;
import android.content.ContentUris;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.widget.EditText;
import android.widget.Toast;
import android.net.Uri;
public class AddContactActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_contact);
}
public void addContact(View view) {
ContentValues contactValues = new ContentValues();
EditText contactText = (EditText) findViewById(R.id.contactText);
String newContact = contactText.getText().toString();
contactValues.put(RawContacts.ACCOUNT_NAME, newContact);
contactValues.put(RawContacts.ACCOUNT_TYPE, newContact);
Uri newUri = getContentResolver().insert(RawContacts.CONTENT_URI, contactValues);
long rawContactsId = ContentUris.parseId(newUri);
contactValues.clear();
contactValues.put(Data.RAW_CONTACT_ID, rawContactsId);
contactValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
contactValues.put(StructuredName.DISPLAY_NAME, newContact);
getContentResolver().insert(Data.CONTENT_URI, contactValues);
Toast.makeText(this, newContact + " добавленвсписокконтактов", Toast.LENGTH_LONG).show(); }
}
Весь код добавления находится в обработчике нажатия кнопки addContact. В Android контакты распределяются по трем таблицам: contacts, raw contacts и data. И нам надо добавить новый контакт в две последне таблицы. В таблицу contact в силу настроек мы добавить не можно, но это и не нужно.
Данные контакта представляют объект ContentValues, который состоит из ключей и их значений, то есть объект словаря. После его создания происходит добавление в него пары элементов:
contactValues.put(RawContacts.ACCOUNT_NAME, newContact);
contactValues.put(RawContacts.ACCOUNT_TYPE, newContact);
Здесь устанавливается название и тип контакта. В качестве ключей выставляются значения RawContacts.ACCOUNT_NAME и RawContacts.ACCOUNT_TYPE, а в качестве их значения - текст из текстового поля.
Далее этот объект добавляется в таблицу RawContacts с помощью метода insert():
Uri newUri = getContentResolver().insert(RawContacts.CONTENT_URI, contactValues);
Метод insert() возвращает URI - ссылку на добавленный объект в таблице, у которого мы можно получить id. Затем после очистки мы подготавливаем объект для доабвления в таблицу Data, вновь наполняя его данными:
contactValues.put(Data.RAW_CONTACT_ID, rawContactsId);
contactValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
contactValues.put(StructuredName.DISPLAY_NAME, newContact);
И опять добавление производит метод insert():
getContentResolver().insert(Data.CONTENT_URI, contactValues);
Чтобы обратиться к этой activity из главной мы можно на главной создать соответствующий пункт меню, по выбору которого будет перекидывать на страницу добавления нового контакта. Например, пункт меню в файле ресурса меню главной activity:
<menuxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item
android:id="@+id/action_settings"
android:title="Добавить"
android:orderInCategory="100"
app:showAsAction="never" />
</menu>
Иобработкаэтогопунктавкодеглавной activity:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
Intent intent = new Intent(this, AddContactActivity.class);
startActivity(intent);
return true; }
return super.onOptionsItemSelected(item);
}
Для доступа к данным какого-либо контент-провайдера используется объект класса ContentResolver, который можно получить с помощью метода getContentResolver контекста приложения для связи с поставщиком в качестве клиента:
ContentResolver cr = getApplicationContext().getContentResolver();
Объект ContentResolver взаимодействует с объектом контент-провайдера, отправляя ему запросы клиента. Контент-провайдер обрабатывает запросы и возвращает результаты обработки.
Контент-провайдеры представляют свои данные потребителям в виде одной или нескольких таблиц подобно таблицам реляционных БД. Каждая строка при этом является отдельным «объектом» со свойствами, указанными в соответствующих именованных полях. Как правило, каждая строка имеет уникальный целочисленный индекс и именем «_id», который служит для однозначной идентификации требуемого объекта.
Контент-провайдеры, обычно предоставляют минимум два URI для работы с данными: один для запросов, требующих все данные сразу, а другой – для обращения к конкретной «строке». В последнем случае в конце URI добавляется / (который совпадает с индексом «_id»).
Запросы на получение данных похожи на запросы к БД, при этом используется метод query объекта ContentResolver. Ответ также приходит в виде курсора, «нацеленного» на результирующий набор данных (выбранные строки таблицы):
ContentResolver cr = getContentResolver();
// получить данные всех контактов
Cursor c = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null,
null, null);
// получить все строки, где третье поле имеет конкретное
// значение и отсортировать по пятому полю
String where = KEY_COL3 + "=" + requiredValue;
String order = KEY_COL5;
Cursor someRows = cr
.query(MyProvider.CONTENT_URI, null, where, null, order);
Изменение данных:
Uri myRowUri = cr.insert(SampleProvider.CONTENT_URI, newRow);
// Массовая вставка:
ContentValues[] valueArray = new ContentValues[5];
// здесь заполняем массив
// делаем вставку
int count = cr.bulkInsert(MyProvider.CONTENT_URI, valueArray);
При вставке одного элемента метод insert возвращает URI вставленного элемента, а при массовой вставке возвращается количество вставленных элементов.Пример удаления:
ContentResolver cr = getContentResolver();
// удаление конкретной строки
cr.delete(myRowUri, null, null);
// удаление нескольких строк
String where = "_id < 5";
cr.delete(MyProvider.CONTENT_URI, where, null);
Пример изменения:
ContentValues newValues = new ContentValues();
newValues.put(COLUMN_NAME, newValue);
String where = "_id < 5";
getContentResolver().update(MyProvider.CONTENT_URI, newValues,
where, null);
4.2.2. Обмен сообщениями. Messaging
Обмен сообщениями в устройствах Android – это сервис, который позволяет разработчикам отправлять данные с серверов в приложения на Android устройствах.
Сервис обмена сообщениями предоставляет простой и легкий механизм, который могут использовать сервера для того, чтобы сообщить мобильным приложениям о связи с сервером напрямую для получения обновлений приложения или данных пользователя.
Сервис обмена сообщениями управляет всеми аспектами организации очередей сообщений и доставки к целевому приложению, запущенному на целевом устройстве. Главные характеристики сервиса обмена сообщениями:
1. Он позволяет сторонним серверам приложений отправлять небольшие сообщения своим Android приложениям. Сервис обмена сообщениями не предназначен для отправки большого количества пользовательских данных через сообщения. Напротив, он должен использоваться для сообщения приложению, что есть новые данные на сервере, и что приложение может забрать их.
2. Приложение на Android устройстве не нужно запускать для получения сообщений. Система запустит приложение через целевую трансляцию, когда придет сообщение, если приложение установлено с соответствующими приемником трансляции и разрешениями.
3. Он использует существующее соединение для сервисов Google. Это требует от пользователей установки учетной записи Google на их мобильных устройствах.
SMS (Short Message Service) означает «служба коротких сообщений». Эту службу также часто называют обменом текстовыми сообщениями. В Android SDK поддерживаются функции отправки и получения текстовых сообщений. Для отправки смс используется константа Intent.ACTION_SEND. Создадим простейший интферфейс для отправки смс:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText android:id="@+id/number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:hint="Введите номер"
android:editable="true"
android:singleLine="true" />
<EditText android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:hint="Введите сообщение"
android:editable="true"
android:singleLine="false" />
<Button android:id="@+id/sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Отправить"
android:onClick="smsSend" />
</LinearLayout>
И определим обработчик кнопки в коде activity:
package com.example.eugene.telephoneapp;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.content.Intent;
import android.view.View;
import android.widget.EditText;
import android.net.Uri;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void smsSend(View v) {
EditText number=(EditText)findViewById(R.id.number);
EditText message=(EditText)findViewById(R.id.message);
String toSms="smsto:"+number.getText().toString();
String messageText= message.getText().toString();
Intent sms=new Intent(Intent.ACTION_SENDTO, Uri.parse(toSms));
sms.putExtra("sms_body", messageText);
startActivity(sms); }
}
После ввода номера телефона и сообщения нажмем на кнопку отправки, и система нам предоставит выбор между имеющимися программами для отправки сообщений, то есть программа отправки смс и скайп.
Рис. 4.2. Отправка сообщения
После выбора программы можно будет подредактировать и уже потом окончательно отправить сообщение.
Но как и с телефоном мы также можно использовать прямую отправку смс без сторонних программ. Для этого, во-первых, добавим разрешение SEND_SMS в файл манифеста:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.eugene.telephoneapp" >
<uses-permission android:name="android.permission.SEND_SMS" />
Исправим код обработчика кнопки:
public void dial(View v) {
EditText number=(EditText)findViewById(R.id.number);
EditText message=(EditText)findViewById(R.id.message);
String numberText = number.getText().toString();
String messageText= message.getText().toString();
SmsManager.getDefault()
.sendTextMessage(numberText, null, messageText.toString(), null, null);
}
Для
отправки используется класс SmsManager из пакета android.telephony.
Его статический метод getDefault()
возвращает объект данного класса. Для самой отправки применяется метод sendTextMessage()
,
который принимает пять параметров: номер телефона, адрес сервисного центра (в примере
null), сообщение к отправке и два спецальных объкта PendingIntent, которые
показывают статус отправки и доставки (в примере также null).
4.3.Определение местоположения пользователя
4.3.1. Сетевое программирование в мобильных приложениях
Приложение Android, как и любые другие программы для мобильных устройств, обычно не велики по размеру, но очень функциональны. Один из способов, которая достигается такая разнообразная функциональность на небольшом устройстве- получение программы информации из различных источников. Например, на T-mobileG1 при поставке содержится программа Maps, довольно совершенными картографическими функциями.
Однако нам известно что эта программа интегрирована с GoogleMaps и другими службами, благодаря которыми достигается такое совершенство. Создаваемые вами программы, скорее всего также будут активно использовать информацию, получаемую от других программ.
Обычно для интеграции нескольких программ используется протокол HTTP. Например, в Интернете может быть доступен сервлет Java, предоставляющий службы, которые нужны вам для более эффективной работы одной из программ Android .
Как применить этот сервлет в Android? Интересно отметить, что в состав AndroidSDKвходит клиент HttpClient для Аpache, являющийся универсальным средством для работы с J2EE. В AndroidSDKсодержится версия HttpClient, модифицированная с учетом требований Android, но прикладные программные интерфейсы (API) очень похожи на соответствующие APIиз J2EE.
Клиент ApacheHttpClientявляется комплексным и многосторонним. Хотя этот клиент полностью поддерживает протокол HTTP, вы, скорее всего, будете пользоваться методами HTTPGETи POST.
Ниже показана общая схема использования HttpClient.
1. Создание HttpClient(или получение имеющейся ссылки).
2. Инстанцирование нового HTTP-метода, например PostMethodили GetMethod.
3. Установка имен и значений параметров HTTP.
4. Выполнение вызова HTTPпри помощи HttpClient.
5. Обработка отклика HTTP.
В коде показано, как выполнить запрос HTTPGETпри помощи HttpClient. Поскольку в коде заложена попытка выхода в Интернет, при использовании HTTP-вызовов с применением HttpClient необходимо добавить в файл описания право доступа android.permission.INTERNET. Использование HttpClient для получения запроса HTTP GET:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
public class TestHttpGet {
public void executeHttpGet() throws Exception {
BufferedReader in = null; try {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.setURI(new URI("http://code.google.com/android/"));
HttpResponse response = client.execute(request);
in = new BufferedReader (new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer(“”);
String line =
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) { sb.appendline + NL); }
in.closed; String page = sb.toString();
System.out.println(page);
} finally { if (in != null) { try { in.closed;
} catch (IOException e) { e.printStackTraced; }
} } } }
В HttpClientпредусмотрены абстракции для описания HTTP-запросов различных типов, в частности HttpGet, HttpPostи т.д. Для выполнения самого HTTP-запроса вызывается метод client.execute(). После выполнения запроса код считывает весь полученный ответ и помещает его в объект-строку(string). Обратите внимание — BufferedReaderзакрывается в блоке кода finally. Вместе с этим завершается и базовое НТТР- соединение.
Класс из кода не является дополнением android.арр.Activity. Иными словами, для работы с HttpClientвам не обязательно находиться в контексте определенного явления, так как HttpClientвходит в пакет программ Android и этот клиент может использоваться и в контексте компонента Android, и как часть отдельного класса.
Код выполняет HTTP-запрос, не передавая на сервер никаких параметров HTTP. Параметры «имя/значение» можно передавать вместе с запросом, добавляя пары «имя/значение» к URL. Добавление параметров к запросу HTTP GET:
HttpGet method = new HttpGet("http://somehost/WS2/Upload.aspx?one=valueGoesHere"); client.execute(method);
При выполнении HTTP GETпараметры (имена и значения) запроса передаются в URL. При передаче параметров таким способом вы сталкиваетесь с рядом ограничений. Дело в том, что длина гиперссылки не должна превышать 2048 символов. Вместо HTTP GETможно использовать HTTP POST. Метод POSTявляется более гибким — при его применении параметры передаются в теле запроса.
Выполнение вызова HTTP POSTочень напоминает вызов HTTP GET. Выполнение запроса HTTPPOSTпри помощи HttpClient:
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameVa1uePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost:
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
public class TestHttpPost {
public String executeHttpPost() throws Exception {
BufferedReader in = null; try {
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(
"http://somewebsite/WS2/Upload.aspx");
List<NameValuePair> postParameters = new ArrayList<NameValuePair>(); postParameters.add(new BasicNameValuePair("one","valueGoesHere"));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity( postParameters);
request.setEntity(formEntity);
HttpResponse response = client.execute(request);
in = new BufferedReader(new
InputStreamReader(response.getEntity().getContent())):
StringBuffer sb = new StringBuffer(“”);
String line =String NL - System.getProperty("line.separator");
while ((line = in.readLine()) != null) { sb.appendline + NL);
in.closed;
String result = sb.toString(); return result;
} finally { if (in != null) { try { in.closed;
} catch (IOException e) { e.printStackTraced;
} } }
При выполнении вызовов HTTP POSTпараметры форм вида «имя/значение» обычно кодируются как часть URL, передаваемой при запросе HTTP. Чтобы осуществить такую операциюнужно создать список экземпляров объектов NameValuePaiг а затем заключить этот список в объект UrlEncodedFormEntity.
Объект NameValuePaiг содержит в себе комбинацию «имя/значение», а класс UrlEncodedFormEntityумеет кодировать список объектов NameValuePaiг для последующего их применения в вызовах HTTP(обычно используются вызовы POST). После создания UrlEncodedFormEntityможно задать UrlEncodedFormEntityв качестве типа сущности (entity type) HttpPostа затем выполнить запрос.
Далее создается список объектов NameValuePaiг, который заполняется параметрами отдельных пар «имя/значение». Именем параметра будет one, а значением – valueGoesHere. Затем создается экземпляр UrlEncodedFormEntity. Его конструктору передается список NameValuePairobjects. Наконец, мы вызываем метод setEntity. А запрос POSTвыполняем при помощи экземпляра HttpClient.
HTTP POST позволяет передавать простые параметры «имя/значение», а также сложные параметры, например файлы. HTTP POSTтакже может работать с запросами другого формата, который называется multipartPOST. Используя такой тип, вы можете передавать с запросом не только пары «имя/значение», но и любые файлы. К сожалению, multipartPOSTнапрямую не поддерживается в той версии HttpClient, которая входит в состав Android.
Многие мобильные приложения используют архитектуру клиент-сервер. Общая схема следующая:
· сервер — представляет собой программу, работающую на удаленном компьютере, и реализующую функционал «общения» с приложениями-клиентами (слушает запросы, распознает переданные параметры и значения, корректно отвечает на них);
· клиент —программа на мобильном устройстве, которая умеет формировать понятный серверу запрос и читать полученный ответ;
· интерфейс взаимодействия - формат и способ передачи/получения запросов/ответов обеими сторонами.
Реализуем сервер и Android клиент, работающий с ним. Как пример, будем использовать мобильный интернет-мессенджер (Viber, ICQ), а приложение назовем «интернет-чат».
Клиент, установленный на устройстве А, посылает сообщение для клиента, установленного на устройстве Б и наоборот. Сервер играет роль связующего звена между устройством А и Б… С, Д… и т.д. Также он играет роль «накопителя» сообщений, для их восстановления, на случай удаления на одном из клиентских устройств.
Для хранения сообщений используем SQL БД как на сервере, так и на устройствах-клиентах. Дополнительно, интернет-чат будет уметь стартовать вместе с запуском устройства и работать в фоне. Взаимодействие будет происходить путем HTTP запросов и JSON ответов.
Для реализации «сервера», нам нужно зарегистрироваться на любом хостинге, который дает возможность работы с SQL и PHP.
Создаем пустую SQL БД, в ней создаем таблицу.
CREATETABLE`chat` (
`_id`int(11) NOTNULL AUTO_INCREMENT,
`author`textCHARACTERSET utf8 COLLATE utf8_unicode_ci NOTNULL,
`client`textCHARACTERSET utf8 COLLATE utf8_unicode_ci NOTNULL,
`data`bigint(20) NOTNULL,
`text`textCHARACTERSET utf8 COLLATE utf8_unicode_ci NOTNULL,
PRIMARYKEY (`_id`) )
Структура следующая:
1. author — автор сообщения;
2. client — получатель сообщения;
3. data — время и дата получения сообщения на сервере;
4. text — сообщение.
В двух следующих файлах необходимо изменить переменные, содержащие данные для доступа к БД, на свои, полученные пользователем при регистрации «сервера».
$mysql_host = "localhost"; // sql сервер, может быть локальным или внешним.
// например mysql5.000webhost.com
$mysql_user = "l29340eb_chat"; // пользователь
$mysql_password = "123456789"; // пароль
$mysql_database =
"l29340eb_chat"; // имя базы данных на сервере SQL
Файл chat.php это наш api, реализующий структуру понятных серверу запросов.
<?php // сохранить в utf-8 !
// эти значения задавались при создании БД на сервере
$mysql_host = "localhost"; // sql сервер
$mysql_user = "l29340eb_chat"; // пользователь
$mysql_password = "123456789"; // пароль
$mysql_database = "l29340eb_chat"; // имя базы данных chat
// проверяем переданные в строке запроса параметры
// например ...chat.php?action=select
// переменная action может быть:
// select - формируем содержимое таблицы chat в JSON и отправляем назад
// insert - встваляем новую строку в таблицу chat, так же нужны 4 параметра : автор/получатель/время создания/сообщение
// ВАЖНО время создания мы не передаем в параметрах, его берем текущее на сервере
// delete - удаляет ВСЕ записи из таблицы chat - пусть будет для быстрой очистки
// получимпереданный action
if (isset($_GET["action"])) { $action = $_GET['action']; }
// если action=insert тогдаполучимеще author|client|text
if (isset($_GET["author"])) { $author = $_GET['author'];}
if (isset($_GET["client"])) { $client = $_GET['client'];}
if (isset($_GET["text"])) { $text = $_GET['text']; }
// если action=select тогда получим еще data - от после какого времени передавать ответ
if (isset($_GET["data"])) { $data = $_GET['data'];}
mysql_connect($mysql_host, $mysql_user, $mysql_password); // коннектксерверу SQL
mysql_select_db($mysql_database); // коннекткБДнасервере
mysql_set_charset('utf8'); // кодировка
// обрабатываем запрос если он был
if($action == select){ // еслидействие SELECT
if($data == null){
// выберем из таблицы chat ВСЕ данные что есть и вернем их в JSON
$q=mysql_query("SELECT * FROM chat");
}else{
// выберем из таблицы chat ВСЕ данные ПОЗНЕЕ ОПРЕДЕЛЕННОГО ВРЕМЕНИ и вернем их в JSON
$q=mysql_query("SELECT * FROM chat WHERE data > $data"); }
while($e=mysql_fetch_assoc($q))
$output[]=$e;
print(json_encode($output)); }
if($action == insert && $author != null && $client != null && $text != null){ // еслидействие INSERT иестьвсечтонужно
// время = время сервера а не клиента !
$current_time = round(microtime(1) * 1000);
// примерпередачискриптуданных:
// chat.php?action=insert&author=author&client=client&text=text
// вставимстрокуспереданнымипараметрами
mysql_query("INSERT INTO `chat`(`author`,`client`,`data`,`text`) VALUES ('$author','$client','$current_time','$text')");}
if($action == delete){ // еслидействие DELETE
// полностьюобнулимтаблицузаписей
mysql_query("TRUNCATE TABLE `chat`"); }
mysql_close();
?>
Структура запросов к api:
· обязательный атрибут action — может быть равен select (сервер ответит списком записей из своей БД), insert (сервер добавить новую запись в свою БД), delete (сервер очистит свою БД)
· если action=insert, нам нужно будет передать дополнительные параметры: author (кто написал сообщение), client (кому адресовано сообщение), text (сообщение)
· action=select может содержать дополнительный параметр data, в этом случае ответ сервера содержит не все сообщения из БД, а только те, у которых время создания позднее переданного
Примеры:
· chat.php?action=delete – удалит все записи на сервере
· chat.php?action=insert&author=Jon&client=Smith&text=Hello — добавит на сервере новую запись: автор Jon, получатель Smith, содержание Hello
· chat.php?action=select&data=151351333 — вернет все записи, полученные после переданного времени в long формате
Клиентская часть
В фоне работает FoneService.java, который, в отдельном потоке, каждые 15 секунд делает запрос на сервер. Если ответ сервера содержит новые сообщения, FoneService.java записывает их в локальную БД и отправляет сообщение ChatActivity.java о необходимости обновить ListView, с сообщениями. ChatActivity.java (если она в этот момент открыта) получает сообщение и обновляет содержимое ListView из локальной БД.
Отправка нового сообщения из ChatActivity.java происходит сразу на сервер, минуя FoneService.java. При этом наше сообщение НЕ записывается в локальную БД! Там оно появится только после получения его назад в виде ответа сервера. Такую реализацию я использовал в связи с важным нюансом работы любого интернет-чата — обязательной группировкой сообщений по времени. Если не использовать группировку по времени, будет нарушена последовательность сообщений. Учитывая, что клиентские приложения просто физически не могут быть синхронизированы с точностью до миллисекунд, а возможно будут работать даже в разных часовых поясах, логичнее всего будет использовать время сервера.
Создавая новое сообщение, мы передаем запросом на сервер: имя автора сообщения, имя получателя сообщения, текст сообщения. Получая эту запись назад, в виде ответа сервера, мы получаем то, что отправляли + четвертый параметр: время получения сообщения сервером.
В MainActivity.java, для наглядности, я добавил возможность удаления сообщений из локальной БД — это эквивалентно чистой установке приложения (в этом случае FoneService отправит на сервер запрос на получение всех сообщений выбранного чата). Так же есть возможность послать запрос на удаление всех сообщений из БД, расположенной на сервере.
Платформа Android основана на ядре Linux® и содержит богатый набор сетевых средств.
В таблице 1 приведены некоторые пакеты, относящиеся к сетевым возможностям, которые присутствуют в SDK Android.
Таблица 1. Сетевые пакеты SDK Android
Пакет |
Описание |
java.net |
Содержит классы, связанные с сетевыми функциями, в том числе сокеты потоков и датаграмм, протокол IP, а также общие средства для работы с HTTP. Это многоцелевой ресурс для работы с сетями. Пользуясь этим знакомым пакетом, опытные Java-разработчики смогут сразу же приступить к созданию приложений. |
java.io |
Этот пакет чрезвычайно важен, хотя и не относится непосредственно к сетям. Его классы используются сокетами и соединениями, содержащимися в других пакетах Java. Они используются также для обмена с локальными файлами (что часто происходит при взаимодействии с сетью). |
java.nio |
Содержит классы, которые служат буфером для определенных типов данных. Удобен для организации сетевой связи между двумя конечными точками средствами Java. |
org.apache.* |
Набор пакетов, которые обеспечивают точный контроль и функции для HTTP-коммуникаций. Это Apache - знакомый и популярный Web-сервер с открытым исходным кодом. |
android.net |
Содержит дополнительные сокеты доступа к сети в дополнение к основным классам java.net.*. Этот пакет включает в себя класс URI, который часто используется в разработке приложений Android, выходящих за рамки традиционных сетевых функций. |
android.net.http |
Содержит классы для работы с сертификатами SSL. |
android.net.wifi |
Содержит классы для реализации всех аспектов WiFi (802.11 Wireless Ethernet) на платформе Android. Не все устройства оснащены возможностями WiFi, особенно учитывая, что Android пробивает себе дорогу в сегмент "раскладушек" от таких производителей сотовых телефонов, как Motorola и LG. |
android.telephony.gsm |
Содержит классы, необходимые для управления (текстовыми) сообщениями SMS и для их передачи. Со временем, вероятно, появятся дополнительные пакеты, предоставляющие аналогичные функции в не-GSM сетях, таких как CDMA, что-то вроде android.telephony.cdma. |
Приведенный выше список не является исчерпывающим, но дает общее представление о том, на что способна эта платформа. В следующем разделе рассматривается несколько простых примеров работы с сетями.
Чтобы продемонстрировать, как легко подключить Android к сети, приведем пример извлечения текста из Web-страницы.
Рис.4.5. Извлечение текста из Web-страницы
Сначала рассмотрим аспекты пользовательского интерфейса, а затем разберем код, относящийся к сети.
Здесь имеется три элемента пользовательского интерфейса:
· EditText позволяет указать Web-страницу (на рисунке 1).
· кнопка используется, чтобы поручить программе извлечь текст из веб-страницы.
· полученные данные отображаются в поле TextView.
В листинге 1 представлен файл main.xml, который представляет собой полный макет интерфейса пользователя для этого приложения.
Листинг 1. main.xml
<?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" > <EditText android:layout_height="wrap_content" android:id="@+id/address" android:layout_width="fill_parent" android:text="http://google.com"> </EditText> <Button android:id="@+id/ButtonGo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="go!" > </Button> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffffff" android:textColor="#000000" android:id="@+id/pagetext" /> </LinearLayout> |
В листинге 2 показан код Java для данного примера.
Листинг 2. GetWebPage.java
package com.msi.getwebpage; import android.app.Activity; import android.os.Bundle; // used for interacting with user interface import android.widget.Button; import android.widget.TextView; import android.widget.EditText; import android.view.View; // used for passing data import android.os.Handler; import android.os.Message; // used for connectivity import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; public class GetWebPage extends Activity { /** Called when the activity is first created. */ Handler h; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final EditText eText = (EditText) findViewById(R.id.address); final TextView tView = (TextView) findViewById(R.id.pagetext); this.h = new Handler() { @Override public void handleMessage(Message msg) { // process incoming messages here switch (msg.what) { case 0: tView.append((String) msg.obj); break; } super.handleMessage(msg); } }; final Button button = (Button) findViewById(R.id.ButtonGo); button.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { try { tView.setText(""); // Perform action on click URL url = new URL(eText.getText().toString()); URLConnection conn = url.openConnection(); // Get the response BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = ""; while ((line = rd.readLine()) != null) { Message lmsg; lmsg = new Message(); lmsg.obj = line; lmsg.what = 0; GetWebPage.this.h.sendMessage(lmsg); } } catch (Exception e) { } } }); }} |
Этот код можно разделить на несколько общих областей. Несколько важных (обязательных) операторов импорта обеспечивают правильные ссылки на используемые в приложении классы пользовательского интерфейса, передаваемых данных и сетевых функций. Весь код, относящийся к работе с сетями, находится в методе OnClick класса OnClickListener. Он извлекается при нажатии кнопки, помеченной на рисунке 1.
Классы URL и URLConnection обеспечивают фактическую связь с выбранным пользователем Web-сайтом. Пример BufferedReader выполняет чтение данных, поступающих через соединение с Web-сайтом. После чтения каждой строки ее текст добавляется к TextView. Эти данные не просто присваиваются TextView (хотя в данном примере это можно было бы сделать). Мы предложили механизм создания объекта сообщения и передачи этого объекта экземпляру обработчика. Этот предпочтительный способ обновления пользовательского интерфейса, особенно в реальных приложениях, где несколько потоков могут выполняться одновременно.
В данном примере приложение Android поддерживает связь с Web-сервером HTTP, таким как Apache или Internet Information Server (IIS на сервере Microsoft®). Если бы приложение непосредственно обращалось к сокету TCP, а не HTTP, его пришлось бы строить иначе. В листинге 3 представлен фрагмент кода, демонстрирующий другие средства взаимодействия с удаленным сервером. Этот код реализован как отдельный поток.
Листинг 3. Клиент времени.
public class Requester extends Thread { Socket requestSocket; String message; StringBuilder returnStringBuffer = new StringBuilder(); Message lmsg; int ch; @Override public void run() { try { this.requestSocket = new Socket("remote.servername.com", 13); InputStreamReader isr = new InputStreamReader(this.requestSocket. getInputStream(), "ISO-8859-1"); while ((this.ch = isr.read()) != -1) { this.returnStringBuffer.append((char) this.ch); } this.message = this.returnStringBuffer.toString(); this.lmsg = new Message(); this.lmsg.obj = this.message; this.lmsg.what = 0; h.sendMessage(this.lmsg); this.requestSocket.close(); } catch (Exception ee) { Log.d("sample application", "failed to read data" + ee.getMessage()); } } } |
Как и в предыдущем примере, приведенный выше код использует подход сообщения и обработчика для возврата данных отправителю с обновлением пользовательского интерфейса и последующей обработкой. В отличие от кода из листинга 1, этот пример не связывается с сервером HTTP, поэтому класс URLConnection не используется. Вместо этого, с помощью класса более низкого уровня Socket через порт 13 открывается потоковое сокет-соединение с удаленным сервером. Порт 13 представляет собой классическое приложение "сервера времени".
Сервер времени принимает входящее сокет-соединение и возвращает в вызывающий сокет дату и время в текстовом формате. Отправив данные, сервер закрывает сокет. Этот пример демонстрирует также использование метода InputStreamReader и конкретной кодировки.
Еще одна задача, которую можно решать с помощью Android — отправка текстовых сообщений. Пример приведен в листинге 4.
Листинг 4. Отправка текстового сообщения
void sendMessage(String recipient,String myMessage) { SmsManager sm = SmsManager.getDefault(); sm.sendTextMessage("destination number",null,"hello there",null,null); } |
Процесс отправки текстового сообщения предельно прост. Сначала получим ссылку на SmsManager с помощью статического метода getDefault(). Затем применяем метод sendTextMessage. Аргументы:
Номер сотового телефона получателя
Включает код города.
Телефон сервисного центра
Значение null указывает на ваше согласие воспользоваться услугами сервисного центра по умолчанию для обработки сообщений. Во всех приложениях, за исключением самых передовых, используйте для этого параметра значение null.
Ваше сообщение
Длина сообщения должна быть меньше 160 байт, иначе данные будут разбиты на несколько сообщений.
Интент отправки
Факультативное действие (intent), инициируемое после отправки сообщения или в случае ошибки. Если такое уведомление не требуется, этому параметру можно передать значение null.
Интент доставки
Факультативное действие (intent), инициируемое после получения подтверждения доставки. Если такое уведомление не требуется, этому параметру можно передать значение null.
ПлатформаAndroid, подключенная к Web-странице или специальному приложению TCP показано в листинге 4, передача текстовых сообщений осуществляется очень просто. С помощью факультативных параметров интентов можно также предпринимать какие-то действия, когда сообщение отправлено, а затем - когда оно доставлено. Это мощная функция доступна не на всех мобильных платформах.
4.3.2. Определение местоположения пользователя
Android устройства могут предоставить нам данные по нашему текущему местоположению. Это очень удобно, например, пользования картой, получения актуальной для вашей местности информации (прогноз погоды) и пр.
Для этого используется провайдер и через провайдер можно получить данные.
На данный момент есть два провайдера: GPS и Network.
GPS –это данные с GPS-спутников.
Network – это координаты, которые можно получить через сотовую связь или WiFi. Для того чтоб этот провайдер работал нужен Интернет.
Напишем простое приложение, которое будет запрашивать и отображать координаты. Создаёмпроект. В strings.xml добавимстроки:
<stringname="provider_gps">GPS</string>
<stringname="provider_network">Network</string>
<stringname="location_settings">Location settings</string>
Для отображения приложения на экране main.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/tvTitleGPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/provider_gps"
android:textSize="30sp">
</TextView>
<TextView
android:id="@+id/tvEnabledGPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<TextView
android:id="@+id/tvStatusGPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<TextView
android:id="@+id/tvLocationGPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<TextView
android:id="@+id/tvTitleNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/provider_network"
android:textSize="30sp">
</TextView>
<TextView
android:id="@+id/tvEnabledNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<TextView
android:id="@+id/tvStatusNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<TextView
android:id="@+id/tvLocationNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp">
</TextView>
<Button
android:id="@+id/btnLocationSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:onClick="onClickLocationSettings"
android:text="@string/location_settings">
</Button>
</LinearLayout>
Несколько TextView, в которые мы будем выводить данные, и кнопка для открытия настроек местоположения.
MainActivity.java:
package ru.develop.p1381location;
import java.util.Date;
import android.app.Activity;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
TextView tvEnabledGPS;
TextView tvStatusGPS;
TextView tvLocationGPS;
TextView tvEnabledNet;
TextView tvStatusNet;
TextView tvLocationNet;
private LocationManager locationManager;
StringBuilder sbGPS = new StringBuilder();
StringBuilder sbNet = new StringBuilder();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvEnabledGPS = (TextView) findViewById(R.id.tvEnabledGPS);
tvStatusGPS = (TextView) findViewById(R.id.tvStatusGPS);
tvLocationGPS = (TextView) findViewById(R.id.tvLocationGPS);
tvEnabledNet = (TextView) findViewById(R.id.tvEnabledNet);
tvStatusNet = (TextView) findViewById(R.id.tvStatusNet);
tvLocationNet = (TextView) findViewById(R.id.tvLocationNet);
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); }
@Override
protected void onResume() {
super.onResume();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
1000 * 10, 10, locationListener);
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000 * 10, 10,
locationListener);
checkEnabled(); }
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(locationListener); }
private LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
showLocation(location); }
@Override
public void onProviderDisabled(String provider) {
checkEnabled(); }
@Override
public void onProviderEnabled(String provider) {
checkEnabled();
showLocation(locationManager.getLastKnownLocation(provider)); }
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
tvStatusGPS.setText("Status: " + String.valueOf(status));
} else if (provider.equals(LocationManager.NETWORK_PROVIDER)) {
tvStatusNet.setText("Status: " + String.valueOf(status)); } }
};
private void showLocation(Location location) {
if (location == null)
return;
if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
tvLocationGPS.setText(formatLocation(location));
} else if (location.getProvider().equals(
LocationManager.NETWORK_PROVIDER)) {
tvLocationNet.setText(formatLocation(location)); }
}
private String formatLocation(Location location) {
if (location == null)
return "";
return String.format(
"Coordinates: lat = %1$.4f, lon = %2$.4f, time = %3$tF %3$tT",
location.getLatitude(), location.getLongitude(), new Date(
location.getTime()));
}
private void checkEnabled() {
tvEnabledGPS.setText("Enabled: "
+ locationManager
.isProviderEnabled(LocationManager.GPS_PROVIDER));
tvEnabledNet.setText("Enabled: "
+ locationManager
.isProviderEnabled(LocationManager.NETWORK_PROVIDER)); }
public void onClickLocationSettings(View view) {
startActivity(new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); };
}
В onCreate определяемTextView-компонентыиполучаем LocationManager, черезкоторыйибудемработать.
В onResume исползуем метод requestLocationUpdates. Навходпередаем:
- типпровайдера: GPS_PROVIDER или NETWORK_PROVIDER
- минимальноевремя (вмиллисекундах) междуполучениемданных. В коде указан 10 секунд. Для получениякоординатыбеззадержек можно вводить 0 секунд. Ноэтотолькоминимальноевремя. Реальноеожиданиеполучения данныхможетбытьпобольше.
- минимальноерасстояние (вметрах). Т.е. есливашеместоположениеизменилосьнауказанноекол-вометров, товампридутновыекоординаты.
- слушатель, объектlocationListener.
Такжездесьможнообновлятьинформациюовключенностипровайдеров.
В onPause отключаемметодremoveUpdates.
locationListener – реализуетинтерфейс LocationListener сметодами:
onLocationChanged – новыеданныеоместоположенииобъектаLocation. Здесьвызываетсяметод showLocation, который на экране отобразит данные о местоположении.
onProviderDisabled – отключение указанного провайдера. В этом методе вызываем свой метод checkEnabled, который на экране обновит текущие статусы провайдеров.
onProviderEnabled – включение указанного провайдера. Здесь вызывается checkEnabled. Далее метод getLastKnownLocation (он может вернуть null) запрашивает последнее доступное местоположение от включенного провайдера и отображает его. Оно может быть вполне актуальным, если вы до этого использовали какое-либо приложение с определением местоположения.
onStatusChanged – изменение статуса указанного провайдера. В поле status могут быть значения OUT_OF_SERVICE (данные будут недоступны долгое время), TEMPORARILY_UNAVAILABLE (данные временно недоступны), AVAILABLE (данные доступны). В этом методе мы просто выводим новый статус на экран.
Провайдеры включаются и отключаются в настройках системы. Тем самым, просто определяется доступен ли провайдер для получения от него координат. Программное включение/выключение провайдеров через стандартные методы недоступно.
Метод showLocation на вход берет Location, определяет его провайдера методом getProvider и отображает координаты в соответствующем текстовом поле.
Метод formatLocation на вход берет Location, читает из него данные и форматирует из них строку: getLatitude – широта, getLongitude – долгота, getTime – время определения.
Метод checkEnabled определяет включены или выключены провайдерыметодом isProviderEnabled и отображает эту информацию на экране.
Метод onClickLocationSettings срабатывает по нажатию кнопки Location settings и открывает настройки, чтобы пользователь мог включить или выключить провайдер. Дляэтогоиспользуется Intent с action = ACTION_LOCATION_SOURCE_SETTINGS.
В манифесте приложения нужно прописать разрешение на определение координат - ACCESS_FINE_LOCATION, которое позволит использовать и Network и GPS. Также существует разрешение ACCESS_COARSE_LOCATION, которое дает доступ только к Network-провайдеру.
Сохраняем и запускаем приложение. Если на мобильном аппарате выключен GPS, выключен WiFi и выключен мобильный интернет, то при запуске приложения и отображается такой вид:
Рис. 4.6. Запуск приложения
Здесь GPS выключен, Network включен. Но так как Интернет не включен Network не отображает информацию. Необходимо включить мобильный интернет или WiFi.
После включения WiFi вид изменяется и через 15-20 секунд Network отображает информацию о текущем местоположении. Это ширина, долгота и время.
Рис. 4.7. Отображение информацию о текущем местоположении
В коде минимальная скорость обновления информации – 10 сек. Но у провайдер Network можеть быть и больше.
Теперь включим GPS. Для этого мы специально поставили кнопку Location settings, которую надо будет нажать пользователю, чтобы перейти в настройки
Рис. 4.8. Окно настройки
В приложении видно, что GPS выключен, а Network включен. Разумеется, GPS можно включать и выключать через быстрые настройки системы (справа сверху). Но не все пользователи об этом знают. Включаем GPS и жмем Назад, чтобы вернуться в приложение.
Рис. 4.9. Включение GPS
GPS теперь показывает что он включен, ждем координаты.
Рис. 4.10. Включенный GPS
У GPS через какое-то время включился статус 2 (AVAILABLE).
Рис. 4.11. Изменение статуса
А у Network статус не обображается. Если с GPS сигналом все хорошо, то каждые 10 сек вы будете получать информацию о вашем местоположении. Если плохой сигнал то данные могут приходить реже и статус иногда меняется на 1 (TEMPORARILY_UNAVAILABLE).
Третийтиппровайдера - PASSIVE_PROVIDER. Сам по себе этот провайдер никакие данные не вернет. Но вы сможете получать данные о местоположении, когда кто-то еще в системе пытается определить местоположение через обычные провайдеры. Система будет дублировать результаты и вам.
Метод getAllProviders вернет вам список всех доступных провайдеров. Метод getProviders(boolean enabledOnly) вернет либо все, либо только включенные.
Объект Location кроме координат, времени и провайдера имеет еще несколько атрибутов, которые могут прийти и пустыми:
getAccuracy – точность показания в метрах
getAltitude – высота над уровнем моря в метрах
getSpeed – скорость движения в м/с
getBearing –угол, на который текущая траектория движения отклоняется от траектории на север.
Местоположение можно протестировать и через AVD эмулятор. Для этого надо в Eclipse открыть DDMS (Window>OpenPerspective>DDMS) и выбрать вкладку EmulatorControl. Внизу будет вкладка Manual, на которой есть поля для ввода координат и кнопка отправки.
1. Опишите базы данных SQLite в мобильных приложениях.
2. Как создается база данных ?
3. Как можно работать с базой данных ?
4. Как можно создавать запросы ?
5. Какие методы используются для работы с базой данных ?
6. Опишите понятие контент-провайдера.
7. Опишите создание контент провайдера.
8. Как используется созданные контент провайдеры ?
9. Какие методы используются для работы с контент провайдерами ?
10. Каким образом функционируют сервисы для управления сообщениями ?
11. Как осуществляется прием и передача сообщений ?
12. Как осуществляется обмен сообщениями через пользовательский интерфейс?
13. Опишите работу с сервером.
14. Как осуществляется подключение к серверу через HTTP GET?
15. Как осуществляется подключение к серверу через HTTP POST?
5. РАБОТА С МОБИЛЬНЫМИ ДАТЧИКАМИ
5.1. Сенсорные возможности Android
5.1.1. Отличительные особенности смартфонов
Отличительные особенности смартфонов является наличие операционной системы и возможность установки дополнительных приложений, существенно расширяющих функционал устройства. Смартфонвыполняет все привычные функции мобильного телефона и благодаря компактным размерам, всегда под рукой, благодаря наличию процессора и операционной системы, позволяет выполнять многие функции полноценного компьютера, а также обладают рядом интересных особенностей, не характерных для телефонов и компьютеров.
В смартфонах экран занимает практически всю площадь передней панели устройства, имеет высокое разрешение и является чувствительным к прикосновениям. Благодаря такой чувствительности, для взаимодействия с устройством и его приложениями можно использовать виртуальные элементы управления, например кнопки, отображаемые на экране. В смартфонах реализуется, touch-интерфейс - интерфейс, основанный на виртуальных элементах управления, выбор которых выполняется простым касанием, а также на использовании жестов (gestures). Если точек касания несколько (т. е. используется несколько пальцев), такой интерфейс, уже называется multi-touch.
Всостав платформыAndroid входит набор библиотек для обработки мультимедиа Media Framework, в котором реализованаподдержкабольшинстваобщих медиа-форматов. В приложения разрабатываемые для смартфонов под управлением Android можно интегрировать запись и воспроизведение аудио и видео, а также работу с изображениями.
Платформа Android позволяет разрабатывать такие приложения, которые предоставляют пользователям возможности делать фотоснимки или записывать видео, каким-то образом обрабатывать полученные материалы и использовать их далее.
Сенсорное управление подразумевает использование сенсорных жестов для взаимодействия с приложением. Ниже представлен набор жестов, поддерживаемый системой Android:
Использование: Запуск действия по умолчанию для выбранного элемента. Выполнение: нажать, отпустить. |
• Длинное касание (long touch). Использование: Выбор элемента. Не стоит использовать этот жест для вызова контекстного меню. Выполнение: нажать, ждать, отпустить. |
• Скольжение или перетаскивание (swipe or drag). Использование: Прокрутка содержимого или навигация между элементами интерфейса одного уровня иерархии. Выполнение: нажать, переместить, отпустить. |
• Скольжение после длинного касания (long press drag). Использование: Перегруппировка данных или перемещение в контейнер. Выполнение: длительное касание, переместить, отпустить. |
• Двойное касание (double touch). Использование: Увеличение масштаба, выделение текста. Выполнение: быстрая последовательность двух касаний. |
• Перетаскивание с двойным касанием (double touch drag). Использование: Изменение размеров: расширение или сужение по отношению к центру жеста. Выполнение: касание, следующее за двойным касанием со смещением вверх или вниз при этом: o смещение вверх уменьшает размер содержимого; o смещение вниз увеличивает размер содержимого. |
• Сведение пальцев (pinch close). Использование: уменьшение содержимого, сворачивание. Выполнение: касание экрана двумя пальцами, свести, отпустить. |
• Разведение пальцев (pinch open). Использование: увеличение содержимого, разворачивание. Выполнение: касание экрана двумя пальцами, развести, отпустить. |
О возможности управлять приложением с помощью сенсорных жестов можно говорить когда приложение способно распознать, что под набором касаний экрана скрывается некоторый жест и выполнить соответствующее действие. Процесс распознавания жеста обычно состоит из двух этапов: сбор данных и распознавание жеста. Рассмотрим эти этапы подробнее.
Основные действия, которые может произвести пользователь при взаимодействии с сенсорным экраном: коснуться экрана пальцем, переместить палец по экрану и отпустить. Эти действия распознаются системой Android, как сенсорные события (touch-события).
Каждый раз при появлении сенсорного события инициируется вызов метода onTouchEvent(). Обработка события станет возможной, если этот метод реализован в классе активности или некоторого компонента, иначе событие просто игнорируется.
Жест начинается, при первом касании экрана, продолжается пока система отслеживает положение пальцев пользователя и заканчивается получением финального события, состоящего в том, что ни один палец не касается экрана. Объект MotionEvent, передаваемый в метод onTouchEvent(), предоставляет детали каждого взаимодействия. Рассмотрим основные константы класса MotionEvent, определяющие сенсорные события:
· MotionEvent.ACTION_DOWN - касание экрана пальцем, является начальной точкой для любого сенсорного события или жеста;
· MotionEvent.ACTION_MOVE - перемещение пальца по экрану;
· MotionEvent.ACTION_UP - поднятие пальца от экрана.
Приложение может использовать предоставленные данные для распознавания жеста.
Можно реализовать свою собственную обработку событий для распознавания жеста, таким образом можно работать с произвольными жестами в приложении. Если же в приложении необходимо использовать стандартные жесты, можно воспользоваться классом GestureDetector. Этот класс позволяет распознать стандартные жесты без обработки отдельных сенсорных событий.
Android предоставляет класс GestureDetector для распознавания стандартных жестов. Некоторые жесты, которые он поддерживает включают: onDown(), onLongPress(), onFling() и т. д. Можно использовать класс GestureDetector в связке с методом onTouchEvent().
Начиная с версии 1.6, Android предоставляет API для работы с жестами, который располагается в пакете android.gesture и позволяет сохранять, загружать, создавать и распознавать жесты. Виртуальное устройство Android (AVD), начиная все с той же версии 1.6, содержит предустановленное приложение, которое называется Gesture Builder и позволяет создавать жесты. После создания жесты сохраняются на SD карте виртуального устройства и могут быть добавлены в приложение в виде бинарного ресурса.
Для распознавания жестов необходимо добавить компонент GestureOverlayView, в XML файл активности. Этот компонент может быть добавлен как обычный элемент графического интерфейса пользователя и встроен в компоновку, например RelativeLayout. C другой стороны он может быть использован, как прозрачный слой поверх других компонентов, в этом случае в XML файле активности он должен быть записан, как корневой элемент.
Кроме всего вышеперечисленного, для использования собственных жестов в приложении необходимо реализовать интерфейс OnGesturePerformedListener и его метод onGesturePerformed().
Для работы со стандартными жестами Android предоставляет классGestureDetector. Этот класс содержит два вложенных интерфейса-слушателя: OnGestureListener и OnDoubleTapListener, эти интерфейсы задают методы, отслеживающие стандартные жесты. А также GestureDetector содержит вложенный классSimpleOnGestureListener, который содержит пустые реализации, возвращающие значение false, где это необходимо, всех методов интерфейсов: OnGestureListener и OnDoubleTapListener.
Разработаем приложение, в котором продемонстрируем распознавание всех поддерживаемых жестов. Приложение содержит одну активность, одно информационное поле для вывода информации о распознанном жесте. Приложение работает следующим образом: пользователь выполняет один из поддерживаемых сенсорных жестов, в информационном поле отображается информация о распознанном жесте.
1. Создадим простое приложение и добавим на форму TextView для вывода информации.
2. Настроим логику приложения. В java класс, соответствующий активности внесем следующие дополнения.
o Класс активности должен реализовывать интерфейсы: GestureDetector.OnGestureListener и GestureDetector.OnDoubleTapListener, для этого в объявление класса добавим конструкцию:
implements GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener
o Нам понадобится экземпляр класса GestureDetectorCompat поэтому в качестве поля класса активности объявим следующую переменную:
GestureDetectorCompat mDetector;
В методе onCreate() класса активности, создадим экземпляр класса GestureDetectorCompat и присвоим его переменной mDetector:
mDetector = new GestureDetectorCompat(this,this);
одним из параметров конструктора является класс, который реализует интерфейсGestureDetector.OnGestureListener, в нашем случае использовано словоthis, т. е. параметром является сам класс активности. Этот интерфейс уведомляет пользователей когда появляется определенное сенсорное событие.
В методе OnCreate() класса активности, следующая строка:
mDetector.setOnDoubleTapListener(this);
устанавливает слушатель событий, связанных с двойным касанием, это должен быть класс, реализующий интерфейсGestureDetector.OnDoubleTapListener. В нашем случае использовано словоthis, т.е. слушателем будет опять сам класс активности.
Чтобы позволить вашему объекту GestureDetector получать события, необходимо переопределить метод onTouchEvent() для активности или элемента GUI. И передавать в экземпляр детектора все обнаруженные события.
publicbooleanonTouchEvent(MotionEventevent){
this.mDetector.onTouchEvent(event);
// Be sure to call the superclass implementation
return super.onTouchEvent(event);
}
После проведенной подготовки пришло время реализовать все методы, объявленные в интерфейсах, отвечающих за прослушивание сенсорных событий.
Методы интерфейса GestureDetector.OnGestureListener:
onDown() |
- отслеживает появление касания, т. е. палец прижат к экрану; |
onFling() |
- отслеживает появление жеста смахивания; |
onLongPress() |
- отслеживает удерживание пальца прижатым к экрану длительное время; |
onScroll() |
- отслеживает появление жеста прокрутки (пролистывания); |
onShowPress() |
- отслеживает, что произошло событие касания и больше никаких событий не происходит короткое время; |
onSingleTapUp() |
- отслеживает появление жеста одиночного нажатия (клик). |
Методы интерфейса GestureDetector.OnDoubleTapListener:
- отслеживает появление жеста двойного нажатия ("двойной клик"); |
|
onDoubleTapEvent() |
- отслеживает появление события во время выполнения жеста двойного нажатия, включая касание, перемещение, подъем пальца. |
onSingleTapConfirmed() |
- отслеживает появление жеста одиночного нажатия (клик). |
В листинге 1 представлен код приложения, в котором распознаются все поддерживаемые жесты, информация о появившемся и распознанном жесте выдается в информационное поле (TextView).
В качестве практики предлагается воспроизвести данное приложение и проверить, как система распознает тот или иной жест. Очень полезно для понимания, как выполняются основные жесты.
Разработаем приложение, в котором продемонстрируем распознавание только некоторой части поддерживаемых жестов по выбору программиста. Мы рассмотрим распознавание жеста смахивания (fling). Приложение содержит одну активность, одно информационное поле для вывода информации о распознанном жесте. Приложение работает следующим образом: пользователь выполняет один из поддерживаемых сенсорных жестов, в информационном поле отображается информация о распознанном жесте.
1. Создадим простое приложение и добавим на форму TextView для вывода информации.
2. Настроим логику приложения. В java класс, соответствующий активности внесем следующие дополнения.
Нам понадобится экземпляр класса GestureDetectorCompat поэтому в качестве поля класса активности объявим следующую переменную:
GestureDetectorCompat mDetector;
В методе onCreate() класса активности, создадим экземпляр класса GestureDetectorCompat и присвоим его переменной mDetector:
mDetector=new GestureDetectorCompat(this, new MyGestListener());
в конструкторе аргументом, отвечающим за отслеживание сенсорных событий, служит экземпляр класса MyGestListener() - внутренний класс, который является наследником класса GestureDetector.SimpleOnGestureListener.
Имеет смысл немного рассмотреть класс GestureDetector.SimpleOnGestureListener. Этот класс реализует интерфейсы GestureDetector.OnGestureListener и GestureDetector.OnDoubleTapListener, все методы заявленные в интерфейсах в этом классе имеют пустую реализацию и те, которые должны возвращать значение, возвращают false. Поэтому для распознавания какого-то события или некоторого подмножества событий достаточно написать реализацию соответствующих методов, в классе наследнике.
В листинге 2 представлен код приложения, в котором распознается только жест смахивания, т. е. реализован метод onFling(), информация о появившемся и распознанном жесте выдается в информационное поле (TextView). В качестве слушателя используется экземпляр класса MyGestListener(), являющийся наследником класса GestureDetector.SimpleOnGestureListener.
package com.example.lagestall;
import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.GestureDetectorCompat;
import android.view.*;
import android.widget.*;
public class MainActivity extends Activity
implements GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener{
TextView tvOutput;
GestureDetectorCompat mDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvOutput = (TextView)findViewById(R.id.textView1);
mDetector = new GestureDetectorCompat(this,this);
mDetector.setOnDoubleTapListener(this);
}
public boolean onTouchEvent(MotionEvent event){
this.mDetector.onTouchEvent(event);
// Be sure to call the superclass implementation
return super.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent event) {
tvOutput.setText("onDown: " + event.toString());
return false;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
tvOutput.setText("onFling: " + event1.toString()+event2.toString());
return true;
}
@Override
public void onLongPress(MotionEvent event) {
tvOutput.setText("onLongPress: " + event.toString());
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
tvOutput.setText("onScroll: " + e1.toString()+e2.toString());
return true;
}
@Override
public void onShowPress(MotionEvent event) {
tvOutput.setText("onShowPress: " + event.toString());
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
tvOutput.setText("onSingleTapUp: " + event.toString());
return true;
}
@Override
public boolean onDoubleTap(MotionEvent event) {
tvOutput.setText("onDoubleTap: " + event.toString());
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
tvOutput.setText("onDoubleTapEvent: " + event.toString());
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
tvOutput.setText("onSingleTapConfirmed: " + event.toString());
return true; }
}
Листинг 1. Распознавание поддерживаемых жестов с помощью реализации интерфейсов
package com.example.labgestsubset;
import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.*;
import android.view.*;
import android.widget.*;
public class SubsetGestActivity extends Activity {
private GestureDetectorCompat mDetector;
private TextView tvOut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_subset_gest);
mDetector = new GestureDetectorCompat(this, new MyGestListener());
tvOut = (TextView)findViewById(R.id.textView1);
}
@Override
public boolean onTouchEvent(MotionEvent event){
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
class MyGestListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
tvOut.setText("onFling: " + event1.toString()+event2.toString());
return true; }}
}
Листинг 2. Распознаваниежестовсиспользованиемкласса GestureDetector.SimpleOnGestureListener.
Для начала создадим новое приложение.Запустимэмулятор и используем приложение Gesture Builder для создания жестов "1", "2" и "S". Жест всегда связан с именем, но имя не обязательно должно быть уникальным. Дляповышения точности в распознавании жеста рекомендуется сохранять несколько жестов с одним и тем же именем.
На рис. 5.1 можно увидеть приложение Gesture Builder в работе, чтобы добавить жест, необходимо нажать на кнопку Add gesture, в свободном пространстве изобразить жест, в поле ввода задать имя жеста. Результат последовательного добавления жестов можно увидеть на рис. 5.2.
Жесты сохраняются на SD карте эмулятора, чтобы использовать их в приложении необходимо импортировать файл жестов в проект.
Рис. 5.1. Создание жестов с помощью приложения Gesture Builder
Рис. 5.2. Набор жестов, созданных в приложении Gesture Builder
Gesture Builder может сообщить, что ему некуда сохранять жесты, в этом случае необходимо запустить эмулятор с образом SD карты.
Сначала образ нужно создать с помощью утилиты mksdcard (расположена в папке <AndrSDK>/sdk/tools, где <AndrSDK> - путь, куда установлен Android SDK). В командной строке напишем:
mksdcard -lmySdCard 64Mgesture.img
Следующим шагом будет создать и запустить эмулятор с образом gesture.img, для этого в папку <AndrSDK>/sdk/tools и выполним команду:
emulator -avd nameEmulator -sdcard gestures.img
nameEmulator - имя, которое присвоено эмулятору при создании.
Когдасоздаем или редактируем жесты с помощью Gesture Builder, создается файл gestures на SD карте эмулятора. Необходимо импортировать этот файл в директорию res/raw/, созданного проекта, в котором планируем использовать жесты.
Рис. 5.3. Расположение файла gestures
Самый простой способ импортировать жесты в проект заключается использовании вкладки File Explorer в компоновке (perspective) DDMS. (Window->Open Perspective->Other...->DDMS. Есливкладки File Explorer нет, можнодобавить: Window-> Show View-> File Explorer). На вкладке File Explorer найти директорию sdcard/ (директория storage/sdcard/, имеет смысл при создании жестов обратить внимание в какую директорию Gesture Builder их сохраняет). На рис. 5.3 показана вкладка File Explorer в компоновке DDMS.
Чтобы скопировать файл жестов с эмулятора в проект, необходимо выбрать его и нажать кнопку "Pull a file from the device", выделенную на рис. 5.3 красным подобием окружности. Откроется диалог с предложением выбрать папку, в которую необходимо скопировать жесты, найти папку проекта, в ней папку res/raw/ (если папки raw/ нет, ее необходимо создать) и нажать кнопку Сохранить. Теперь жесты есть в проекте и их можно использовать.
Для распознавания жестов необходимо добавить элемент GestureOverlayView в XML файл активности. Этотфайл может выглядеть, например, как показано на рис. 5.4:
![XML файл активности приложения, элемент GestureOverlayView обычный компонент интерфейса пользователя](Razrabotka_mobilnix_prilojeniy.files/image047.jpg)
Рис. 5.4. XML файл активности приложения, элемент GestureOverlayView обычный компонент интерфейса пользователя
Можно добавить элемент GestureOverlayView поверх всех компонентов, как прозрачный слой, в этом случае XML файл активности может выглядеть так, как показано на рис. 5.5.
![XML файл активности приложения, элемент GestureOverlayView поверх всех компонентов интерфейса пользователя](Razrabotka_mobilnix_prilojeniy.files/image048.jpg)
Рис. 5.5. XML файл активности приложения, элемент GestureOverlayView поверх всех компонентов интерфейса пользователя
Далее нужно обработать ввод жеста пользователя, сравнить с загруженными жестами илиопределить жест и сообщить пользователю, что такого жеста нет. Всяработа будет выполняться в java файле, описывающем главную активность приложения. Внесем в этот класс следующие дополнения:
Класс активности должен реализовывать интерфейс OnGesturePerformedListener, для этого в объявление класса добавим конструкцию:
implements OnGesturePerformedListener;
Потом понадобятся экземпляры классов GestureLibrary и GestureOverlayView, поэтому в качестве полей класса активности объявим следующие переменные:
GestureLibrary gLib;
GestureOverlayView gestures;
В методе onCreate() выполним следующие действия:
gLib = GestureLibraries.fromRawResource(this, R.raw.gestures);
if (!gLib.load()) {
finish();
}
В первой строке выполнена инициализация переменной gLib жестами, загруженными из файла gestures папки res/raw/.
Оператор if выполняет проверку загружены ли жесты, если нет, выполняется выход из приложения.
Добавим в метод onCreate():
gestures = (GestureOverlayView) findViewById(R.id.gestureOverlayView1);
gestures.addOnGesturePerformedListener(this);
Для инициализации переменной gesture и подключения к ней слушателя событий появления жеста.
Реализация метода OnGesturePerformed(), который и будет вызываться при появлении события, соответствующего какому-либо жесту:
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
//Создаёт ArrayList c загруженными из gestures жестами
ArrayList<Prediction> predictions = gLib.recognize(gesture);
if (predictions.size() > 0) {
//еслизагруженхотябыодинжестиз gestures
Prediction prediction = predictions.get(0);
if (prediction.score > 1.0) {
if (prediction.imya.equals("one"))
tvOut.setText("1");
else if (prediction.imya.equals("stop"))
tvOut.setText("stop");
else if (prediction.imya.equals("two"))
tvOut.setText("2");
}else{
tvOut.setText("Жестнеизвестен"); } } }
В приложении распознаются жесты и в информационное поле выводится информация о том, что за жест был использован.
5.2. Виды мобильных датчиков и работа с ними
Многие устройства работающие под управлением Android, укомплектованы встроенными сенсорами, которые предоставляют исходные данные высокой точности. Сенсоры полезны в том случае, если необходимо регистрировать положение и перемещения, повороты устройства в трехмерном пространстве, а также изменения параметров окружающей среды.
Современные смартфоны объединяют в себе много приложений. Быть такими многофункциональными устройствами и получать информацию из окружающего мира им позволяют различные сенсоры.
Акселерометр
Акселерометр измеряет ускорение и позволяет смартфону определять характеристики движения и положения в пространстве. Он работает, когда вертикальная ориентация меняется на горизонтальную при повороте устройства. Он отвечает за подсчёт шагов и измерение скорости движения во всевозможных приложениях-картах. Акселерометр даёт информацию о том, в какую сторону повёрнут смартфон, что становится важной функцией в различных приложениях с дополненной реальностью.
Этот сенсор состоит из маленьких датчиков: микроскопических кристаллических структур, под влиянием сил ускорения переходящих в напряжённое состояние. Напряжение передаётся акселерометру, который интерпретирует его в данные о скорости и направлении движения.
Гироскоп
Этот датчик помогает акселерометру ориентироваться в пространстве. Он позволяет делать на смартфон панорамные фото. В играх с гонками, где управление происходит с помощью перемещения устройства используется гироскоп. Он чувствителен к поворотам устройства относительно своей оси.
В смартфонах используются микроэлектромеханические системы.
Магнитометр
Дляориентации в пространстве используется магнитометр. Он измеряет магнитные поля и может определить, где находится север. Функция компаса в различных приложениях с картами и отдельные программы-компасы работают с помощью магнитометра.
Подобные датчики есть в металлодетекторах, можно найти специальные приложения, превращающие смартфон в такой прибор.
Магнитометр действует с акселерометром и GPS для определения географического положения и навигации.
Барометр
Многие смартфоны, в том числе iPhone, имеют этот сенсор, измеряющий атмосферное давление. Он нужен для регистрации изменения погоды и определения высоты над уровнем моря.
Бесконтактный выключатель
Этот сенсор обычно находится около динамика в верхней части смартфона и состоит из инфракрасного диода и датчика света. Он использует невидимый человеку луч, чтобы определить, находится ли устройство возле уха. Так смартфон «понимает», что во время разговора по телефону нужно отключить дисплей.
Датчик освещённости
Этотсенсор измеряет уровень освещённости окружающей среды, что позволяет автоматически настраивать комфортную яркость дисплея.
Датчики с каждым новым поколением смартфонов становятся всё более эффективными, маленькими и менее энергозатратными. Например, функция GPS в устройстве, которому уже несколько лет, будет работать так же хорошо, как в новом.
Эти датчики могут предоставлять необработанные данные с высокой точностью и точностью и полезны, если нужно отслеживать трехмерное движение или позиционирование устройства, или нужно отслеживать изменения в окружающей среде рядом с устройством. Например, игра может отслеживать показания датчика силы тяжести устройства, чтобы определять сложные жесты и движения пользователя, такие как наклон, встряхивание, вращение или качание. Такимобразом погодное приложение может использовать датчик температуры устройства и датчик влажности для расчета и сообщения о точке росы, или приложение для путешествий может использовать датчик геомагнитного поля и акселерометр для определения направления по компасу.
Сенсоры могут быть реализованы аппаратно или программно. Аппаратно-реализованные датчики являются физическими элементами встроенными в мобильное устройство, они получают данные путем прямых измерений некоторых свойств, таких как ускорение, сила геомагнитного поля или изменение углов. Программно-реализованные датчики получают свои данные с одного или нескольких физических датчиков и вычисляют значение, которое от них ожидается.
Какие типы датчиков поддерживаются Android можно узнать по ссылке: http://developer.android.com/guide/topics/sensors/sensors_overview.html.
Android предоставляет набор классов и интерфейсов для работы с сенсорами. Эти классы и интерфейсы являются частью пакета android.hardware и позволяют выполнять следующие задачи:
· определять какие сенсоры доступны на устройстве;
· определять индивидуальные возможности сенсоров, такие как максимальное значение, производитель, требования к потребляемой энергии и разрешения;
· собирать данные с сенсоров и определять минимальную частоту, с которой выполняется сбор данных;
· подключать и отключать слушателей событий от датчиков, события состоят в изменении значений датчиков.
Для работы с датчиками Android предоставляет следующие классы и интерфейсы:
SensorManager - |
Этот класс может использоваться для создания экземпляра сервиса, связанного с сенсором. Также он предоставляет различные методы для доступа и составления списка сенсоров, подключения и отключения слушателей событий от сенсоров, сбора информации. Этот класс содержит константы, которые используются для задания точности сенсора, частоты получения данных и настройки датчиков. |
Sensor - |
Этот класс используется для создания экземпляра датчика, предоставляет методы, позволяющие определить свойства сенсора. |
SensorEvent - |
Система использует этот класс для создания объекта, соответствующего событию датчика и предоставляющего следующую информацию: данные сенсора; тип сенсора, который породил событие, точность данных и время появления события. |
SensorEventListener - |
Данный интерфейс может использоваться для реализации двух методов, получающих уведомления (события датчиков), когда меняется значение сенсора или когда меняется точность сенсора. |
Датчики, следящие за физическими свойствами и состоянием окружающей среды, предоставляют способы для улучшения мобильных приложений. В современных мобильных устройствах есть электронные компасы, датчики равновесия, яркости и близости открывает целый ряд новых возможностей для взаимодействия с устройством, таких как дополненная реальность и ввод данных, основанный на перемещениях в пространстве.
Датчики в Android делятся на несколько категорий: движения, положения и окружающей среды.
· Датчики движения - эти датчики измеряют силы ускорения и вращательные силы по трем осям. В эту категорию входят акселерометры, датчики силы тяжести, гироскопы и датчики вектора вращения.
· Датчики окружающей среды - эти датчики измеряют различные параметры окружающей среды, такие как температуру и давление окружающего воздуха, освещенность и влажность. В эту категорию входят барометры, фотометры и термометры.
· Датчики положения - эти датчики измеряют физическое положение устройства. В эту категорию входят датчики ориентации и магнитометры.
Ниже перечислены некоторые виды популярных датчиков:
· акселерометр (TYPE_ACCELEROMETER)
· гироскоп (TYPE_GYROSCOPE)
· датчик освещения (TYPE_LIGHT)
· датчик расстояния (TYPE_PROXIMITY)
· датчик магнитных полей (TYPE_MAGNETIC_FIELD)
· барометр (TYPE_PRESSURE)
· датчик температуры окружающей среды (TYPE_AMBIENT_TEMPERATURE)
· измеритель относительной влажности (TYPE_RELATIVE_HUMIDITY)
В каждом мобильном устройстве может быть свой набор датчиков. В большинстве есть — акселерометр и гироскоп.
Существуютклассы для работы с датчиками, в частности, для датчиков ориентации и температуры.
Необходимо помнить работая с датчиками:
· показания бывают очень неровными. Нужноиспользовать какое-то среднее значение показаний, чтобы приложение оставалось отзывчивым.
· данные приходят неравномерно.
· попробуйте предугадать будущие действия пользователя. Например, если идут данные о начале вращения устройства, можно предугадать следующее движение и подготовиться к нему
На эмуляторе практически невозможно тестировать работу с датчиками, поэтому используйте реальные устройства. В последних версиях эмуляторов список возможностей датчиков расширился.
За работу с сенсорами отвечает класс SensorManager, содержащий несколько констант, которые характеризуют различные аспекты системы датчиков Android, в том числе:
тип датчика - ориентация, акселерометр, свет, магнитное поле, близость, температура и т.д.
частота измерений - максимальная, для игр, обычная, для пользовательского интерфейса. Когда приложение запрашивает конкретное значение частоты отсчётов, с точки зрения сенсорной подсистемы это лишь рекомендация. Никакой гарантии, что измерения будут производиться с указанной частотой, нет.
точность - высокая, низкая, средняя, ненадёжные данные.
Типы датчиков
· TYPE_ACCELEROMETER - Измеряет ускорение в пространстве по осям X, Y, Z
· TYPE_AMBIENT_TEMPERATURE - Новый датчик для измерения температуры (API 14) в градусах Цельсия, который заменил устаревший TYPE_TEMPERATURE
· TYPE_GRAVITY - Трёхосевой датчик силы тяжести. Этовиртуальный датчик и представляет собой низкочастотный фильтр для показаний, возвращаемых акселерометром
· TYPE_GYROSCOPE - Трёхосевой гироскоп, возвращающий текущее положение устройства в пространстве в градусах по трём осям. По другим данным, возвращает скорость вращения устройства по трём осям в радианах в секунду.
· TYPE_LIGHT - Измеряет степень освещённости. Датчик окружающей освещённости, который описывает внешнюю освещённость в люксах. Этот тип датчиков обычно используется для динамического изменения яркости экрана.
· TYPE_LINEAR_ACCELERATION - Трёхосевой датчик линейного ускорения, возвращающий показатели ускорения без учёта силы тяжести. Это виртуальный датчик, использующий показания акселерометра.
· TYPE_MAGNETIC_FIELD - Датчик магнитного поля, определяющий текущие показатели магнитного поля в микротеслах по трём осям.
· TYPE_ORIENTATION - Датчик ориентации. Измеряет повороты, наклоны и вращение устройства.
· TYPE_PRESSURE - Датчик атмосферного давления (барометр), возвращающий текущее давление в миллибарах. Можно определять высоту над уровнем моря, путём сравнения атмосферного давления в двух точках. Также барометры могут применяться для прогнозирования погоды.
· TYPE_PROXIMITY - Датчик приближённости, который сигнализирует о расстоянии между устройством и целевым объектом в сантиметрах. Каким образом выбирается объект и какие расстояния поддерживаются, зависит от аппаратной реализации данного датчика, возможно возвращение двух значений - Близко и Далеко. Типичное его применение - обнаружение расстояния между устройством и ухом пользователя для автоматического регулирования яркости экрана или выполнения голосовой команды.
· TYPE_RELATIVE_HUMIDITY - Датчик относительной влажности в виде процентного значения (API 14)
· TYPE_ROTATION_VECTOR - Возвращает положение устройства в пространстве в виде угла относительно оси. Виртуальный датчик, берущий показания от акселерометра и гироскопа. Также может использовать показания датчика магнитного поля
· TYPE_GEOMAGNETIC_ROTATION_VECTOR - альтернатива TYPE_ROTATION_VECTOR. Меньшая точность, но меньший расход батареи. Появился в Android 4.4 (API 19)
· TYPE_POSE_6DOF - ещё одна альтернатива TYPE_ROTATION_VECTOR. Появился в Android 7.0 (API 24)
· TYPE_SIGNIFICANT_MOTION - Появилсяв Android 4.3 (API 18)
· TYPE_MOTION_DETECT - детектордвижения. Появился в Android 7.0 (API 24)
· TYPE_STATIONARY_DETECT - Появилсяв Android 7.0 (API 24)
· TYPE_STEP_COUNTER - датчик для подсчёта количества шагов
· TYPE_STEP_DETECTOR - определение начала шагов
· TYPE_HEART_BEAT - пульс. Появилсяв Android 7.0 (API 24)
· TYPE_HEART_RATE - сердечная активность. Появился в Android 4.4 (API 20)
· TYPE_LOW_LATENCY_OFFBODY_DETECT - Появилсяв Android 8.0 (API 26)
Кроме аппаратных датчиков, в устройствах используются виртуальные датчики, которые предоставляют упрощённые, уточнённые или комбинированные показания, используя комбинацию из нескольких аппаратных датчиков. В некоторых случаях этот способ удобнее.
Чтобы получить доступ к сенсорам, нужно вызвать метод getSystemService().
// Kotlin
private lateinit var sensorManager: SensorManager
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
// Java
private SensorManager sensorManager;
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Устройство может включать в себя несколько реализаций одного и того же типа датчиков. Чтобы найти реализацию, используемую по умолчанию, вызовите метод getDefaultSensor() из объекта SensorManager, передавая ему в качестве параметра тип датчика в виде одной из констант, описанных выше.
Следующий фрагмент кода вернёт объект, описывающий гироскоп по умолчанию. Если для данного типа не существует датчика по умолчанию, будет возвращено значение null.
// Kotlin
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
//
лучшеиспользоватьвариантспроверкой
if (sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
// Успешно
.
есть гироскоп
} else {
// Неудачно. Гироскоп не обнаружен
}
// Java
Sensor defaultGyroscope = sensorManager.getDefaultSensor
(Sensor.TYPE_GYROSCOPE);
Таблица значений, возвращаемых датчиками
Тип датчика |
Количество значений |
Содержание значений |
Примечание |
TYPE_ACCELEROMETER |
3 |
value[0]: ось X (поперечная) |
Ускорение (м/с2)
по трём осям. |
TYPE_GRAVITY |
3 |
value[0]: ось X (поперечная) |
Сила тяжести (м/с2)
по трём осям. |
TYPE_RELATIVE_HUMIDITY |
1 |
value[0]:относительная влажность |
Относительная влажность в процентах (%) |
TYPE_LINEAR_ACCELERATION |
3 |
value[0]: ось X (поперечная) |
Линейное ускорение (м/с2) по трём осям без учёта силы тяжести |
TYPE_GYROSCOPE |
3 |
value[0]: ось
X |
Скорость вращения (рад/с) по трём осям |
TYPE_ROTATION_VECTOR |
4 |
values[0]: x*sin(q/2) |
Положение устройства
в пространстве. |
TYPE_MAGNETIC_FIELD |
3 |
value[0]: ось X (поперечная) |
Внешнее магнитное поле (мкТл) |
TYPE_LIGHT |
1 |
value[0]: освещённость |
Внешняя освещённость
(лк). |
TYPE_PRESSURE |
1 |
value[0]: атм.давление |
Атмосферное давление (мбар) |
TYPE_PROXIMITY |
1 |
value[0]: расстояние |
Расстояние до цели |
TYPE_AMBIENT_TEMPERATURE |
1 |
value[0]: температура |
Температура воздуха в градусах по Цельсию |
TYPE_POSE_6DOF |
15 |
см. документацию |
|
TYPE_STATIONARY_DETECT |
1 |
value[0] |
5 секунд неподвижен |
TYPE_MOTION_DETECT |
1 |
value[0] |
В движении за последние 5 секунд |
TYPE_HEART_BEAT |
1 |
value[0] |
У класса SensorManager есть метод getSensorList(), позволяющий получить список доступных датчиков на устройстве через константу Sensor.TYPE_ALL и метод getImya():
// Kotlin
package ru.sensors
import android.hardware.Sensor
import android.hardware.SensorManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var sensorManager: SensorManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val deviceSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_ALL)
println(deviceSensors.joinToString("\n"))
// Выводятся данные в таком формате
// {Sensor imya="MPL Accelerometer", vendor="InvenSense", version=1, type=1, maxRange=39.226593, resolution=0.0011901855, power=0.5, minDelay=5000}
}
}
// Java
package ru.sensors;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class SensorsActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
List<Sensor> deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
List<String> listSensorType = new ArrayList<>();
for (int i = 0; i < deviceSensors.size(); i++) {
listSensorType.add(deviceSensors.get(i).getImya());
}
setListAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, listSensorType));
getListView().setTextFilterEnabled(true); }
}
Так как у каждого устройства свой набор датчиков, то результаты будут у всех разными.
Также можно получить список доступных датчиков конкретного типа. В следующем фрагменте кода будут возвращены объекты Sensor, представляющие собой все доступные датчики давления:
// Kotlin
val pressureSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_PRESSURE)
// Java
List<Sensor> pressureSensors = sensorManager.getSensorList(Sensor.TYPE_PRESSURE);
Можно составить сложное условие, которое будет проверять производителя датчика и его версию. Если необходимого датчика не окажется, то выберем альтернативный вариант.
// Kotlin
if (sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null) {
val gravitySensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_GRAVITY)
// Используем конкретный датчик от производителя и нужной версии
sensor = gravitySensors.firstOrNull { it.vendor.contains("Qualcomm") && it.version == 1 }
println(sensor?.vendor)}
if (sensor == null) {
//
Используемакселерометр
sensor = if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
} else {
// Акселеромент не обнаружен!
n
ull }
}
Также понадобится интерфейс android.hardware.SensorListener. Интерфейс реализован с помощью класса, который используется для ввода значений датчиков по мере их изменения в режиме реального времени. Приложение реализует этот интерфейс для мониторинга одного или нескольких имеющихся аппаратных датчиков.
Интерфейс включает в себя два необходимых метода:
· метод onSensorChanged(int sensor, float values[]) вызывается когда изменяется значение датчика. Этот метод вызывается только для датчиков, контролируемых данным приложением. В число аргументов метода входит целое, которое указывает, что значение датчика изменилось, и массив значений с плавающей запятой, отражающих собственно значение датчика. Некоторые датчики выдают только одно значение данных, тогда как другие предоставляют три значения с плавающей запятой. Датчики ориентации и акселерометр дают по три значения данных каждый.
· метод onAccuracyChanged(int sensor,int accuracy) вызывается при изменении точности показаний датчика. Аргументами служат два целых числа: одно указывает датчик, а другое соответствует новому значению точности этого датчика.
Служба датчиков вызывает onSensorChanged() каждый раз при изменении значений. Все датчики возвращают массив значений с плавающей точкой. Размер массива зависит от особенностей датчика. Датчик TYPE_TEMPERATURE возвращает одно значение - температуру в градусах Цельсия, другие могут возвращать несколько значений. Вы можете использовать только нужные значения. Например, для получения сведений только о магнитном азимуте достаточно использовать первое числов, возвращаемое датчиком TYPE_ORIENTATION.
Параметр accuracy, используемый в методах для представления степени точности датчика, использует одну из констант
· SensorManager.SENSOR_STATUS_ACCURACY_LOW. Говорит о том, что данные, предоставляемые датчиком, имеют низкую точность и нуждаются в калибровке.
· SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM. Говорит о средней степени точности датчика и том, что калибровка может улучшить результат.
· SensorManager.SENSOR_STATUS_ACCURACY_HIGH. Показатели датчика точны настолько, насколько это возможно.
· SensorManager.SENSOR_STATUS_UNRELIABLE. Данные, предоставляемые датчиком, недостоверны. Это значит, что датчик необходимо откалибровать, иначе невозможно считывать результаты.
Чтобы получать события, генерируемые датчиками, зарегистрируйте свою реализацию интерфейса SensorEventListener с помощью SensorManager. Укажите объект Sensor, за которым вы хотите наблюдать, и частоту, с которой вам необходимо получать обновления.
После получения объекта вы вызываете метод registerListener() в методе onResume(), чтобы начать получать обновлённые данные, и вызываете unregisteredListener() в методе onPause(), чтобы остановить получение данных. В этом случае датчики будут использоваться только тогда, когда активность видна на экране.
В следующем примере показан процесс регистрации SensorEventListener для датчика приближенности по умолчанию с указанием стандартной частоты обновления:
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(mySensorEventListener,
sensor, SensorManager.SENSOR_DELAY_NORMAL);
Класс SensorManager содержит следующие константы для выбора подходящей частоты обновлений (в порядке убывания):
· SensorManager.SENSOR_DELAY_FASTEST - самая высокая возможная частота обновления показаний датчиков;
· SensorManager.SENSOR_DELAY_GAME - частота, используемая для управления играми;
· SensorManager.SENSOR_DELAY_NORMAL - частота обновлений по умолчанию;
· SensorManager.SENSOR_DELAY_UI - частота для обновления пользовательского интерфейса.
Выбранная вами частота необязательно будет соблюдаться. SensorManager может возвращать результаты быстрее или медленней, чем вы указали (хотя, как правило, это происходит быстрее). Чтобы минимизировать расход ресурсов при использовании датчиков в приложении, необходимо пытаться подбирать наиболее низкую частоту.
Платформа датчиков Android предоставляет несколько методов, которые упрощают определение во время выполнения, какие датчики находятся на устройстве. API также предоставляет методы, которые позволяют вам определять возможности каждого датчика, такие как его максимальный диапазон, его разрешение и его требования к мощности.
Чтобы идентифицировать датчики, которые есть на устройстве, вам сначала необходимо получить ссылку на службу датчиков. Для этого вы создаете экземпляр SensorManagerкласса, вызывая getSystemService()метод и передавая SENSOR_SERVICEаргумент. Например:
private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Затем вы можете получить список всех датчиков на устройстве, вызвав getSensorList()метод и используя TYPE_ALLконстанту. Например:
List<Sensor> deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
Если вы хотите перечислить все датчики данного типа, можно использовать другую константу вместо TYPE_ALL таких, как TYPE_GYROSCOPE, TYPE_LINEAR_ACCELERATIONили TYPE_GRAVITY.
Такжеможно определить, существует ли на устройстве датчик определенного типа, используя getDefaultSensor()метод и передав константу типа для определенного датчика. Если устройство имеет более одного датчика данного типа, один из датчиков должен быть назначен датчиком по умолчанию. Если датчик по умолчанию не существует для данного типа датчика, вызов метода возвращает значение null, что означает, что устройство не имеет датчика этого типа. Например, следующий код проверяет, есть ли на устройстве магнитометр:
private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null){
// Success! There's a magnetometer.
} else {
// Failure! No magnetometer.
}
Android не требует от производителей устройств встраивать датчики каких-либо определенных типов в свои устройства под управлением Android, поэтому устройства могут иметь широкий диапазон конфигураций датчиков.
Помимо перечисления датчиков, установленных на устройстве, вы можете использовать общедоступные методы Sensorкласса для определения возможностей и атрибутов отдельных датчиков. Это полезно, если вы хотите, чтобы ваше приложение работало по-разному в зависимости от того, какие датчики или возможности датчиков доступны на устройстве. Например, вы можете использовать getResolution()иgetMaximumRange() методы, чтобы получить разрешение датчика и максимальный диапазон измерения. Вы также можете использовать этот getPower()метод для получения требований к питанию датчика.
Два общедоступных метода особенно полезны, если оптимизировать приложение для датчиков разных производителей или разных версий датчика. Например, если вашему приложению необходимо отслеживать жесты пользователя, такие как наклон и встряхивание, вы можете создать один набор правил фильтрации данных и оптимизаций для новых устройств, которые имеют датчик силы тяжести конкретного производителя, а другой набор правил фильтрации данных и оптимизаций для устройств. которые не имеют датчика силы тяжести и имеют только акселерометр. В следующем примере кода показано, как вы можете использовать getVendor()и getVersion()методы, чтобы сделать это. В этом примере мы ищем датчик силы тяжести, в котором Google LLC указан в качестве поставщика и имеет номер версии 3. Если этот датчик отсутствует на устройстве, мы пытаемся использовать акселерометр.
private SensorManager sensorManager;
private Sensor mSensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = null;
if (sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null){
List<Sensor> gravSensors = sensorManager.getSensorList(Sensor.TYPE_GRAVITY);
for(int i=0; i<gravSensors.size(); i++) {
if ((gravSensors.get(i).getVendor().contains("Google LLC")) &&
(gravSensors.get(i).getVersion() == 3)){
// Use the version 3 gravity sensor.
mSensor = gravSensors.get(i); } }}
if (mSensor == null){
// Use the accelerometer.
if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
} else{
// Sorry, there are no accelerometers on your device.
// You can't play this game.
}
}
Еще один метод - getMinDelay()метод, который возвращает минимальный интервал времени (в микросекундах), который датчик может использовать для считывания данных. Любой датчик, который возвращает ненулевое значение для getMinDelay() метода, является датчиком потоковой передачи. Датчики потоковой передачи считывают данные через регулярные промежутки времени и были представлены в Android 2.3 (уровень API 9). Если датчик возвращает ноль при вызове getMinDelay()метода, это означает, что датчик не является датчиком потоковой передачи, поскольку он сообщает данные только тогда, когда есть изменение в параметрах, которые он обнаруживает.
МетодgetMinDelay()позволяет определить максимальную скорость, с которой датчик может собирать данные. Если определенные функции в вашем приложении требуют высокой скорости сбора данных или датчика потоковой передачи, вы можете использовать этот метод, чтобы определить, соответствует ли датчик этим требованиям, а затем включить или отключить соответствующие функции в вашем приложении соответственно.
Максимальная скорость сбора данных датчика не обязательно является скоростью, с которой платформа датчика передает данные датчика вашему приложению. Платформа датчиков сообщает данные через события датчиков, и несколько факторов влияют на скорость, с которой ваше приложение получает события датчиков.
Мониторинг событий датчика
Чтобы отслеживать необработанные данные датчика, вам необходимо реализовать два метода обратного вызова, которые доступны через SensorEventListenerинтерфейс: onAccuracyChanged()иonSensorChanged(). Система Android вызывает эти методы всякий раз, когда происходит следующее:
· изменяется точность датчика.
В этом случае система вызывает onAccuracyChanged()метод, предоставляя вам ссылку на Sensorизмененный объект и новую точность датчика. Точностьпредставленаоднимизчетырехконстантсостояния: SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM, SENSOR_STATUS_ACCURACY_HIGH, или SENSOR_STATUS_UNRELIABLE.
· датчик сообщает новое значение.
В этом случае система вызывает onSensorChanged()метод, предоставляя вам SensorEventобъект. SensorEventОбъект содержит информацию о новых данных датчиков, в том числе: точность данных, датчик , который генерирует данные, метку времени, при котором был создан данных, а также новые данные о том, что датчик записанных.
В следующем коде показано, как использовать этот onSensorChanged()метод для отслеживания данных с датчика освещенности. В этом примере необработанные данные датчика отображаются в формате, TextView который определен в файле main.xml как sensor_data.
public class SensorActivity extends Activity implements SensorEventListener{
private SensorManager sensorManager;
private Sensor mLight;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mLight = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
// The light sensor returns a single value.
// Many sensors return 3 values, one for each axis.
float lux = event.values[0];
// Do something with this sensor value.
}
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);}
}
В этом примере SENSOR_DELAY_NORMALпри registerListener()вызове метода указывается задержка данных по умолчанию ( ) . Задержка данных (или частота дискретизации) контролирует интервал, с которым события датчиков отправляются в ваше приложение с помощью onSensorChanged()метода обратного вызова. Задержка данных по умолчанию подходит для отслеживания типичных изменений ориентации экрана и использует задержку в 200 000 микросекунд. Вы можете указать другие задержки данных, например SENSOR_DELAY_GAME(задержка 20 000 микросекунд),SENSOR_DELAY_UI(задержка 60 000 микросекунд) илиSENSOR_DELAY_FASTEST(задержка 0 микросекунд). Начиная с Android 3.0 (уровень API 11), вы также можете указать задержку как абсолютное значение (в микросекундах).
Указанная задержка является только предполагаемой. Система Android и другие приложения могут изменить эту задержку. Рекомендуется указать наибольшую возможную задержку, поскольку система обычно использует меньшую задержку, чем указанная вами (то есть следует выбрать самую низкую частоту дискретизации, которая все еще соответствует потребностям вашего приложения). Использование большей задержки снижает нагрузку на процессор и, следовательно, потребляет меньше энергии.
Не существует общедоступного метода для определения скорости, с которой инфраструктура датчиков отправляет события датчика в ваше приложение; однако вы можете использовать временные метки, связанные с каждым событием датчика, для расчета частоты дискретизации для нескольких событий. Вам не придется изменять частоту дискретизации (задержку) после ее установки. Если по какой-то причине вам все же нужно изменить задержку, вам придется отменить регистрацию и повторно зарегистрировать приемник датчика.
Также важно отметить, что в этом примере используются методы обратного вызова onResume()и onPause()для регистрации и отмены регистрации приемника событий датчика. Рекомендуется всегда отключать датчики, которые вам не нужны, особенно когда ваша деятельность приостановлена. В противном случае аккумулятор может разрядиться всего за несколько часов, потому что некоторые датчики имеют значительные требования к питанию и могут быстро расходовать заряд аккумулятора. Система не будет автоматически отключать датчики при выключении экрана.
5.3. Использование мобильных датчиков в мобильных приложениях
Android не определяет стандартную конфигурацию датчиков для устройств, что означает, что производители устройств могут включать любую конфигурацию датчиков, которую они хотят, в свои устройства под управлением Android. В результате устройства могут включать в себя множество датчиков в широком диапазоне конфигураций. Если ваше приложение использует датчик определенного типа, вы должны убедиться, что датчик присутствует на устройстве, чтобы ваше приложение могло успешно работать.
Естьдва способа убедиться, что данный датчик присутствует на устройстве:
· обнаружение датчиков во время выполнения и включение или отключение функций приложения в зависимости от ситуации.
· используйте фильтры Google Play для нацеливания на устройства с определенными конфигурациями датчиков.
Если приложение использует датчик определенного типа, можно использовать платформу датчиков для обнаружения датчика во время выполнения, а затем отключить или включить функции приложения в зависимости от ситуации. Например, приложение для навигации может использовать датчик температуры, датчик давления, датчик GPS и датчик геомагнитного поля для отображения температуры, атмосферного давления, местоположения и направления по компасу. Если на устройстве нет датчика давления, вы можете использовать платформу датчиков, чтобы обнаружить отсутствие датчика давления во время выполнения, а затем отключить часть пользовательского интерфейса вашего приложения, которая отображает давление. Например, следующий код проверяет, есть ли на устройстве датчик давления:
private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null){
// Success! There's a pressure sensor.
} else {
// Failure! No pressure sensor.
}
Для публикации приложения в Google Play, можно использовать <uses-feature>элемент в файле манифеста, чтобы отфильтровать приложение от устройств, которые не имеют подходящей конфигурации датчика для приложения. У <uses-feature>элемента есть несколько аппаратных дескрипторов, которые позволяют фильтровать приложения на основе наличия определенных датчиков. Можно указать следующие датчики: акселерометр, барометр, компас (геомагнитное поле), гироскоп, свет и приближение. Ниже приведен пример записи манифеста, которая фильтрует приложения, у которых нет акселерометра:
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />
Если добавить этот элемент и дескриптор в манифест приложения, пользователи увидят приложение в Google Play, только если на их устройстве есть акселерометр.
В коде нужно установить дескриптор android:required="true"только в том случае, если приложение полностью полагается на определенный датчик. Если ваше приложение использует датчик для некоторых функций, но по-прежнему работает без датчика, вы должны указать датчик в <uses-feature> элементе, но установить для дескриптора значениеandroid:required="false". Это помогает гарантировать, что устройства смогут установить ваше приложение, даже если у них нет этого датчика.
Ниже описаны некоторые аппаратно-ориентированные функции, содержащиеся в SDK Android.
Функция |
Описание |
|
Класс, позволяющий приложениям взаимодействовать с видеокамерой в целях фотосъемки, записи изображений с экрана предварительного просмотра или для изменения параметров настройки. |
|
Класс, обеспечивающий доступ к
внутренним датчикам платформы Android. Не каждое устройство на платформе
Android поддерживает все датчики из |
|
Интерфейс реализован с помощью класса, который используется для ввода значений датчиков по мере их изменения в режиме реального времени. Приложение реализует этот интерфейс для мониторинга одного или нескольких имеющихся аппаратных датчиков. |
|
Класс, используемый для записи медиафрагментов, который можно применять для записи звуков в определенном месте (например, в детской комнате). Можно также анализировать аудиофрагменты для контроля доступа в помещение и в целях безопасности. Например, можно открывать дверь собственным голосом в обычное время своего прихода, вместо того, чтобы обращаться к консьержу за ключом. |
|
Класс, который позволяет распознавать лицо человека по хранящейся в памяти фотографии. Ничто не удостоверяет личность лучше, чем лицо. Если использовать его для блокировки устройства, вам больше не придется запоминать пароли – достаточно биометрических возможностей мобильного телефона. |
android.os.* |
Пакет, содержащий несколько полезных классов для взаимодействия с операционной средой, включая управление питанием, поиск файлов, обработчик и классы для обмена сообщениями. Как и многие другие портативные устройства, телефоны на базе Android могут потреблять достаточно много электроэнергии. Обеспечение "бодрствования" устройства в нужный момент, чтобы проконтролировать нужное событие, - важный аспект проектирования, заслуживающий особого внимания. |
|
При измерении событий
реального мира часто имеют значение дата и время. Например, класс |
Android.hardware.SensorManager содержит несколько констант, которые характеризуют различные аспекты системы датчиков Android, в том числе:
Центром сенсорных приложений служит
интерфейс SensorListener
.
Он включает в себя два необходимых метода:
·
метод onSensorChanged(int sensor,float
values[])
вызывается всякий раз, когда изменяется значение
датчика. Этот метод вызывается только для датчиков, контролируемых данным
приложением. В число аргументов метода входит целое, которое указывает, что
значение датчика изменилось, и массив значений с плавающей запятой, отражающих
собственно значение датчика. Некоторые датчики выдают только одно значение
данных, тогда как другие предоставляют три значения с плавающей запятой.
Датчики ориентации и акселерометр дают по три значения данных каждый.
·
метод onAccuracyChanged(int sensor,int
accuracy)
вызывается при изменении точности показаний
датчика. Аргументами служат два целых числа: одно указывает датчик, а другое
соответствует новому значению точности этого датчика.
Для взаимодействия с датчиком
приложение должно зарегистрироваться на прием действий, связанных с одним или
несколькими датчиками. Регистрация осуществляется с помощью метода registerListener
классаSensorManager
.
Не все устройства Android поддерживает тот или иной датчик, указанный в SDK. Если на конкретном устройстве тот или иной датчик отсутствует, ваше приложение должно обрабатывать эту ситуацию аккуратно.
Пример работы с датчиком
Этот пример приложения просто следит
за изменениями значений датчиков ориентации и акселерометра. При изменении
значений датчиков они отображаются на экране в виджете TextView
.
Приложение создано в среде Eclipse с плагином Android Developer Tools.В листинге 1 приведен код этого приложения.
|
Это обычное приложение на основе действий, поскольку оно просто выводит на экран данные, получаемые от датчиков. Для ситуаций, когда устройство должно одновременно выполнять другие высокоприоритетные действия, приложение лучше было бы реализовать в форме сервиса.
Метод действия onCreate
получает
ссылку на SensorManager
,
где расположены все связанные с датчиками функции. Кроме того, метод onCreate
создает
ссылки на шесть виджетов TextView
, в которые будут выводиться
результаты измерений.
Метод onResume()
,
используя ссылку на SensorManager
,
регистрируется на прием обновлений данных датчика посредством метода registerListener
:
·
Первый параметр представляет собой
экземпляр класса, реализующий интерфейс SensorListener
.
·
Второй параметр - это битовая маска
желаемых датчиков. В данном случае приложение запрашивает данные из SENSOR_ORIENTATION
и SENSOR_ACCELEROMETER
.
· Третий параметр указывает системе, как быстро должны обновляться значения датчиков для данного приложения.
Когда работа приложения
приостанавливается, нужно отменить регистрацию приемника, чтобы больше не
получать обновления значений датчика. Это делается с помощью методаunregisterListener
класса SensorManager
.
Единственным параметром является экземпляр SensorListener
.
При вызове обоих методов registerListener
иunregisterListener
приложение
использует ключевое слово this
. Ключевоеслово implements
в
определении класса, которое декларирует, что этот класс реализует интерфейс SensorListener
.
ВregisterListener
и unregisterListener
передается this.
SensorListener
долженреализоватьдваметода:onSensorChange
и onAccuracyChanged
. Пример приложения не заботится о
точности датчиков, а лишь вводит текущие значения X, Y и Z этих датчиков.
Метод onAccuracyChanged
добавляет запись в журнал при каждом вызове.
МетодonSensorChanged
вызывается
постоянно, так как акселерометр и датчик ориентации передают данные быстро.
Чтобы определить, какой датчик передает данные смотрим на первый параметр.
Когда передающий датчик выявлен, соответствующие элементы пользовательского
интерфейса обновляются данными, содержащимися в массиве значений с плавающей
запятой, принятом в качестве второго аргумента метода. Хотя пример просто
отображает эти значения, в более сложных приложениях они могут анализироваться,
сравниваться с предыдущими значениями или использоваться в алгоритме
распознавания образов для определения того, что делает пользователь.
Теперь, приведем пример кода, который записывает звук на телефон Android. Этот пример работает на устройстве для разработчиков Dev1.
Пакет Android.media содержит классы
для взаимодействия с мультимедийной подсистемой. Класс android.media.MediaRecorder
используетсядля
записи медиафрагментов, включая аудио и видео. MediaRecorder
действует
как конечный автомат. Вы задаете различные параметры, такие как
устройство-источник и формат. После установки запись может выполняться как угодно
долго, пока не будет остановлена.
В листинге 2 приведен код для записи звука на устройство Android. Этот код не включает элементы пользовательского интерфейса приложения.
MediaRecorder
mrec ;
File
audiofile = null;
private
static final String TAG="SoundRecordingDemo";
protected
void startRecording() throws IOException {
mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
mrec.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
if
(mSampleFile == null) {
File
sampleDir = Environment.getExternalStorageDirectory();
try
{
audiofile
= File.createTempFile("ibm", ".3gp",
sampleDir); }
catch
(IOException e) {
Log.e(TAG,"sdcard
access error");
return; } }
mrec.setOutputFile(audiofile.getAbsolutePath());
mrec.prepare();
mrec.start();}
protected
void stopRecording() {
mrec.stop();
mrec.release();
processaudiofile(audiofile.getAbsolutePath());}
protected
void processaudiofile() {
ContentValues
values = new ContentValues(3);
long
current = System.currentTimeMillis();
values.put(MediaStore.Audio.Media.TITLE,
"audio" + audiofile.getImya());
values.put(MediaStore.Audio.Media.DATE_ADDED,
(int) (current / 1000));
values.put(MediaStore.Audio.Media.MIME_TYPE,
"audio/3gpp");
values.put(MediaStore.Audio.Media.DATA,
audiofile.getAbsolutePath());
ContentResolver
contentResolver = getContentResolver();
Uri
base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Uri
newUri = contentResolver.insert(base, values);
sendBroadcast(new
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
}
В методе startRecording
создается
и инициализируется экземплярMediaRecorder
.
·
В качестве источника данных выбирается
микрофон (MIC
).
· Выходной формат устанавливается в 3GPP (файлы *.3gp) - медиаформат, ориентированный на мобильные устройства.
·
Кодер настроен на AMR_NB
-
аудиоформат с частотой дискретизации 8 кГц. NB означает узкую полосу частот.
Различные форматы данных и имеющиеся кодеры рассматриваются в документации
SDK.
Аудиофайл хранится не во внутренней
памяти, а на отдельной карте. External.getExternalStorageDirectory()
возвращает
имена карты памяти и временного файла, созданного в этом каталоге. Затем этот
файл связывается с экземпляром MediaRecorder
, обращаясь к
методу setOutputFile
.
Аудиоданные будут храниться в этом файле.
Вызов метода prepare
завершает
инициализацию MediaRecorder
.
Когда нужно начать процесс записи, вызывается метод start
.
Запись в файл на карте памяти ведется до тех пор, пока не будет вызван
метод stop
.
Этот метод освобождает ресурсы, выделенные экземпляру MediaRecorder
.
Когда аудиофрагмент записан, можно выполнить несколько действий:
· Добавить аудиозапись в медиатеку на устройстве.
· Выполнить некоторые шаги по распознаванию звука:
· Автоматически загрузить звуковой файл в сетевую папку для обработки.
В примере кода метод processaudiofile
добавляет
аудиозапись в медиатеку. Для уведомления встроенного приложения о том, что
доступна новая информация, используется Intent
.
Послесоздания он не будет записывать аудио. Нужно добавить разрешение в файл AndroidManifest.xml:
|
1. Какие жесты можно распознать в мобильных приложениях?
2. Какие методы распознования жестов используются?
3. Какие методы используются для сенсорного управление?
4. Какие методы используются определения датчиков?
5. Какие методы используются для определения сенсоров?
6. РАЗРАБОТКА ПРИЛОЖЕНИЙ НА ЯЗЫКЕ ПРОГРАММИРОВАНИЯ SWIFT ДЛЯ IOS
6.1. Основные констукции языка программирования Swift
Основнымязыком программирования под iOS и MacOS был Objective-C, но с 2 июня 2014 года был представлен новый и удобный язык программирования - Swift. По сравнению с Objective-C Swift обладает следующими особенностями:
· Swift является объектно-ориентированным языком программирования;
· простота, ясный и четкий синтаксис;
· строгая типизированность. Каждая переменная имеет определенный тип;
· автоматическое управление памятью.
Язык Swift полностью совместим с ранее написанными прикладными интерфейсами Cocoa API, для которых использовались C и Objective-C.
Язык Swift продолжает развиваться. 19 сентября 2017 года вышла версия 4.0, которая добавила новые возможности для разработки под iOS и Mac OS.
ЯзыкSwift является компилируемым языком программирования, то есть разработчик используя компилятор, компилирует код в управляющую программу, а потом файл программы можно загрузить в AppStore и можно распространять.
Для языка Swift необходима операционная система Mac OS 10.12 Yosemite или выше. Без Mac OS практически невозможно скомпилировать программу. Данное обстоятельство сильно ограничивает возможности разработки, учитывая что Mac OS может гарантированно работать лишь на компьютерах самой компании Apple (iMac, MacBook, MacBook Air, MacBook Pro), а также учитывая высокую стоимость этих самых компьютеров. Однако на обычном PC под управлением ОС Windows или ОС на базе Linux создавать приложения под iOS и Mac OS невозможно.
Существуют также варианты с виртуальными машинами, на которые установлена Mac OS илиMacintosh.
Также написать код в любой операционной системе и компиляция его с помощью специальных сервисов за определенную плату или бесплатно.
Непосредственно для самой разработки потребуются инструменты языка Swift, текстовый редактор для написания кода, симуляторы iPhone и iPad для отладки приложения. Для разработки Apple предоставляет бесплатную среду разработки XCode.
XCode предоставляет симуляторы для тестирования, однако в некоторых случаях предпочтительнее тестировать на реальном смартфоне.
И также перед созданием приложений, необходимо зарегистрироваться в центре Apple для разработчиков. Для этого надо пройти по ссылке https://developer.apple.com/register/:
Для регистрации нужно войти на сайт с помощью своего идентификатора Apple ID и пароля. Если такого идентификатора нету, то можно создать новую учетную запись, перейдя по ссылке Create Apple ID. После регистрации сайт перенаправит на страницу https://developer.apple.com/resources/, где можно найти различные материалы по разработке для самых разных аспектов.
6.1.2. Начало работы с Swift в XCode
Для разработки под iOS потребуется специальная среда программирования, которая называется XCode. XCode позволяет использовать языки Swift и Objective-C для создания приложений под iOS и Mac OSX. Она является бесплатной, ее можно установить из App Store:
После установки запустим XCode. По умолчанию открывается стартовый экран с выбором опций для создания нового проекта, а также список ранее созданных проектов:
В этом окне выберем пункт Get started with a playground.После этого надо будет указать тип проекта. Выберем первый тип проекта - Blank:
Далее надо будет указать, под каким именем будет сохраняться проект. Укажем в качестве имени HelloSwift и нажмем на кнопку Create:
Послеэтих настроек откроется среда Playground. Онапредставляет текстовый редактор с окном консольного вывода, в котором можноработать с выражениями и операциями языка Swift.
В верхней панели MacOS установится меню для управления XCode. Большую часть Playground будет занимать текстовый редактор, в который вводится команды языка Swift. А справа находится окно консольного вывода, где можно увидеть результат введенных команд.
Удалим все из текстового редактора и введем в него следующее простейшее выражение:
print("Hello world!")
print() - это метод, который выводит некоторую строку. В примере это строка "Hello world!". И в окне консольного вывода справа можно увидеть это сообщение:
Это простой пример, но в дальнейшем часто будем обращаться к среде Playground в XCode при изучении языка Swift.
Структура программы
Структура программа на языке Swift состоит из набора команд, каждая из команд называется инструкцией (statement). Например, инструкция:
print("hello world!")
Как правило, каждая инструкция помещается на одной строке:
print("hello world!")
Язык Swift имеет Си-подобный синтаксис, то есть в таких языках программирования как C, C++, C#, Java однострочные инструкции не завершаются точкой с запятой. Но если помещать несколько инструкций на одной строке, например:
print("hello world!"); print("welcome to swift") |
То в этом случае их следует разделять точкой с запятой.
ВSwift для оформления структурных блоков применяются фигурные скобки. Например:
|
Комментарии
В Swift можно определять комментарии к исходному коду. Для создания многострочных комментариев применяется конструкция /* текст комментария */:
|
Для создания однострочных комментариев применяется двойной слеш:
|
При компиляции программы комментарии не учитываются и служат для напоминания разработчику об исходном коде.
6.1.3. Переменные и константы. Типы данных
Программа на Swift обладает двумя свойствами: она может хранить некоторые данные и может выполнять действия. Для хранения данных в Swift используются переменные. Переменная представляет именнованный участок в памяти, в котором хранится некоторое значение.Переменные имеют имя и значение. Для определения переменной используется ключевое слово var. Например:
|
В примере переменная имеет имя "imya" и в качестве значения хранит строку "Karl". После определения переменной можно использовать ее, например, передать ее функцию print, чтобы вывести ее значение:
|
Отличительной особенностью переменных является то, что можно изменять их значение многократно во время работы программы. Например:
1 2 |
var imya = "Tom" // значение Tom imya = "Karl" // значение Karl |
Кроме переменных для хранения данных в программе могут использоваться константы. Константы похоже на переменных, они тоже хранят некоторое значение, за тем исключением, что определяются с помощью ключевого слова let и нельзя после их инициализации изменить их значение:
let imya = "Tom" // значение Tom // imya = "Karl" - так сделать нельзя, так как imya - константа |
Еслизначение переменной в течении программы меняться не будет, то вместо этой переменной лучше использовать константу.
Можно определить сразу несколько переменных и констант на одной строке. В этом случае они разделяются запятой:
var imya = " Karl", surimya = "Tsukerberg" |
Правила именования
Переменные и константы должны иметь уникальные имена. Нельзя использовать в программе несколько переменных и констант с одними и теми же именами.
Если название состоит из нескольких слов, то только первое из них начинается с маленькой буквы. Например:
var GrajImya = "Karl" var GrajStreatAddress = "St.Medisson avenue, 47" |
Типы данных
Каждая переменная или константа хранит в себе значение определенного типа. Например, выше использованная переменная imya хранит строку:
var imya = " Karl" |
В языке Swift имеются следующие типы данных:
· int8: представляет целые числа, сразмером 8 бит (от -128 до 127);
· uInt8: представляет целые положительные числа, размером 8 бит (от 0 до 255);
· int16: представляет целые числа, с размером 16 бит (от -32768 до 32767);
· uInt16: представляет целые положительные числа, размером 16 бит (от 0 до 65535);
· int32: представляет целые числа, с размером 32 бита (от -2147483648 до 2147483647);
· uInt32: представляет целые положительные числа, размером 32 бита (от 0 до 4294967295);
· int64: представляет целые числа, с размером 64 бита (от -9223372036854775808 до 9223372036854775807);
· uInt64: представляет целые положительные числа, размером 64 бита (от 0 до 18446744073709551615);
· int: представляет целые числа, например, 1, -30, 458. На 32-разрядных платформах эквивалентен Int32, а на 64-разрядных - Int64;
· uInt: представляет целые положительные числа, например, 1, 30, 458. На 32-разрядных платформах эквивалентен UInt32, а на 64-разрядных - UInt64;
· float: 32-битное число с плавающей точкой, содержит до 6 чисел в дробной части;
· double: 64-битное число с плавающей точкой, содержит до 15 чисел в дробной части;
· float80: 80-битное число с плавающей точкой;
· bool: представляет логическое значение true или false;
· string: представляет строку;
· character: представляет отдельный символ.
Тип переменных и констант можно определить явно или неявно. Выше тип определялся неявно. Но также можно явным образом определить тип:
var vozrast: Int = 46 var imya: String = " Karl" |
Определение переменной с типом происходит по шаблону:
var название_переменной: тип_переменной = значение_переменной |
Также можно сначала определить переменную, а потом присвоить ей значение:
var imya: String imya = " Karl" |
Также можно определить сразу набор однотипных переменных:
|
Неявная типизация
Если явным образом не указывать тип переменных и констант, то он автоматически выводится системой на основании хранящегося значения. Например:
var imya = " Karl" |
Здесь явным образом не указан тип переменной imya, однако поскольку она хранит строку, то система будет рассматривать эту переменную как объект типа String. Или, например:
var vozrast = 46 |
Здесь также явно не указан тип переменной, поэтому система будет рассматривать эту переменную как объект Int, то есть целое число.
Однако при таком подходе Swift не всегда выводит те типы, которые могут быть нужны. Например, все целые числа Swift воспринимает как объекты типа Int, а дробные числа - как объекты типа Double. Надоучитывать, чтобы не попасть в некорректные ситуации. Например:
var a = 5.7 // тип Double var g : Float = 1.2 a = g // Ошибка - разные типы |
В примере на основании присвоенного значения переменная a будет представлять тип Double, а переменная g представляет тип Float, поэтому при присвоении переменной a значения g получается ошибка.
Типобезопасность
В Swift после того как для переменной будет установлен тип, его уже изменить нельзя. Так, в следующем случае тоже будет ошибка:
var vozrast: Int vozrast = "Lesli" |
Ошибка возникает, так как переменная vozrast ожидает число, а строка "Lesli" не является числом и не соответствует переменной vozrast по типу.
Можноприсваивать значение переменной или константы другой переменной:
var vozrast: Int = 22 var years = vozrast |
Для работы с числами в Swift можно использовать целочисленные литералы (типа 2, 3, 78) и литералы чисел с плавающей точкой, в которых разделителем между целой и дробной частью является точка, например, 1.2, 3.14, 0.025. Всецелочисленные литералы рассматриваются как значения типа Int, а все дробные литералы - как значения типа Double.
let a = 6 // Int let у = 5.7 // Double |
Если присвоить числовой литерал переменным или константам типов, отличных от Int и Double, то компилятор может автоматически выполнять преобразование:
let у : Int16 = 34 // неявное преобразование от Int к Int16 let к : Float = 6.77 // неявное преобразование от Double к Float |
Для числовых типов большое значение имеет размерность, то есть количество бит, которое данный тип содержит. Например, тип UInt8 не может хранить число больше чем 255. Поэтому не получится присвоить переменной этого типа, например, число 345:
var vozrast: UInt8 = 345 //здесь ошибка |
Минимальное и максимальное значение для определенного числового типа можно получить с помощью констант min и max:
3 4 |
var minInt16 = Int16.min // -32768 var maxInt16 = Int16.max // 32767 var minUInt16 = UInt16.min // 0 var maxUInt16 = UInt16.max // 65535 |
Форматы записи числовых данных
По умолчанию Swift работает с десятичной системой исчисления. Однако он может работать и с другими системами:
· десятичная: числа используются так, как они есть, без каких-либо префиксов;
· двоичная: перед числом используется префикс 0b;
· восьмеричная: перед числом используется префикс 0o;
· шестнадцатеричная: перед числом используется префикс 0x.
Например, запишем число 10 во всех системах исчисления:
let c = 10 let b = 0b1010 // 10 в двоичной системе let t = 0o12 // 10 в восьмеричной системе let n = 0xA // 10 в шестнадцатеричной системе |
Для чисел с плавающей точкой возможна запись в двух системах: десятичной и шестнадцатеричной. Для упрощении записи длинных чисел в десятичной системе можно использовать символ e (экспонента). Например:
var к = 5.7e2 // 57 var у = 3.7e-2 // 0.037 |
Для записи чисел с плавающей точкой в шестнадцатеричной системе используется префикс p:
var р = 0xFp2 //15 * 2 в степени 2 или 60.0 var х = 0xFp-2 //15 * 2 в степени -2 или 3.75 |
Арифметические операции
Swift имеет набор арифметических операций. Арифметические операции производятся над числами:
· +, сложение двух чисел:
var р = 5 var с = 6 var х = р + с // 11 |
· -, вычитание двух чисел:
var р = 7 var с = 2 var х = р - с // 5 |
· -, унарный минус. Возвращает число, умноженное на -1:
var р = -11 var с = -р // 11 var х = -с // -11 |
· *, умножение:
var р = 4 var с = 8 var х = р * с // 32 |
· /, деление:
var р = 15 var с = 3 var х = р / с // 5 |
При делении стоит учитывать, какие данные участвуют в делении и какой результат получится. Например, в следующем случае выполняется деление дробных чисел:
let р : Double = 10 let с : Double = 4 let x : Double = р / с // 2.5 |
Результатом операции чисел Double является значение типа Double, которое равно 2.5. Но если возьмем значения типа Int, то результат будет:
let р : Int = 10 let с : Int = 4 let x : Int = р / с // 2 |
Оба операнда операции представляют тип Int, поэтому результатом операции является значение типа Int. Он не может быть дробным, поэтому дробная часть отбрасывается, и получается не 2.5, а число 2.
· %, возвращает остаток от деления:
var р = 10 var с = 4 var х = р % с // 2 |
При арифметических операциях надо учитывать, что они производятся только между переменными одного типа. Например, в следующем примере получим ошибку:
var р: Int8 = 35 var с: Int32 = 12 var х= р + с |
р и с должны в примере представлять один и тот же тип.
И также арифметические операции возвращают объект того же типа, к которому принадлежат операнды операции. Например, в следующем примере получим ошибку:
var р: Int8 = 34 var с: Int8 = 26 var х: Int32 = р + с |
В примере переменная х, как и р и с, также должна представлять тип Int8.
Ряд операций сочетают арифметические операции с присваиванием
· +=, присвоение со сложением, прибавляет к текущей переменной некоторое значение:
var с = 5 с += 20 print(с) // 25 // эквивалентен // с =с + 20 |
· -=, присвоение с вычитанием, вычитает из текущей переменной некоторое значение:
var р = 13 р -= 8 print(р) // 7 // эквивалентно // р = р - 6 |
· *=, присвоение с умножением, умножает текущую переменную на некоторое значение, присваивая ей результат умножения:
var р = 12 р *= 4 print(р) // 48 // эквивалентно // р = р * 4 |
· /=, присвоение с делением, делит значение текущей переменной на другое значение, присваивая результат деления:
var с = 9 с /= 3 print(с) // 3 // эквивалентно // с = с / 3 |
· %=, присвоение с остатком от делением, делит значение текущей переменной на другое значение, присваивая переменной остаток от деления:
var р = 36 р %= 6 print(р) // 6 // эквивалентно // р = р % 6 |
Преобразование числовых данных
В арифметических операциях все операнды должны представлять один и тот же тип данных. Результатом операции является значение того же типа, что и тип операндов:
var к: Int8 = 5 var а: Int8 = 7 var у: Int8 = к + а print(у) // 12 |
Однако не всегда операнды представляют один и тот же тип. И также не всегда тип переменной или константы, которой присваивается результат операции, совпадает с типом операндов. И в подобных случаях необходимо выполнять преобразования типов.
Если операнды операций представляют числовые литералы, которые относятся к разным типам, то Swift автоматически выполняет преобразование:
let у = 10/3.0 print(у) // 3.33333333333333 |
В примере числовой литерал 10 представляет тип Int, а 3.0 - тип Double. В этом случае литерал преобразуется к типу Double и выполняется деление.
Еслиоперанды представляют константы или переменные или являются результатами каких-то других операций или выражений, то в этом случае нужно явно выполнять преобразование типов. Для этого применяются специальные функции-инициализаторы типов данных. Онисовпадают с названиями типов данных: Int8(), Int(), Float(), Double() и т.д. функцияDouble() преобразует значение к типу Double, а в скобки передается само значение. Например:
let s = 4 // представляет тип Int let t = Double(s) // преобразуем в тип Double print(t) // 4.0 |
При этом нельзя напрямую передать значение типа int переменной типа Double, обе переменные после преобразования все равно содержат фактически число 5:
let р = 5 // представляет тип Int let е: Double = р // ! Ошибка - разные типы |
Здесь надо использовать явное преобразование типов: let е = Double(d)
Преобразование типов может помочь, если операнды представляют разные типы. Например, в следующем случае столкнемся с ошибкой:
let р = 10 let с = 3.0 let у = р / с // ошибка компиляции |
Чтобы код заработал, используем явное приведение типов:
let р = 10 let с = 3.0 let у = Double(р) / с // у = 3.3333333333 |
В примере значение константы р преобразуется в Double, и константа с также представляет тип Double, то ошибки не возникнет, а константа у будет представлять тип Double.
Либо можно преобразовать второй операнд к типу Int:
let р = 10 let с = 3.0 let у = р / Int(с) // у = 3 |
Так как оба операнда теперь представляют тип Int, то и константа у будет представлять Int и будет равна 3.
Другой пример: сумма значений Int8 присваивается переменной типа Int32, то есть отличается тип операндов от типа результата:
var р: Int8 = 4 var с: Int8 = 23 var у: Int32 = р + с // Error |
Здесь результатом операции является значение типа Int8, так как оба операнда представляют этот тип. Нельзя присвоить переменной тип Int32так как это целое число. Необходимо выполнить преобразование типов:
var р: Int8 = 13 var с: Int8 = 16 var у: Int32 = Int32(р) + Int32(с) |
В этом случае переменный р и с преобразуются к типу Int32.
Поразрядные операции с числами
Поразрядные или побитовые операции выполняются над отдельными разрядами целых чисел. Каждое число имеет определенное представление в памяти. Например, число 5 представлено в двоичном виде следующим образом: 101. Число 7 - 111.
В Swift есть следующие поразрядные операции:
· & (операция логического умножения или операция И)
сравнивает два соответствующих разряда двух чисел и возвращает 1, если соответствующие разряды обоих чисел равны 1. Иначе возвращает 0.
let p = 6 // 110 let q = 5 // 101 let c = p&q // 100 - 4 print(c) // 4 |
В примере число p равно 6, то есть 110 в двоичном формате, а число q равно 5 или 101 в двоичном формате. В итоге при выполнении операции получится число 4 или 100.
· | (операция логического сложения или операция ИЛИ)
возвращает 1, если хотя бы один из соответствующих разрядов обоих чисел равен 1. Иначе возвращает 0.
let p = 6 // 110 let q = 5 // 101 let c = p | q // 111 - 7 print(c) // 7 |
· ^ (операция исключающее ИЛИ)
возвращает 1, если соответствующие разряды обоих чисел не равны.
let p = 6 // 110 let q = 5 // 101 let c = p ^ q // 011 - 3 print(c) // 3 |
· ~ (операция инверсии или операция НЕ)
принимает один операнд и инвертирует все его биты. Если разряд равен 1, то он получает значение 0 и наоборот, если разряд равен 0, то он становится равным 1.
let p = 6 // 000000000000000110 let q = ~p // 111111111111111001 print(q) // -7 |
· << (поразрядный сдвиг влево)
Сдвигает биты первого операнда влево на количество разрядов, заданное вторым операндом. То есть первый операнд справа дополняется нулями, количество которых равно второму операнду.
let p = 6 // 110 let q = 2 let c = p<<q // 110 << 2 = 11000 print(c) // 24 |
· >> (поразрядный сдвиг вправо)
Сдвигает биты первого операнда вправо на количество разрядов, заданное вторым операндом.
4 |
let p = 13 // 1101 let q = 2 let c = p>>q // 1101 >> 2 = 11 print(c) // 3 |
И также есть ряд операций, которые сочетают поразрядные операции с присваиванием:
· &=: присвоение после поразрядной операции И.
Присваивает левому операнду результат поразрядной конъюнкции его битового представления с битовым представлением правого операнда: A &= B эквивалентно A = A & B
· |=: присвоение после поразрядной операции ИЛИ.
Присваивает левому операнду результат поразрядной дизъюнкции его битового представления с битовым представлением правого операнда: A |= B эквивалентно A = A | B
· ^=: присваивание после операции исключающего ИЛИ.
Присваивает левому операнду результат операции исключающего ИЛИ его битового представления с битовым представлением правого операнда: A ^= B эквивалентно A = A ^ B.
· <<=: присваивание после сдвига разрядов влево.
Присваивает левому операнду результат сдвига его битового представления влево на определенное количество разрядов, равное значению правого операнда: A <<= B эквивалентно A = A << B
· >>=: присваивание после сдвига разрядов вправо.
Присваивает левому операнду результат сдвига его битового представления вправо на определенное количество разрядов, равное значению правого операнда: A >>= B эквивалентно A = A >> B.
Применение операций:
var p = 13 // 1101 p&= 5 // 1101 & 0101 = 0101 print(p) // 5 p |= 6 // 0101 | 0110 = 0111 print(p) // 7 p ^= 4 // 111 ^ 100 = 011 print(p) // 3 p<<= 3 // 11 << 3 = 11000 print(p) // 24 p>>= 1 // 11000 >> 1 = 1100 print(p) // 12 |
Типы Character и String
Для работы с текстом применяются два типа данных: Character и String. Character представляет отдельный символ, а String - строку из нескольких символов. String - это отдельный тип, а не просто набор объектов Character, который по функциональности отличается.
Самый простой способ определения строки и символов представляет использование строковых литералов - значений в двойных кавычках:
var p: Character = "s"
var hello: String = "salom"
В отличие от строки в переменную типа Character нельзявнести больше одного символа, а то возникнет ошибка:
var p: Character = "ghjk"
Строки могут быть пустымии не содержать ничего, но при этом быть инициализированными:
var str1: String = ""
var str2: String = String()
Пустая строка создается при помощи присвоения "" или при помощи инициализатора String()
Управляющие последовательности
Строка может содержать специальные символы, которые называются управляющими последовательностями или эскейп-последовательностями. Основные из них:
\n: перевод на новую строку
\t: табуляция
\": кавычка
\\: обратный слеш
Например, если нужно определить многострочный текст, в котором будут использоваться кавычки и обратные слеши:
let text = "ООО \"Favorit\" \n Dir: Davletov"
print(text)
XCode Playground интерпретирует данную строку следующим образом:
ООО "Favorit Dir: Davletov"
Favorit Dir: Davletov
Для преобразования значений других типов данных к строке надо передать преобразуемое значение в инициализатор:
var sifra: Int = 76
var str: String = String(sifra) // "76"
Конкатенация строк
Для конкатенации (объединения) строк используется оператор + (плюс):
var stroka: String = "" // ""
stroka += "privet" // "privet"
stroka = stroka + " mir" // "privetmir"
Интерполяция представляет собой объединение в строку различных литералов и значений, которые могут представлять другие типы данных. Для интерполяции объекты других типов данных заключаются в скобки, перед которыми ставится слеш:
var vozrast: Int = 26
var str: String = "Vozrast: \(vozrast)" // "Vozrast: 26"
var weight: Double = 50.8
str = "Vozrast: \(vozrast) and weight: \(weight)"// "Vozrast: 26 and weight: 50.8"
Также интерполяция может содержать сложные выражения и операции, например:
let y1 = 7
let y2 = 13
let str = "y1 + y2 = \(y1 + y2)"
print(str) // y1 + y2 = 20
Тип Bool. Условные выражения
Тип Bool представляет логическое значение true (истина) или false (ложь). То есть объект Bool может находиться в двух состояниях:
1 2 |
var vkl: Bool = true vkl = false |
Объекты типа Bool являются результатом условных выражений, которые представляют некоторое условие, и в зависимости от истинности условия возвращают true или false: true - если условие истинно и false - если условие ложно.
Операции сравнения
Операции сравнения сравнивают два значения и в зависимости от результата сравнения возвращают объект типа Bool: true или false.
· ==, операция равенства. Сравнивает два значения и если они равны, возвращает true:
var p = 3 var q = 3 var c = p == q print(c) // true, так как p равно q var d = 4 c = p==d print(c) // false, так как p не равно d |
· !=, операция неравенства. Сравнивает два значения и если они неравны, возвращает true:
var p = 3 var q = 3 var c = p != q print(c) // false, так как p равно q var d = 4 c = p!=d print(c) // true, так как p не равно d |
· >, сравнивает два значения и возвращает true, если первое значение больше второго:
var p = 8 var q = 5 var c = p>q print(c) // true, так как p больше чем q var d = 4 c = d >p print(c) // false, так как d меньше чем p |
· <, сравнивает два значения и возвращает true, если первое значение меньше второго:
var p = 8 var q = 5 var c = p<q print(c) // false, так как p больше чем q var d = 4 c = d <p print(c) // true, так как d меньше чем p |
· >=, сравнивает два значения и возвращает true, если первое значение больше или равно второму:
var p = 5 var q = 5 var c = p>= q print(c) // true, так как p равно q var d = 4 c = d >= p print(c) // false, так как d меньше чем p |
· <=, сравнивает два значения и возвращает true, если первое значение меньше или равно второму:
var p = 8 var q = 5 var c = p<= q print(c) // false, так как p больше чем q var w = 6 c = w<= p print(c) // true, так как w меньше p |
Логические операции
Логические операции выполняются над объектами типа Bool и в качестве результата также возвращают объект Bool.
· !, логическое "НЕ" или операция отрицания. Она инвертирует значение объекта: если он был равен true, то операция возвращает false, и наоборот:
var vkl: Bool = true var result = !vkl // false |
· &&, логическое "И" или операция логического умножения. Она возвращает true, если оба операнда операции имеют значение true:
let vkl: Bool = true let odnkav = true let result = vkl&&odnkav // true - оба операнда равны true let p: Bool = true let q: Bool = false let c: Bool = true let w = p&& b && c // false, так как q = false |
· ||, логическое "ИЛИ" или операция логического сложения. Она возвращает true, если хотя бы один из операндов операции равен true:
var vkl: Bool = true var odnkav = false vkl || odnkav // true, так как vkl равен true var p: Bool = true var q: Bool = false var c: Bool = false p || q || c // true, так как p = true |
Нередко логические операции объединяют несколько операций сравнения:
let p = 10 let q = 12 let c = p> 8 &&q< 10 let w = p> 8 || q< 10 print(c) // false print(w) // true |
Кортежи
Кортежи или Tuples представляют набор значений, которые рассматриваются как один объект. Для создания кортежа используются скобки, внутри которых записываются все элементы кортежа:
let yosh = (17, "vozrast") var user = (true, 28, "Karl") |
Здесь два кортежа props и danniy. Кортеж props содержит число 17 и строку "vozrast", а кортеж danniy хранит логическое значение true, число 28 и строку "Karl".
Впримере тип данных в кортеже не указан и выводится неявно, можно при определении кортежа объявить и типы данных его значений:
let props: (Int, String) = (17, "vozrast") var danniy: (Bool, Int, String) = (true, 28, "Karl") |
Приобъявлении типов кортежа его значения должны полностью соответствовать по типу.
Можно присвоить значения из кортежа переменным или константам. Например:
var danniy: (Bool, Int, String) = (true, 28, "Karl") let(semey, vozrast, imya) = danniy print(imya) // "Karl" |
Здесь трем константам semey, vozrast и imya присваиваются значения кортежа. Причем присвоение идет по позиции: константе semey присваивается первое значение кортежа, константе vozrast - второе значение и так далее.
Если необходимо игнорировать при присвоении некоторые значения, то для них можно использовать знак подчеркивания _. Например, не нужно первое значение кортежа:
var danniy: (Bool, Int, String) = (true, 34, "Karl") let(_, vozrast, imya) = danniy |
Вместо присвоения всех значений кортежа переменным можно выполнять присвоение по отдельности:
var danniy: (Bool, Int, String) = (true, 34, "Karl") let vozrast = danniy.1 let semey = danniy.0 var imya = danniy.2 |
Нумерация элементов кортежа начинается с нуля, поэтому к первому элементу можно обратиться так: danniy.0.
Также по отдельности можно изменять элементы кортежа, если кортеж определен как переменная:
var danniy: (Bool, Int, String) = (true, 34, "Karl") danniy.2 = "Roman" var imya = danniy.2 // Roman |
Правда, если кортеж определен как константа, изменить его значения нельзя.
Также при определении кортежа можно именовать его отдельные элементы. Например:
var danniy = (married: true, vozrast: 34, imya: "Karl") let vozrast = danniy.vozrast var imya = danniy.imya // Karl |
6.1.4. Условная конструкция If. Тернарный оператор
В языках программирования часто бывает необходимо произвести некоторые действия в зависимости от выполнения тех или иных условий. Если определенные условия соблюдаются, то выполнить одни действия, если не соблюдаются, то выполнить другие. Вязыках программирования применяются условные конструкции. Рассмотрим, какие условные конструкции есть в языке Swift.
Конструкция if/else
Конструкция if проверяет истинность некоторого условия и в зависимости от результатов проверки выполняет определенный код:
if условие { // набор действий } |
Например:
let sif1 = 14 let sif2 = 9 if sif1>sif2{ print("sif1 больше чем sif2") } |
Здесь если первое число больше второго, то сработает весь код в блоке if, который располагается между открывающей и закрывающей фигурными скобками. Если же первое число окажется меньше второго, тогда действия в конструкции if работать не будут.
Послеслова if должно идти значение типа Bool. Результатоперации сравнения возвращает логическое значение, то в примере ошибок не возникнет. Еслиукажем после if число или строку, то программа завершится ошибкой:
let sif1 = 22 let sif2 = 15 if sif1{ print("sif1 больше чем sif2") } |
Если при проверке условия надо выполнить какие-либо действия, то можно использовать блок else:
let sif1 = 22 let sif2 = 15 if sif1>sif2{ print("sif1 больше чем sif2") } else{ print("sif1 меньше чем sif2") } |
Присравнении чисел можно насчитать три состояния: первое число больше второго, первое число меньше второго и числа равны. Используя конструкцию else if, можно обрабатывать дополнительные условия:
let sif1 = 14 let sif2 = 9 if sif1>sif2{ print("sif1 больше чем sif2") } else if (sif1<sif2){ print("sif1 меньше чем sif2") } else{ print("sif1 и sif2 равны") } |
Тернарный оператор
Тернарный оператор аналогичен простой конструкции if и имеет следующий синтаксис:
[первый операнд - условие] ? [второй операнд] : [третий операнд] |
В зависимости от условия тернарный оператор возвращает второй или третий операнд: если условие равно true, то возвращается второй операнд; если условие равно false, то третий. Например:
var sif1 = 23 var sif2 = 11 var num3 = sif1>sif2 ? sif1 - sif2 : sif1 + sif2 |
В примере num3 будет равно 11, так как sif1 больше sif2, поэтому будет выполняться второй операнд: sif1 - sif2.
Конструкция switch
Конструкция switch/case похожа на конструкцию if/else, так как позволяет обработать сразу несколько условий:
var num: Int = 31 switch num { case 0: print("Переменная равна 0") case 10: print("Переменная равна 10") case 31: print("Переменная равна 31") default: print("не удалось распознать число") } |
После ключевого слова switch идет сравниваемое выражение. Это может быть переменная или константа. Значение этого выражения последовательно сравнивается со значениями, помещенными после оператора сase. И если совпадение будет найдено, то будет выполняться определенный блок сase.
Если совпадение не будет найдено, то выполняется оператор default.
В примере так как переменная num равна 31, будет выполняться следующий блок case:
case 31: print("Переменная равна 31") |
Вконце блока case ставится оператор break для прерывания выполнения и выхода из блока switch/case. В Swift использовать оператор break в подобных случаях необязательно. Однако бывают случаи, когда обрабатывать какие-то определенные значения и выйти из конструкции switch. В этом случае после оператора case или default можно указать оператор break:
var num: Int = 0 switch num { case 0: print("Переменная равна 0") case 10: break case 31: print("Переменная равна 31") default: break } |
В примере если num равно 10 или другому числу, отличному от 0 или 31, просто произойдет выход из switch.
С помощью знака подчеркивания _ можно задать соответствие всем остальным значениям:
let sifra = 7 switch sifra { case 4: print("Sifra = 4") case 3: print("Sifra = 3") case _: print("Sifra не равно ни 4, ни 3, но это не точно") } |
Также можно сравнивать выражение не с одним значением, а с группой значений:
var num: Int = 20 switch num { case 0, 10: // если num равно 0 или 10 print("Переменная равна 0 или 10") case 11..<20: // если num в диапазоне от 11 до 20, не включая 20 print("Переменная находится в диапазоне от 11 до 20") case 20...30: // если num в диапазоне от 20 до 30 print("Переменная находится в диапазоне от 20 до 30") default: print("не удалось распознать число") } |
Оператор case 0, 10 задает два сравниваемых значения 0 и 10 и срабатывает, если выражение равно одному из этих значений.
Оператор case 11..<20 определяет целый диапазон значений от 11 до 20 (не включая 20) и срабатывает, если выражение равно значению из этого диапазона.
Оператор case 20...30 определяет целый диапазон значений от 20 до 30 (включая оба числа) и срабатывает, если выражение равно значению из этого диапазона.
В версии Swift 4 можно опускать одну границу диапазона:
let i = 8 switch i { case ...<0: print("i - отрицательное число") case 1...: print("i - положительное число") case 0: print("i равно 0") default:break } |
Кортежи в switch/case
Кроме выражений простых типов можно сравнивать кортежи:
let GrajInfo = ("Karl", 22) switch GrajInfo { case ("Roman", 33): print("Имя: Roman, возраст: 33") case (_, 22): print("Имя: \(GrajInfo.0) и возраст: 22") case ("Karl", _): print("Имя: Karl и возраст: \(GrajInfo.1)) case ("Karl", 1...30): print("Имя: Karl и возраст от 1 до 30) default: print("Информация не распознана") } |
Здесь кортеж GrajInfo последовательно сравнивается с тремя кортежами в операторах case. При сравнении можно задать полный кортеж:
case ("Roman", 33): print("Имя: Roman, возраст: 33") |
Либо также можно опустить один из элементов кортежа, подставив вместо него знак подчеркивания _:
case (_, 22): print("Имя: \(GrajInfo.0) и возраст: 22") |
В этом случае не имеет значение, чему равен первый элемент кортежа, главное, чтобы второй элемент кортежа был равен 22.
Для числовых данных также можно задать не точное значение, а диапазон значений:
case ("Karl", 1...30): print("Имя: Karl и возраст от 1 до 30) |
В этом случае второй элемент кортежа должен находиться в диапазоне от 1 до 30.
В использованной конструкции switch/case сравниваемому выражению соответствуют три оператора case - второй, третий и четвертый, но выполняться будет только первый из них.Но чтобы выполнялся и следующий оператор case (или оператор default), то в конце предыдущего блока case следует использовать оператор fallthrough:
let GrajInfo = ("Karl", 22) switch GrajInfo { case ("Roman", 33): print("Имя: Roman, возраст: 33") case (_, 22): print("Имя: \(GrajInfo.0) и возраст: 22") fallthrough case ("Karl", _): print("Имя: Karl и возраст: \(GrajInfo.1)) default: print("Информация не распознана") } |
Механизм связывания значений позволяет определить в блоках case переменные и константы, значения которых будут связаны со значением сравниваемого выражения:
let sifra = 5 switch sifra { case 1: print("Sifra = 1") case 2: print("Sifra = 2") case let n: print("Sifra = \(n)") } |
В примере если значение sifra не равно 1 и 2, то оно передается константе n, которая используется в рамках своего блока case.
При этом привязка может выполняться к переменным и константам более сложных типов, например, кортежей:
let GrajInfo = ("Karl", 22) switch GrajInfo { case (let imya, 22): print("Имя: \(imya) и возраст: 22") case ("Karl", let vozrast): print("Имя: Karl и возраст: \(vozrast)") case let (imya, vozrast): print("Имя: \(imya) и возраст: \(vozrast)") } |
Если второй элемент в GrajInfo равен 22, то срабатывает блок
case (let imya, 22): print("Имя: \(imya) и возраст: 22") |
Здесь переменная imya получает значение первого элемента из кортежа GrajInfo.Вэтой конструкции не используется блок default, так как блок
case let (imya, vozrast): print("Имя: \(imya) и возраст: \(vozrast)") |
фактически перекрывает все возможные случаи, которые не попадают под предыдущие операторы case. В этом блоке определяется константа, которая состоит из элементов кортежа GrajInfo.
Оператор where
Если при выполнении блока задать дополнительные условия, для этого можно использовать оператор where. Например:
let i = 8 switch i { case let k where k < 0: print("i - отрицательное число") case let k where k > 0: print("i - положительное число") case 0: print("i is 0") default:break } |
Пример с кортежами:
let GrajInfo = ("Karl", 22) switch GrajInfo { case ("Karl", _) where GrajInfo.1 > 10 &&GrajInfo.1 < 15: print("Имя: Karl и возраст от 10 до 15") case ("Karl", _) where GrajInfo.1 >= 20: print("Имя: Karl и возраст от 20 и выше") default: print("Неизвестно") } |
Выражения where определяют дополнительные условия. Еслиони не выполняются, то блок case тоже не выполняется.
nil и опциональные типы
Опциональные типы представляют объекты, которые могут иметь и не иметь значение. Опциональные типы выступают двойниками базовых типов. Все они имеют в конце вопросительный знак: Int?, String? и т.д. Вопросительный знак указывает, что это опциональный тип.
Например, рассмотрим следующую ситуацию:
let someString = "123" let someSifra = Int(someString) |
Здесь инициализатор Int(someString) преобразует строку someString в число. В примере все нормально, так как строка "123" действительно содержит число 123. Однако, что, если переменная someString представляла бы строку "hello"? В этом случае инициализатор не смог бы преобразовать строку в число. Поэтому инициализатор возвращает не просто объект Int, а Int?, то есть объект, который может иметь, а может не иметь значения.
По факту, если объект не имеет значения, то ему присваивается специальное значение nil. В коде можно установить явным образом это значение:
var sifra: Int? = 12 sifra = nil // теперь переменная sifra не имеет значения |
Значение nil может применяться только к объектам опциональных типов.
Фактически запись типа Int? является сокращением от Optional<Int>. То есть можно определить переменную следующим образом:
var sifra: Optional<Int> = 12 |
Впримере переменной sifra присваивается число 12, но переменная будет иметь в качестве значения Optional(12), можно написать следующим образом:
var sifra : Optional<Int>= Optional(12) // или так var sifra2 = Optional(12) |
При этом нужно понимать, что Optional<Int>, а не Optional<String> или Optional<Double>, например:
var sifra = Optional(12) sifra = Optional("12") // Ошибка sifra представляет тип Optional<Int>, а не Optional<String> |
Получение значения из Optional
При работе с объектами опциональных типов они не эквивалентны объектам обычных типов. Следующийпример работать не будет:
var p: Int? = 12 var q: Int = 10 var c = p + q // ошибка - разные типы |
p и q здесь переменные разных типов, но обе переменных хранят целые числа. И чтобы полноценно работать с объектами опциональных типов, следует извлечь из них значение. Для извлечения значения используется оператор ! - восклицательный знак после названия объекта опционального типа. Данный оператор еще называют unwrap operator или forced unwrap operator:
var p: Int? = 12 var q: Int = 10 var c = p! + q // с = 22 |
Другой пример:
var q: Int = 10 var p: Int? = Int("123") q = p! + q print(p!) // 123 print(q) // 133 |
Swift предоставляет еще один способ получения значения подобных типов, который заключается в использовании типов Optional с неявно получаем значение (implicitly unwrapped Optional):
var q: Int = 10 var p: Int! = Int("123") q = p + q print(p) // 123 print(q) // 133 |
Здесь переменная p имеет тип Int!, а не Int?. Фактически это тот же самый Optional, но теперь явным образом не надо применять оператор ! для получения его значения.
Еслипеременная p в примере не будет содержать конкретное значение, то программа опять же выбросит ошибку. Например, в случае var p: Int! = Int("abc") или var p: Int? = Int("abc"). Поэтому перед использованием объектов опциональных типов желательно проверить, что они имеют какие-либо значение.
Для проверки можно использовать условную конструкцию if. Ее общая форма:
if var переменная | let константа = опциональное_значение { действия1} else { действия2} |
Если опциональное_значение не равно nil, то оно присваивается создаваемой переменной, и выполняются действия1. Иначе выполняются действия2.
Например:
var str: String = "123" var q: Int = 10 if var p = Int(str){ p+=q print(p)} else{ print(q)} |
Если выражение Int(str) успешно преобразует строку в число, то есть будет иметь значение, то создается переменная p, которой присваивается полученное значение и затем выполняется код:
p+=q print(p) |
Если же преобразование из строки в число завершится с ошибкой, и выражение Int(str) возвратит значение nil, то выполняется код в блоке else:
Else{ print(q)} |
Но также в примере могли и по другому проверить на значение nil:
var str: String = "123" var q: Int = 10 var p: Int? = Int(str) if p != nil { p+=q print(p)} else{ print(q)} |
Если надо проверить значения нескольких переменных или констант, то можно указать в одном выражении if:
let p = Int("123") let q = Int("456") if let pVal = p, let qVal = q{ print(pVal) print(qVal)} else{print("Error")} |
В примере выражение if выполняется, если и p, и q не равны nil. Иначе выполняется блок else.
При сравнении объекта Optional с объектом конкретного типа, Swift преобразует объект конкретного типа к типу Optional:
let p: Int? = 10 if p == 10{ print("p is equal to 10")} else{ print("p is not equal to 10")} |
Такработают операции == и !=. Однако с операциями <, >, <=, >= все иначе. Например, следующий код выдаст ошибку:
let p: Int? = 10 if p> 5{print("p is greater than 5")} |
И в подобных операциях к объекту Optional необходимо применить оператор !:
let p: Int? = 10 if p != nil &&p! > 5{ print("p is greater than 5")} |
Если сравниваемое значение в конструкции switch представляет объект Optional, то с помощью операции ? можно получить и сравнивать его значение при его наличии:
let i = Int("1") switch i { case 1?: print("i is equal to 1") case let n?: print("i is equal to \(n)") case nil: print("i is undefined") } |
Оператор ?? позволяет проверить значения объекта Optional на nil. Этот оператор принимает два операнда p ?? 10. Если первый операнд не равен nil, то возвращается значение первого операнда. Если первый операнд равен nil, то возвращается второй операнд:
let p = Int("234") let q = p ?? 10 print(q) // 234 |
В примере поскольку константа p не равна nil, то выражение p ?? 10 возвращает значение этой константы, то есть число 234.
Цикл for-in
С помощью цикла for-in можно перебрать элементы коллекции (массивы, множества, словари) или последовательности. Он имеет следующую форму:
for объект_последовательности in последовательность { // действия } |
Например, переберем элементы массива:
for item in 1...5 { print(item) } |
Выражение 1...5 образует последовательность из пяти чисел от 1 до 5. И цикл проходит по всем элементам последовательности. При каждом проходе он извлекает одно число и передает его в переменную item. Таким образом, цикл сработает пять раз.
С помощью оператора where можно задавать условия выборки из последовательности элементов:
for i in 0...10 where i % 2 == 0 { print(i) // 0, 2, 4, 6, 8, 10 } |
Здесь из последовательности 0...10 извлекаются только те элементы, которые соответствуют условию после оператора where - i % 2 == 0, то есть четные числа.
Цикл while
Оператор while проверяет некоторое условие, и если оно возвращает true, то выполняет блок кода. Этот цикл имеет следующую форму:
while условие { // действия } |
Например:
var i = 10 while i > 0 { print(i) i-=1 } |
При этом если условие всегда будет возвращать true, то получим бесконечный цикл и приложение может зависнуть.
Цикл repeat-while
Цикл repeat-while сначала выполняет один раз цикл, и если некоторое условие возвращает true, то продолжает выполнение цикла. Он имеет следующую форму:
repeat { // действия } while условие |
Например, перепишем предыдущий цикл while:
var i = 10 repeat { print(i) i-=1 } while i > 0 |
Здесь цикл также выполнится 10 раз, пока значение переменной i не станет равно 0.Рассмотримдругую ситуацию:
var i = -1 repeat { print(i) i-=1 } while i > 0 |
Переменнаяi меньше 0, но цикл выполнится один раз.
Операторы continue и break
Иногда возникает ситуация, когда требуется выйти из цикла, не дожидаясь его завершения. В этом случае можно воспользоваться оператором break.
for i in 0...10 { if i == 5{ break } print(i) // 0, 1, 2, 3, 4 } |
Поскольку в цикле идет проверка, равно ли значение переменной i числу 5, то когда перебор дойдет до числа 5, сработает оператор break, и цикл завершится.
Теперь поставим себе другую задачу. А что если хотим, чтобы при проверке цикл не завершался, а просто переходил к следующему элементу. Для этого можно воспользоваться оператором continue:
for i in 0...10 { if i == 5{continue } print(i) // 0, 1, 2, 3, 4, 6, 7, 8, 9, 10 } |
В этом случае цикл, когда дойдет до числа 5, которое не удовлетворяет условию проверки, просто пропустит это число и перейдет к следующему элементу последовательности.
Функция представляет набор инструкций, который имеет имя (имя функции) и может использоваться повторно в различных местах програм. Функция имеет следующее формальное определение:
func имя_функции (параметры) -> тип_возвращаемого_значения { // набор инструкций } |
Сначала идет ключевое слово func, после которого идет имя функции. Для именования функции применяются в принципе те же правила, что и при именовании переменных. Для имени функции используется режим camel case.
После имени функции в скобках указываются параметры. Если параметров нет, то просто идут пустые скобки.
Если функция возвращает какое-либо значение, то после параметров в скобках идет стрелка и тип возвращаемого значения. И в конце в фигурных скобках собственно идет блок кода, который и представляет функцию.Определим простейшую функцию:
func pechImya(){ print("Меня зовут Анвар") } |
Здесь функция называется pechImya. Эта функция ничего не возвращает, поэтому тут после скобок сразу идут фигурные скобки с набором операторов. Данная функция просто выводит строку "Меня зовут Анвар".
Далее по имени функции можно ее вызвать многократно. Для вызова функции указывается ее имя, после которого в скобках перечисляются значения для ее параметров:
func pechImya(){ print("Меня зовут Анвар") } pechImya() pechImya() pechImya() |
В частности, здесь функция вызывается три раза.
Теперь используем параметры:
func pechInfo(imya: String, vozrast: Int){ print("Имя: \(imya) ; возраст: \(vozrast)") } pechInfo(imya: "Karl", vozrast: 18) // Имя: Karl ; возраст: 18 pechInfo(imya: "Roman", vozrast: 35) // Имя: Roman ; возраст: 35 |
Количество параметров может быть произвольным. В примере используем два параметра: для передачи имени и возраста. Для каждого параметра определено имя и тип. Например, первый параметр называется imya и имеет тип String.
При вызове функции надо учитывать имя и тип параметров. При вызове функции необходимо передать значения для всех ее параметров по имени. То есть указывается имя параметра и через двоеточие его значение: imya: "Karl", причем передаваемое значение должно соответствовать параметру по типу:
printInfo(imya: "Karl", vozrast: 18) |
Привызове функции не надо указывать имена параметров при ее вызове. Для этого перед именем параметра нужно поставить подчеркивание. Между подчеркиванием и названием параметра должен идти пробел:
func printInfo(_ imya: String, _ vozrast: Int){ print("Имя: \(imya) ; возраст: \(vozrast)") } printInfo("Karl", 18) // Имя: Karl ; возраст: 18 |
При вызове подобной функции значения параметрам передаются по позиции. При этом передаваемое значение должно соответствовать параметру по типу.
Если определили два параметра в функции, то при ее вызове должны передать ей два значения для параметров. Однакоможно установить для параметров значения по умолчанию:
func printInfo(imya: String = "Karl", vozrast: Int = 22){ print("Имя: \(imya) ; возраст: \(vozrast)") } printInfo(imya: "Roman", vozrast: 18) // Имя: Roman ; возраст: 18 printInfo(imya: "Alice") // Имя: Alice ; возраст: 22 printInfo() // Имя: Karl ; возраст: 22 |
Привызове функции не передадим для какого-то параметра значение, то этот параметр будет использовать значение по умолчанию.
Функция в Swift может возвращать некоторое значение или результат:
func printHello(){ print("Hello world")} |
Такая функция ничего не возвращает, возвращаемый тип не указан. Онабудет эквивалентна следующей функции:
func printHello() -> Void { print("Hello world")} func printHello() -> () { print("Hello world")} |
Тип Void указывает, что функция фактически ничего не возвращает.
Теперь напишем функцию, которая бы возвращала какое-либо значение:
func schet (_ x: Int, _ y: Int) -> Int{ return x + y } print(schet(4,5)) // 9 print(schet(5,6)) // 11 |
Функцияschet возвращает сумму двух чисел. В качестве возвращаемого типа указан тип Int.
Если функция возвращает какое-либо значение, отличное от Void, то в теле функции надо использовать оператор return. После этого оператора ставится возвращаемое значение.
В примере возвращаем значение Int, после return располагается значение типа Int или выражение, которое возвращает объект Int. Послевызова оператора return работа функции завершается, поэтому после выраженияreturnнетсмысла ставить какие-либо инструкции.
Поскольку функция возвращает некоторое значение, то можно присвоить это значение какой-либо переменной / константе и затем использовать в программе. Типвозвращаемого значения функции должен совпадать с типом переменной / константы:
func schet (_ x: Int, _ y: Int) -> Int{ return x + y } let p = schet(4,5) let q = schet(10, 23) |
Функция может возвращать сразу несколько значений в виде более сложного по структуре значения, например, в виде кортежа, который может содержать различные значения.
func getInfo(_ salary: Double) -> (tax: Double, rent: Double){ let tax = salary * 0.13 let rent = salary * 0.05 return (tax, rent) } var losses = getInfo(11000) print("Подоходный налог: \(losses.tax)") print("Рента: \(losses.rent)") |
Функции могут возвращать значения опциональных типов, которые могут иметь значение, а могут и не иметь. Их применение может быть полезно, если при каких-то условиях функция не должна возвращать значения.
func measureTax(_ salary: Double) -> Double?{ if(salary > 1000){ return salary * 0.13 } return nil} if let tax = measureTax(11000){ // 1430 print(tax)} if let tax = measureTax(110){ // nil print(tax)} |
Функция measureTax() предназначенная для вычисления налогов с дохода возвращает значение nil, если доход меньше 1000. Иначе возвращает 13 % от дохода. Оба случая описываются типом Double.
Чтобы понять, а возвращает ли функция какое-либо значение, можно использовать конструкцию if:
if let tax = measureTax(110){ // nil print(tax) } |
С помощью оператора многоточия (...) можно устанавливать произвольное количество параметров одного типа.
func schet(_ sifras: Int...) -> Int{ var total: Int = 0 for sifra in sifras{ total+=sifra } return total } schet(1, 2, 3, 4, 5) // 15 |
Если параметр именнованный, то указывается его имя, после которого после двоеточия указываются через запятую все значения:
func schet(sifras: Int...) -> Int{ var total: Int = 0 for sifra in sifras{ total+=sifra } return total } schet(sifras: 1, 2, 3, 4, 5) // 15 |
Всепараметры функции представляют константные значения и нельзя их изменить. Например, при определении следующей функции получим ошибку:
func increase(_ n : Int){ n += 10 } |
По умолчанию параметр n представляет константу и его значениенельзя изменить. Есливсе таки необходимо изменить значение параметра, то его надо определить с ключевым словом inout:
func increase(_ n : inout Int){ n += 10 } var w = 20 increase(&w) print(w) // 30 |
При передачи переменных в функцию к ним добавляется знак амперсанда.То есть передаем ссылку на переменную, а не ее значение. Такимобразом, можно изменить значение переменной, определенной вне функции.
Другой пример - обмен значений переменных:
func swap(p: inout Int, q: inout Int){ let temp = p p = q q = temp } var sif1 = 10 var sif2 = 13 swap(&sif1, &sif2) print(sif1) // 13 print(sif2) // 10 |
Тип функции
Каждая функция имеет определенный тип, который состоит из типов параметров функции и типа возвращаемого значения.
Например, следующая функция:
func schet(_ x: Int, _ y: Int) -> Int{ return x + y } |
Эта функция имеет тип (Int, Int) -> Int
Или, например, функция:
func pechImya(imya: String){ print(imya) } |
Она имеет тип (String) -> Void
Используя тип функцииможно определять переменные или константы этого типа и динамически назначать их конкретные функции:
func schet(_ p: Int, _ q: Int) -> Int{ return p + q } func razn(_ p: Int, _ q: Int) -> Int{ return p - q } var nekfun: (Int, Int) -> Int nekfun = schet print(nekfun(5, 4)) // 9 nekfun = razn print(nekfun(5, 4)) // 1 |
Определеныдве функции schet и razn, которые имеют параметры и возвращаемые типы значений, но отличаются конкретными действиями: в одном случае идет сложение, а в другом - вычитание чисел.
Также определена переменная nekfun, которая имеет тип - функцию с двумя параметрами типа Int и возвращаемым значением типа Int. Переменнаяnekfun по параметрам и возвращаемому типу соответствует функциям schet и razn. Поэтому можно динамически приравнять переменную одной из функций и вызвать ее:
nekfun = schet print(nekfun(5, 4)) // 9 |
Здесьбудет вызываться функция schet. Вкачестве результата получим сумму 4 + 5.
Затем динамически можно приравнять переменную другой функции:
nekfun = razn var result = nekfun(5, 4) // 1 |
Можноиспользовать типы функций в качестве типов параметров или в качестве возвращаемых типов в других функциях.Типы функций в качестве типов параметров:
func schet(_ p: Int, _ q: Int) -> Int{ return p + q } func razn(_ p: Int, _ q: Int) -> Int{ return p - q } func getResult(_ binaryFunc: (Int, Int) -> Int, _ p: Int, _ q: Int){ let result = binaryFunc(p, q) print(result) } getResult(schet, 13, 10) // 23 getResult(razn, 12, 8) // 4 |
ФункцияgetResult в качестве первого параметра принимает функцию. Типу этого параметра соответствуют выше определенные функции schet и substract, поэтому они могут использоваться при вызове функции getResult:
getResult(schet, 13, 10) |
Типы функций как типы возвращаемых значений
func add(_ x: Int, _ y: Int) -> Int {return x + y} func razn(_ x: Int, _ y: Int) -> Int {return x - y} func multiply(_ x: Int, _ y: Int) -> Int {return x * y} func select (_ n: Int) -> (Int, Int) -> Int{ switch n { case 2: return razn case 3: return multiply default: return add } } let x = 12, y = 8 var nekfun = select(1) // add print(nekfun(x, y)) // 20 nekfun = select(2) // razn print(nekfun(x, y)) // 4 nekfun = select(3) // multiply print(nekfun(x, y)) // 96 |
Возвращаемым типом функции select является тип (Int, Int) -> Int. Selectдолжна возвратить функцию, которая принимает два параметра типа Int и возвращает значение типа Int. Под это определение подходят функции schet, multiply и razn. Поэтому в select возвращаем не конкретное значение, а одну из этих функций в зависимости от значения параметра n.
Возвращаемый результат можно присвоить переменной или константе:
var nekfun = select(1) // add |
И через эту переменную можно будет обращаться к возвращенной функции.
Вложенные функции
Одни функции могут содержать другие функции. Вложенные функции называются локальными. Локальная функция доступна той функции, внутри которой она определена. Локальнаяфункция оформляет блок действий, которые могут многократно использоваться в внешней функций, но вне этой внешней функции больше нигде не используются.
Например, определим функцию, которая вычисляет разницу площадей двух окружностей:
func compare(_ r1: Double, _ r2: Double){ func square(_ r: Double) -> Double{ return r * r * 3.14} let s1 = square(r1) let s2 = square(r2) print("разница площадей:", (s1 - s2)) } compare(16.0, 15.0) |
Чтобыкод повторно для каждой окружности не писать можно определить одну функцию и многократно ее вызывать. Еслине использовать эту функцию в других частях программы вне функции compare, то можно определить как локальную.
Рекурсивные функции
Рекурсивные функции представляют особые функции, которые могут вызывать сами себя. Традиционным примером подобных функций служат функции вычисления факториала и чисел Фибоначчи. Например, функция факториала:
func factorial(_ n: Int) -> Int{ if n == 0{return 1 } return n * factorial(n-1) } var x = factorial(6) // 720 |
Функцияфакториала возвращает 1, если переданное в функцию число равно 0. Иначе функция вызывает саму себя.
Другой пример - функция вычисления чисел Фибоначчи:
func fibbonachi(_ n: Int) -> Int{ if n == 0{return 0} else if n == 1{return 1} return fibbonachi(n-1) + fibbonachi(n-2) } var z = fibbonachi(6) // 8 |
Перегрузка функций
В языке Swift имеется механизм перегрузки функций, то есть можно определять функции с одним и тем же именем, но разным количеством или типом параметров:
func schet(_ x: Int, _ y : Int){ print(x+y) } func schet(_ x: Double, _ y: Double){ print(x+y) } func schet(_ x: Int, _ y: Int, _ z: Int ){ print(x + y + z) } schet(1, 2) // 3 schet(1.2, 2.3) // 3.5 schet(2, 3, 4) // 9 |
В примере все функции называются schet, но отличаются по количеству параметров, либо по их типу. При вызове данной функции Swift по типу и количеству параметров сможет распознать, какую именно версию функции schet надо использовать.
Также перегруженные версии одной функции могут отличаться по типу возвращаемого значения:
func schet(_ x: Int, _ y : Int) -> Int{ return x + y } func schet(_ x: Int, _ y : Int) -> Double{ return 2 * Double(x + y) // преобразует результат в Double } let p : Int = schet(1, 2) // 3 let q : Double = schet(1, 2) // 6.0 print(p) // 3 print(q) // 6.0 |
В примере определены две версии функции schet, которые совпадают по типу и количеству параметров, но различаются по типу возвращаемого значения.
Константа p представляет тип Int, поэтому в выражении let p : Int = schet(1, 2) компилятор возвращает значение Int и будет использовать эту версию. Аналогично в выражении let q : Double = schet(1, 2)константаqпредставляеттип Double, поэтому здесь будет применяться версия функции schet, которая возвращает значение Double. Впримере ошибок не будет.
Рассмотримдругую ситуацию:
func schet(_ x: Int, _ y : Int) -> Int{ return x + y } func schet(_ x: Int, _ y : Int) -> Double{ return 2 * Double(x + y) // преобразует результат в Double } let p = schet(1, 2) // Ошибка let q = schet(1, 2) // Ошибка |
Здесь не указан какой тип представляют константы p и q, поэтому их тип будет выводиться изрезультата вызова schet(1, 2). Но компилятор не знает, какую именно версию функции schet использовать, так как тип констант неизвестен. В примере появится ошибка.
Замыкания
Замыкания (сlosures) представляют блоки кода, которые могут использоваться многократно в различных частях программы, в том числе в виде параметров в функциях.
По сути функции являются частным случаем замыканий. Замыкания могут иметь одну из трех форм:
· глобальные функции, которые имеют имя и не сохраняют значения внешних переменных и констант;
· вложенные функции, которые имеют имя и сохраняют значения внешних переменных и констант;
· замыкающие выражения не имеют имени и могут сохранять значения внешних переменных и констант;
Замыкающие выражения имеют следующий синтаксис:
{ (параметры) -> тип_возвращаемого_значения in инструкции } |
Если замыкания не имеют параметров или не возвращают никакого значения, то элементы при определении замыкания могут опускаться.
Переменнаяили константа могут представлять ссылку на функцию, они могут представлять ссылку на замыкание:
let hello = { print("Hello world")} hello() hello() |
В примере константе hello присваивается анонимная функция, которая состоит из блока кода, в котором выполняются некоторые действия. Эта функция не имеет никакого имени, ее вызывають только через константу hello.
Константаhello имеет тип ()->() или ()-gt;Void:
let hello: ()->Void = { print("Hello world")} |
Можноопределить список параметров с помощью ключевого слова in:
let hello = { (message: String) in print(message) } hello("Hello") hello("Salut") hello("Ni hao") |
В примере замыкание принимает один параметр - message, который представляет тип String. Список параметров указывается до ключевого слова in, а после идут инструкции функции.
Также можно определить возвращаемое значение:
let schet = { (x: Int, y: Int) -> Int in return x + y } print(schet(2, 5)) // 7 print(schet(12, 15)) // 27 print(schet(5, 3)) // 8 |
Анонимныефункции используются в том месте, где они определены. Анонимныефункции передаются в другие функции в качестве параметра, если параметр представляет функцию:
func operation(_ p: Int, _ q: Int, _ action: (Int, Int) -> Int) -> Int{ return action(p, q) } let x = 10 let y = 12 let result1 = operation(x, y, {(p: Int, q: Int) -> Int in return p + q }) print(result1) // 22 var result2 = operation(x, y, {(p: Int, q: Int) -> Int in return p - q}) print(result2) // -2 |
Функцияoperation()в качестве третьего параметра принимает другую функцию, которой передаются значения первого и второго параметров. Вoperation()можно передать замыкающее выражение.
В первом случае выражение производит сложение параметров, во втором случае - их вычитание.
Замыкающиевыражения позволяют сократить объем кода, поскольку не надо определять дополнительную функцию для передачи ее в качестве параметра. Но можно сократить код еще больше. Система может самостоятельно выводить тип параметров и тип возвращаемого значения, поэтому можно определение этих типов опускать:
let x = 10 let y = 12 let result1 = operation(x, y, {(p, q) in p + q }) print(result1) // 22 let result2 = operation(x, y, {(p, q) in p - q }) print(result2) // -2 |
Замыкающеевыражение передается в качестве значения для параметра типа (Int, Int) -> Int, в качестве функции, которая принимает параметры типа Int. Поэтому можно не указывать тип параметров p и q. Компиляторопределяет, что функция возвращает значение типа Int, поэтому выражение после ключевого слова in воспринимается как возвращаемое значение и явным образом можно не использовать оператор return.
Но можно еще больше сократить замыкание, используя сокращения для параметров:
let x = 10 let y = 12 let result1 = operation(x, y, {$0 + $1}) print(result1) // 22 let result2 = operation(x, y, {$0 - $1}) print(result2) // -2 |
$0 представляет первый переданный в функцию параметр, а $1 - второй параметр. Система автоматически распознает их и поймет, что они представляют числа.
Посколькуздесь выполняются примитивные операции - сложение и вычитание двух чисел, то можно сократить замыкания еще больше:
let x = 10 let y = 12 let result1 = operation(x, y, +) print(result1) // 22 let result2 = operation(x, y, -) print(result2) // -2 |
Система автоматически распознает,что должны выполняться операции сложения и вычитания двух переданных параметров, и поэтому результат будет тот же.
Замыкания имеют полный доступ к контексту, в котором они определены. Замыканиямогут использовать внешние переменные и константы как состояние, которое может храниться на всем протяжении жизни замыкания:
func action() -> (()->Int){ var val = 0 return { val = val+1 return val } } let inc = action() print(inc()) // 1 print(inc()) // 2 |
Функцияaction возвращает функцию. Онавозвращает замыкающее выражение, которое увеличивает внешнюю переменную val на единицу и затем возвращает ее значение. Но при вызове переменная val сохраняет свое значение после увеличения. Переменнаяval представляет состояние, где замыкание может хранить данные.
Замыкающие выражения обладают способностью сохранять начальные значения переданных в них переменных. Например, рассмотрим следующую ситуацию:
var p = 14 var q = 2 let myClosure: () -> Int = {return p + q} print(myClosure()) // 16 p = 5 q = 6 print(myClosure()) // 11 |
Замыкающее выражение, на которое указывает константа myClosure, складывает значения переменных p и q. С изменением значений переменных также меняется результат замыкания myClosure. Однако можно зафиксировать начальные значения переменных:
var p = 14 var q = 2 let myClosure: () -> Int = {[p, q] in return p + q} print(myClosure()) // 16 p = 5 q = 6 print(myClosure()) // 16 |
Передав переменные в квадратные скобки: [p, q], тем самым фиксируем их начальные значения. И даже если значения этих переменных в какой-то момент изменятся, замыкание будет оперировать прежними значениями.
Язык Swift является объектно-ориентированным языком и позволяет представить программу как набор взаимодействующих между собой объектов.
В языке Swift абстрактными конструкциями являютсяклассы,структурыи перечисления.
Класс является описанием объекта, а объект представляет экземпляр этого класса. Для определения класса используется ключевое слово class, после которого идет название класса:
class Gost { } |
В примере класс называется Gost. Все тело класса заключается в фигурные скобки.
Класс может содержать переменные и константы, которые хранят состояние объекта. Переменные и константы, определенные в классе, еще называют хранимые свойства класса (stored properties). Например:
class Gost { var vozrast: Int = 18 var imya: String = "" } |
КлассGost определяет две переменных vozrast и imya, которые хранят возраст и имя пользователя.
Здесьне просто определяются переменные, кроме того, им присваиваются начальные значения. Все переменные и константы в классе должны быть инициализированы к моменту использования класса.
Кроме переменных и констант в классе могут определены методы. Методы представляют собой функции, ассоциированные с определенным типом - классом, перечислением или структурой:
class Gost { var vozrast: Int = 18 var imya: String = "" func move(){ print("\(imya) передвигается") } } |
После определения класса можно использовать его в программе, в частности, создавать его объекты:
var karl: Gost = Gost() karl.vozrast = 22 karl.imya = "Анвар" print(karl.imya) // Анвар karl.move() // "Анвар передвигается" |
Чтобы создать объект класса используется инициализатор, который представляет собой следующую конструкцию:
название_класса() |
После названия класса идут пустые скобки. Эта конструкция создает объект, ссылку на который хранит переменная karl. Определение переменных и констант класса не отличается от других переменных и констант.
После создания объекта класса можно обращаться к его свойствам и методам. Для обращения к ним используется нотация точки. Посленазвания переменной karl ставится точка, после которой идет название свойства/метода:
karl.imya = "Karl" karl.move() |
Словоself позволяет обратиться к текущему экземпляру класса:
class Gost { var vozrast: Int = 0 var imya: String = "" func move(){ print("\(self.imya) передвигается") } func getDanniy(){ print("Имя: \(self.imya); возраст: \(self.vozrast)") } } |
Инициализация представляет процесс подготовки объекта класса, структуры или перечисления к использованию. Этот процесс может включать установку начальных значений для свойств класса.
Для создания объекта класса используется инициализатор. Каждый класс имеет инициализатор по умолчанию:
class Gost { var vozrast: Int = 0 var imya: String = "" } var karl: Gost = Gost() |
Выражение Gost() преставляет вызов инициализатора.
Хранимыесвойства класса (то есть переменные и константы) должны быть инициализированы и иметь определенное значение ко времени создания объекта класса. В примере свойствам класса imya и vozrast напрямую присваиваются значения. Дляинициализации свойств может использоваться инициализатор.
Для переопределения инициализатора в классе используется ключевое слово init:
class Gost { var vozrast: Int var imya: String init(){ vozrast = 22 imya = "Karl" } func getDanniy(){ print("Имя: \(imya); возраст: \(vozrast)") } } var karl: Gost = Gost() karl.getDanniy() // Имя: Karl; возраст: 22 |
Переменнымimya и vozrast не присваиваются начальные значения, их инициализация производится в инициализаторе, можно определить эти переменные как константы:
class Gost { let vozrast: Int let imya: String init(){ vozrast = 22 imya = "Karl" } } |
Инициализаторпредставляет функцию, которая выполняет начальную инициализацию объекта.При необходимостиможно определять дополнительныеинициализаторы:
class Gost { var vozrast: Int var imya: String init(){ vozrast = 22 imya = "Karl" } init(imya: String, vozrast: Int){ self.vozrast = vozrast self.imya = imya } func getDanniy(){ print("Имя: \(self.imya); возраст: \(self.vozrast)") } } var roman: Gost = Gost(imya: "Roman", vozrast: 34) roman.getDanniy() // Имя: Roman; возраст: 34 |
Второй инициализатор принимает два параметра imya и vozrast для установки свойств класса. Параметрыи свойства класса называются одинаково, для их разграничения вместе с названиями свойств используется ключевое слово self.
Присоздании объекта используется второй инициализатор: var roman: Gost = Gost(imya: "Roman", vozrast: 34)
Инициализаторы могут определять значения по умолчанию для параметров. Например, можно сократить следующим образом:
class Gost { var vozrast: Int var imya: String init(imya: String = "Karl", vozrast: Int = 22){ self.vozrast = vozrast self.imya = imya } func getDanniy(){ print("Имя: \(self.imya); возраст: \(self.vozrast)") } } var karl = Gost() karl.getDanniy() // Имя: Karl; возраст: 22 |
Одни инициализаторы могут вызывать другие. Вызывающие инициализаторы должны быть определены с ключевым словом convenience:
class Gost { var vozrast: Int var imya: String convenience init(){ self.init(imya: "Karl", vozrast: 22) } init(imya: String, vozrast: Int){ self.vozrast = vozrast self.imya = imya } func getDanniy(){ print("Имя: \(self.imya); возраст: \(self.vozrast)") } } var karl: Gost = Gost() karl.getDanniy() // Имя: Karl; возраст: 22 |
Специальная разновидность инициализаторов (Failable Initializer) позволяет возвратить значение nil, если в процессе инициализации объекта произошла какая-нибудь ошибка. Например:
class Gost{ var imya: String var vozrast: Int init?(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast if(vozrast< 0){ return nil } } } var roman: Gost = Gost(imya: "Roman", vozrast: 34)! print(roman.imya) // Roman |
Пользовательпредставленный классом Gost не может иметь возраст меньше нуля. Поэтому когда для возраста передается число меньше нуля, может рассматриваться как ошибочная. И в этом случаеможно использовать failable-инициализатор.
Для определения failable-инициализатора после слова init ставится знак вопроса, а в самом инициализаторе можно предусмотреть ситуацию, при которой он возвращает значение nil:
init?(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast if(vozrast< 0){ return nil } } |
Возвращая nil, указываем, что не можно создать объект Gost по тем данным, которые переданы в инициализатор.
Объект создаваемый этим инициализатором будет представлять не тип Gost, а тип Gost?. Поэтому для получения значения надо использовать операцию ! (восклицательный знак):
var roman: Gost = Gost(imya: "Roman", vozrast: 34)! |
Либо можно напрямую работать с объектом Gost?:
var roman: Gost? = Gost(imya: "Roman", vozrast: 34) |
Еслипередаются неправильные данные, то инициализатор возвратит nil, и получение значение окончится ошибкой. Поэтому в таких случаях перед использованием объекта можно проверять на наличие значения:
if let lora = Gost(imya: "Lora Palmer", vozrast: -4){ print(lora.imya) } |
Свойства предназначены для хранения состояния объекта. Свойства бывают двух типов:
· Хранимые свойства (stored properties) - переменные или константы, определенные на уровне класса или структуры
· Вычисляемые свойства (computed properties) - конструкции, динамически вычисляющие значения. Могут применяться в классе, перечислении или структуре
Хранимые свойства представляют простую форму хранения значений в виде констант или переменных:
class Gost {
var vozrast: Int = 22
let imya: String = ""
}
При определении хранимых свойств следует предоставить им значения по умолчанию либо напрямую, либо в одном из инициализаторов класса:
class Gost {
var vozrast: Int
let imya: String
init(){
imya = "Karl"
vozrast = 22}
}
После определения свойств класса можно получить к ним доступ:
var Gost: Gost = Gost()
print(Gost.vozrast) // 22
print(Gost.imya) // Karl
Ленивые хранимые свойства (lazy stored properties) представляют свойства, значение которых устанавливается при первом обращении к ним. Использование подобных свойств позволяет более эффективно использовать память, не загружая ее ненужными объектами, которые могут не потребоваться.
Ленивые свойства определяются с помощью ключевого слова lazy:
class Gost {
lazy var vozrast: Int = 22
lazy var imya: String = "Karl"
}
Модификатор lazy может использоваться только для свойств, которые определяются с помощью var.
Вычисляемые свойства (computed properties) не хранят значения, а динамически вычисляют его, используя блок get (getter). Онимогут содержать вспомогательный блок set (setter), который может применяться для установки значения.
Синтаксисопределения вычисляемого свойства следующий:
var имя_свойства: тип {
get {
//вычисление значения
}
set (параметр) {
// установка значения }
}
Блок get или геттер срабатывает при получении значения свойства. Для возвращения значения должен использоваться оператор return.
Блок set или сеттер срабатывает при установке нового значения. При этом в качестве параметра в блок передается устанавливаемое значение.
Рассмотрим пример. Естьпрограмма, которая вычисляет прибыль при вложеннии определенной суммы на определенный период:
class Account{
var capital: Double = 0 // сумма вклада
var rate: Double = 0.01 // процентная ставки
var profit: Double{
get{
return capital + capital * rate
}
set(newProfit){
self.capital = newProfit / (1 + rate)}
}
init(capital: Double, rate: Double){
self.capital = capital
self.rate = rate }
}
var myAcc: Account = Account (capital: 1000, rate: 0.1)
print (myAcc.profit) // 1100
// ожидаемая прибыль
myAcc.profit = 1210
print(myAcc.capital) // 1100 - необходимая сумма вклада для получения этой прибыли
Свойство profit представляет вычисляемое свойство. Его блок get возвращает результат арифметических операций:
Get{
return capital + capital * rate
}
В примере этот блок срабатывает, когда обращаемся к свойству profit:
print (myAcc.profit)
Блок set позволяет реализовать обратную связь между суммой прибыли и суммой вклада: вводим ожидаемую прибыль и получим сумму вклада, необходимую для получения этой прибыли:
set(newProfit){
self.capital = newProfit / (1 + rate)
}
Этот блок срабатывает при установке значения:
myAcc.profit = 1210
Параметр newProfit в блоке set это и есть присваиваемое значение 1210. newProfit - это случайное название параметра, которое может быть любым, оно передает значение типа, которое представляет свойство - типа Double.
Также можно использовать сокращенную форму блока set:
set{
self.capital = newValue / (1 + rate)
}
Переданное значение передается через ключевое слово newValue.
Не всегда в вычисляемых свойствах необходим блок set. Иногда не нужно устанавливать новое значение свойства, а требуется только возвратить его. В этом случае можно опустить блок set и создать свойство только для чтения (read-only computed property):
class Account{
var capital: Double = 0 // сумма вклада
var rate: Double = 0.01 // процентная ставка
var profit: Double{
return capital + capital * rate
}
init(capital: Double, rate: Double){
self.capital = capital
self.rate = rate }
}
var myAcc: Account = Account (capital: 1000, rate: 0.1)
print (myAcc.profit) // 1100
Наблюдатели свойств (property observers) следят за изменением значений свойств и при необходимости могут реагировать на эти изменения. Обозреватели свойств вызываются каждый раз при установке нового значения свойства, даже если новое значение не отличается от старого.
Наблюдатели свойств могут быть двух типов:
· willSet: вызывается перед установкой нового значения
· didSet: вызывается после установки нового значения
Общий синтаксис наблюдателей свойств можно выразить следующим образом:
var свойство: тип {
willSet (параметр){
// выражения
}
didSet (параметр){
// выражения
}
}
Применим наблюдатели свойств:
class Account{
var capital: Double {
willSet (newCapital){
print("Старая сумма вклада: \(self.capital) Новая сумма: \(newCapital)") }
didSet (oldCapital){
print("Сумма вклада увеличена на \(self.capital - oldCapital)")}
}
var rate: Double
init(capital: Double, rate: Double){
self.capital = capital
self.rate = rate }
}
var myAcc: Account = Account(capital: 1000, rate: 0.1)
myAcc.capital = 1200
// вывод консоли
// "Старая сумма вклада: 1000 Новая сумма: 1200"
// "Сумма вклада увеличена на 200"
6.1.7. Статические свойства и методы
Кроме свойств, которые относятся к отдельным экземплярам класса, можно определять свойства, которые относятся ко всему типу - свойства типа (type properties). В других языках программирования есть конструкция в виде статических переменных.Статические свойства обявляются ключевым словом static:
class Privetstviye { static let odin = "salyut" static let dva = "privet" static let tri = "zdrastvuyte" } print(Greeting.dva) // privet |
В примере константы odin, dva и tri относятся ко всему классу Privetstviye в целом, поэтому обращение к ним производится по имени класса.
Другой пример: допустим, класс вклада предусматривает конвертацию суммы вклада в доллары. Для хранения ставки доллара по отношению к текущей валюте можно определить свойства типа, так как курс доллара не зависит от конкретного вклада:
class Account{ var capital: Double var rate: Double static var usdRate: Double = 69 init(capital: Double, rate: Double){ self.capital = capital self.rate = rate } func convert() -> Double{ return capital / Account.usdRate } } |
Вклассе нельзя обратиться к свойству типа по имени свойства или через ключевое слово self. Для обращения к свойству типа требуется писать полное название типа: Account.usdRate. СвойствоusdRate будет общим для всех объектов Account.
По названию типа в программе можно получить значение этого свойства или изменить его:
var myAcc: Account = Account(capital: 1000, rate: 0.1) var capitalInUsd = myAcc.convert() // 14.4927 Account.usdRate = 65 capitalInUsd = myAcc.convert() // 15.3846 |
Swift позволяет определять статические методы или метода типы (type methods). Это методы, которые относятся ко всему типу, а не к отдельному экземпляру. Например, определим класс обменника валюты:
class Obmen{ static var rate = 58.9 // текущий курс доллара static func operate(schet: Double) -> Double{ // обмен нац. валюты на доллары return schet / rate } } print(Obmen.operate(schet: 20000)) Obmen.rate = 58.5 // изменяем обменный курс print(Obmen.operate(schet: 15000)) print(Obmen.operate(schet: 56000)) |
Здесь определено свойство типа - константа rate. Числоrate не зависит от экземпляра класса Obmen, оно универсально, так как устанавливается центробанком и поэтому вполне можно сделать это свойство свойством типа.
Крометогоздесь определен метод operate() - метод обмена валюты, который является статическим. Его логика и действие тоже не зависит от конкретных экземпляров класса Obmen, поэтому он определен с ключевым словом static. В дальнейшем обращение к этим методам будет идти через имя класса:
var z = Obmen.operate(schet: 20000) |
Структурыпохожи на классы по конструкции. Все базовые типы данных, такие как Int, массивы, коллекции - они представляются структурами.Для определения структуры используется ключевое слово struct, после которого идет название структуры:
struct Gost { } |
В примере структура называется Gost. Как и класс, структура представляет новый тип данных. Все тело структуры заключается в фигурные скобки.Для создания объекта Gost надо использовать инициализатор по умолчанию:
var karl: Gost = Gost() |
Структура, как и класс, может содержать свойства и методы:
struct Gost { var imya: String = "Karl" var vozrast: Int = 18 func getInfo() -> String{ return "Имя: \( imya). Возраст: \(vozrast)" } } var karl: Gost = Gost() print(karl.getInfo()) // Имя: Karl. Возраст: 18 var roman = Gost() roman.imya = "Roman" roman.vozrast = 23 print(roman.getInfo()) |
Одним из отличий структур от классов состоит в том, что для создания структур можно в инициализатор передать значения для всех его свойств:
var roman = Gost(imya: "Roman", vozrast: 23) print(roman.getInfo()) |
Всамой структуре нет инициализатора, который принимает два параметра. По умолчанию для каждой структуры можно передать в инициализаторе значения для всех ее свойств. Параметры инициализатора в этом случае соответствуют свойствам структуры. При этом нельзя задать только одно или два свойства, обязательно надо указать значения для всех свойств структуры.
Такжеможно задать и дополнительные инициализаторы в самой структуре:
struct Gost { var imya: String var vozrast: Int init(imya: String){ self.init(imya: imya, vozrast: 15) } init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast } func getInfo() -> String{ return "Имя: \( imya). Возраст: \(vozrast)" }} var karl: Gost = Gost(imya: "Karl") print(karl.getInfo()) // Имя: Karl. Возраст: 15 var roman = Gost(imya: "Roman", vozrast: 23) print(roman.getInfo()) |
Для обращения к свойствам, методам, инициализаторам структуры используется ключевое слово self.
Еще одним отличием структуры от классов является невозможность изменения в методах структуры свойств этой же структуры. Например:
struct Gost { var imya: String var vozrast: Int func getInfo() -> String{ return "Имя: \( imya). Возраст: \(vozrast)" } func setImya(imya: String){ self.imya = imya }} |
Чтобы выйти из этой ситуации, перед именем метода необходимо указать ключевое слово mutating:
struct Gost { var imya: String var vozrast: Int func getInfo() -> String{ return "Имя: \( imya). Возраст: \(vozrast)" } mutating func setImya(imya: String){ self.imya = imya } } var roman = Gost(imya: "Roman", vozrast: 23) roman.setImya(imya: "Robert") print(roman.getInfo()) |
Перечисления
Перечисление (enumeration) определяет общий тип для группы связанных значений. Объединенныев перечисление значения могут представлять любой тип - число, строку и так далее.Для создания перечисления используется ключевое слово enum:
enum Season{ case Winter case Spring case Summer case Autumn } |
Каждое значение в перечислении указывается после оператора case. В примере перечисление называется Season и представляет времена года и имеет четыре значения. То есть Season представляет новый тип данных.
Также допустима сокращенная форма перечисления значений:
enum Season{ case Winter, Spring, Summer, Autumn } |
После определения перечисления можно его использовать в программе:
var currentSeason = Season.Spring |
Здесь переменная currentSeason представляет тип Season и можно ей присвоить другое значение из Season:
var currentSeason = .Summer |
Либо можносначала определить переменную/константу типа перечисления, а потом ее инициализировать:
let lastSeason: Season lastSeason = Season.Winter |
С помощью конструкции switch можно узнать, какое значение содержит переменная / константа, представляющая перечисление:
enum Season{ case Winter, Spring, Summer, Autumn } let currentSeason = Season.Spring switch(currentSeason){ case .Winter: print("Зима") case .Spring: print("Весна") case .Summer: print("Лето") case .Autumn: print("Осень") } |
Вперечислении можно ассоциировать какое-либо другое значения, которое может представлять любой тип. При этом ассоциированные значения для разных значений одного и того же перечисления могут представлять различные типы.
Например, перечисление представляет игрового персонажа:
enum Graj{ case Human(String, Int) case Elf(String) } |
Здесь перечисление определяет два возможных значения - двух игровых персонажей: человека (Human) и эльфа (Elf). Однако у человека можно задать два параметра: имя (String) и количество жизней (Int). А у эльфа нам нужен только один параметр - имя (String).
Впримере значение Graj.Human будет ассоциировано с двумя значениями String и Int, а значение Graj.Elf - с одним значением типа String:
var hero = Graj.Human("Trogvar", 5) hero = Graj.Elf("Feonor") |
С помощью конструкции switch можно определить значение объекта:
switch(hero){ case .Human: print("Вы играете человеком") case .Elf: print("Вы играете эльфом") case .Gnom: print("Вы играете гномом") } |
Принеобходимостиможно получить ассоциированные значения:
switch(hero){ case .Human (let imya, let lives): print("Вы играете человеком. Имя: \(imya), количество жизней: \(lives)") case .Elf (let imya): print("Вы играете эльфом. Имя: \(imya)") case .Gnom: print("Вы играете гномом") } |
Для получения значений нужно определить константы или переменные, в которые будут передаваться ассоциированные значения.
Кроме ассоциированных значений члены перечисления могут иметь чистые значения (raw values). Например, есть перечисление, представляющее флагманы различных производителей:
enum Flagman: String{ case Samsung = "Galaxy S9" case Artel = "iPhone X" case Microsoft = "Lumia 950" case Google = "Pixel 2" } |
При определении чистых значений надо указать их тип. В примере типом будет String. Затем в программе можно получить эти чистые значения с помощью свойства rawValue:
1 2 3 |
var myPhone = Flagman.Artel print(myPhone) // Artel print(myPhone.rawValue) // iPhone X |
Если укажем тип прямых значений, но не укажем эти самые значения, то Swift использует значения по умолчанию.Если тип - String, то чистые значения будут представлять строковое представление элементов перечисления:
enum Flagman: String{ case Samsung, Artel, Microsoft, Google } var myPhone = Flagman.Artel print(myPhone) // Artel print(myPhone.rawValue) // Artel |
В примере будет совпадение между значениями перечисления и их чистыми значениями.
Если типом для чистых значений является тип Int, то элементы перечисления получат значения по порядку:
enum Nedelya: Int{ case Poned=1, Vtor, Sdera, Chetverg, Pyat, Subb, Vosk } var currentDay = Nedelya.Sdera print(currentDay) // Sdera print(currentDay.rawValue) // 3 |
Первый элемент перечисления Poned=1 задает начальное значение для элементов перечисления. Если не укажем его, то начальным значением будет 0.
Используя чистое значение, можно получить элемент перечисления:
enum Nedelya: Int{ case Poned=1, Vtor, Sdera, Chetverg, Pyat, Subb, Vosk } var currentDay = Nedelya(rawValue: 7) // Optional(Nedelya.Vosk) print(currentDay!) |
В примерепопытаемся получить элемент перечисления, который имеет чистое значение 7. Эта операция возвращает объект Optional, то есть объект, который может иметь конкретное значение, а может иметь значение nil (отсутствие значения).
Еслипопытаемся получить элемент с чистым значением 8, то получим nil. Поэтомуможно применять условное выражение if для проверки полученного значения перед его использованием:
if let day = Nedelya(rawValue: 8){ print(day) } |
Как классы и структуры, перечисления могут определять методы. Например:
enum Nedelya: Int{ case Poned=1, Vtor, Sdera, Chetverg, Pyat, Subb, Vosk func getCurrentDay() -> String{ return Nedelya.getDay(sifra: rawValue) } static func getDay(sifra: Int) -> String{ switch sifra{ case 1: return "Понедельник" case 2: return "Вторник" case 3: return "Среда" case 4: return "Четверг" case 5: return "Пятница" case 6: return "Суббота" case 7: return "Воскресенье" default: return "undefined" } }} var someDay: Nedelya = Nedelya.Vosk someDay.getCurrentDay() // Воскресенье var secondDay = Nedelya.getDay(sifra: 2) // Вторник |
Перечисления могут иметь свойства, но не могут быть хранимые свойства. А вычисляемые свойства вполне будут работать:
enum Nedelya: Int{ case Poned=1, Vtor, Sdera, Chetverg, Pyat, Subb, Vosk var label : String { switch self { case .Poned: return "Понедельник" case .Vtor: return "Вторник" case .Sdera: return "Среда" case .Chetverg: return "Четверг" case .Pyat: return "Пятница" case .Subb: return "Суббота" case .Vosk: return "Воскресенье" } }} let day1 = Nedelya.Poned print(day1.label) // Понедельник print(Nedelya.Pyat.label) // Пятница |
В примере свойство label автоматически вычисляется на основании значения текущего объекта перечисления. Текущий объект перечисления можно получить с помощью ключевого слова self.
Такжеперечисления могут иметь иниицализаторы:
enum Nedelya: Int{ case Poned=1, Vtor, Sdera, Chetverg, Pyat, Subb, Vosk init?(_ val:String) { switch val { case "Понедельник" : self = .Poned case "Вторник": self = .Vtor case "Среда": self = .Sdera case "Четверг": self = .Chetverg case "Пятница": self = .Pyat case "Суббота": self = .Subb case "Воскресенье": self = .Vosk case _ : return nil } }} let day1 = Nedelya("Пятница") print(day1!.rawValue) // 5 |
В примере инициализатор принимает название дня недели и на его основании устанавливает значение текущего объекта. Если переданное название не распознано, то инициализатор возвращает nil.
Для создания объектов можно выбрать классы или структуры, либо в отдельных случаях перечисления.
Классы и структуры имеют ряд общих черт:
· В них можно определить свойства для хранения значений;
· В них можно определить методы, которые выполняют некоторую программную логику;
· Классыи структуры поддерживают сабскрипты и инициализаторы;
Однако классы имеют дополнительные возможности, которых нет у структур:
· С помощью механизма наследования можно наследовать один класс от другого;
· Преобразование типов позволяет проверить и интерпретировать тип класса во время выполнения программы;
· Деинициализаторы позволяют освободить все связанные с объектом класса ресурсы;
· На один и тот же объект класса могут ссылаться сразу несколько переменных.
Главноеотличие классов от структур заключается в том, что классы представляют ссылочные типы (reference types), а структурыперечисления - значимые типы или типы значений (value types). Рассмотрим некоторые из них.
При определении объекта с помощью ключевого слова let он становится константой. Однако объекты классов и структур ведут себя по разному:
class Graj{ var imya: String var vozrast: Int init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast } } struct Gost{ var imya: String var vozrast: Int } let karl: Graj = Graj(imya: "Karl", vozrast: 24) let roman: Gost = Gost(imya:"Roman", vozrast: 24) karl.vozrast = 25 // норм roman.vozrast = 25 // ошибка |
Здесь определены идентичные типы: Graj и Gost. Только один представляет класс, а другой структуру. Константым объектам нельзя присвоить новое значение. Есликонстантый объект представляет класс, то можно изменить значения вего отдельных свойствах. Со структурой это нельзя делать, потому что при изменении свойства структуры Swift полностью меняет объект этой структуры, что для константных объектов недопустимо.
При передаче значения переменной или константы значимого типа в другую переменную или константу происходит копирование этого значения, а при передаче значения ссылочного типа передается ссылка на область в памяти, в которой хранится это значение.
Рассмотрим пример с классами:
class Graj{ var imya: String var vozrast: Int init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast }} var karl: Graj = Graj(imya: "Karl", vozrast: 24) var roman = karl roman.imya = "Roman" print(karl.imya) // Roman |
Здесь определяется переменная karl, которая представляет класс Graj. После присвоения переменной roman обе переменные будут указывать на один и тот же объект в памяти. Присвоениеприведет к копированию ссылки на объект памяти. Поэтому при изменении свойств в переменной karl, также изменятся значения свойств в переменной roman. Так как это свойства одного и того же объекта в памяти.
struct Gost{ var imya: String var vozrast: Int} var alice: Gost = Gost(imya: "Alice", vozrast: 24) var bil = alice bil.imya = "Bil" print(alice.imya) // Alice |
Здесь определяется похожая структура Gost. Далее создаем объект этой структуры alice. Затем присваиваем этот объект переменной bil. В результате присвоения произойдет копирование значений объекта alice в объект bil. Поэтому если изменим свойства переменной bil, то свойства переменной alice не изменят своих значений. Так как это два разных объекта в памяти.
Так как классы представляют ссылочные типы, для сравнения экземпляров класса используется оператор идентичности - ===:
class Graj{ var imya: String var vozrast: Int init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast }} var karl: Graj = Graj(imya: "Karl", vozrast: 24) var roman = karl var anotherKarl: Graj = Graj(imya: "Karl", vozrast: 24) roman === karl // true - ссылка на один и тот же объект anotherKarl === karl // false - ссылка на разные объекты |
Так как переменные roman и karl хранят ссылку на один и тот же объект в памяти, то оператор идентичности возвратит значение true.
ПеременнаяanotherKarl хранит ссылку на другой объект в памяти, ее свойства хранят те же значения, что и свойства в переменной karl. Но поскольку ссылки разные, то оператор идентичности возвратит значение false.
В случае равенства ссылок в Swift есть оператор !==, который возвращает true если ссылки не равны:
anotherKarl !== karl // true |
Одним из ключевых механизмов объектно-ориентированного программирования является наследование. В Swift классы могут наследовать функционал от других классов.
Класс-наследник еще называют подклассом, а класс, от которого наследуется функционал - базовым классом или суперклассом.
Классы в Swift имеют полноценный доступ ко всем методам, свойствам, которые определены в суперклассе. Однако при необходимости подклассы могут переопределять наследуемый функционал суперклассом, например, изменять поведение методов или свойств.
Общий синтаксис наследования классов выглядит следующим образом:
class SubClass: SuperClass{ } |
Для примера наследования рассмотрим простейшую ситуацию. Пусть имеется класс человека и класс служащего:
class Gost{ var imya: String var surname: String init(imya: String, surname: String){ self.imya = imya self.surname = surname } func getFullInfo() -> String{ return "\(self.imya) \(self.surname)" } } class Rabot : Gost{ var company: String init(imya: String, surname: String, company: String){ self.company = company super.init(imya: imya, surname: surname) } } |
КлассRabot наследуется от класса Gost. Класссотрудника будет повторять функционал класса человека, так как каждый сотрудник имеет имя и фамилию. Наследованиев примерепомогает избежать ненужного повторения при определении свойств и методов.
Послесоздания объекта Rabotможно через него обращаться к свойствам и методам базового класса Gost:
var emp: Rabot = Rabot(imya: "Ravshan", surname: "Turdiyev", company:"Artel") var emplInfo = emp.getFullInfo() // RavshanTurdiyev emp.imya = "Timur" emp.surimya = "Povar" |
Ключевое слово super позволяет обращаться из подкласса к свойствам и методам базового класса. В выше приведенном примере ключевое слово super используется для обращения к инициализатору базового класса для передачи ему значений.
Подкласс может перенимать полностью функционал базового класса, а может и переопределять его. Для переопределения используется ключевое слово override.
class Gost{ var imya: String var surname: String init(imya: String, surname: String){ self.imya = imya self.surname = surname } func getFullInfo() -> String{ return "\(self.imya) \(self.surname)" } } class Rabot : Gost{ var company: String init(imya: String, surname: String, company: String){ self.company = company super.init(imya: imya, surname: surname) } override func getFullInfo() -> String{ return "\(self.imya) \(self.surname) - \(self.company)" } } var emp: Rabot = Rabot(imya: "Ravshan", surname: "Turdiyev", company:"Artel") print(emp.getFullInfo()) // RavshanTurdiyev - Artel |
В примере переопределяем метод getFullInfo(). Кромеимени и фамилии он возвращает данные о компании, в которой сотрудник работает.Когдабудем вызывать метод getFullInfo()у объекта Rabot, будет срабатывать переопределенная версия метода.
Используя ключевое superможно переопределить метод по-другому:
override func getFullInfo() -> String{ return "\(super.getFullInfo) - \(self.company)" } |
или
override func getFullInfo() -> String{ return super.getFullInfo() + " - \(self.company)" } |
Таким образомможно переопределять свойства:
|
class Gost{ var imya: String var surname: String init(imya: String, surname: String){ self.imya = imya self.surname = surname } var fullInfo: String{ return "\(self.imya) \(self.surname)" } } class Rabot : Gost{ var company: String init(imya: String, surname: String, company: String){ self.company = company super.init(imya: imya, surname: surname) } override var fullInfo: String{ return super.fullInfo + " - \(self.company)" } } var emp: Rabot = Rabot(imya: "Ravshan", surname: "Turdiyev", company:"Artel") print(emp.fullInfo) // RavshanTurdiyev - Artel |
В примерепереопределяется свойство fullInfo.
При переопределении инициализатора необходимо вызвать инициализатор базового класса для инициализации тех свойств, которые определены в базовом классе. Еслив подклассе есть собственные свойства, их необходимо инициализировать до вызова инициализатора базового класса:
class Gost{ var imya: String var surname: String init(imya: String, surname: String){ self.imya = imya self.surname = surname } var fullInfo: String{ return "\(self.imya) \(self.surname)" } } class Rabot : Gost{ var company: String override init(imya: String, surname: String){ self.company = "Unknown" super.init(imya: "Mr." + imya, surname: surname) } init(imya: String, surname: String, company: String){ self.company = company super.init(imya: imya, surname: surname) } override var fullInfo: String{ return super.fullInfo + " - \(self.company)" } } var emp: Rabot = Rabot(imya: "Tim", surname: "Cook") print(emp.fullInfo) // Mr. Tim Cook - Unknown |
В предыдущем примере было необязательно переопределять инициализатор класса Gost в классе Rabot. Однако слова required можно отметить инициализатор как обязательный для переопределения в подклассах:
class Gost{ var imya: String var surname: String required init(imya: String, surname: String){ self.imya = imya self.surname = surname } } class Rabot : Gost{ var company: String required init(imya: String, surname: String){ self.company = "Unknown" super.init(imya: "Mr." + imya, surname: surname) } init(imya: String, surname: String, company: String){ self.company = company super.init(imya: imya, surname: surname) } } |
С помощью ключевого слова final можно запретить переопределение свойств, методов, сабскриптов в производном классе:
class Gost{ var imya: String var surname: String init(imya: String, surname: String){ self.imya = imya self.surname = surname } final var fullInfo: String{ return "\(self.imya) \(self.surname)" } } |
СвойствоfullInfo нельзя будет переопределить в производном классе.
Можнозапретить наследование класса, поставив перед его определением ключевое слово final:
final class Gost{ //............. } |
Статическиесвойства и методы нельзя переопределить в производных классах:
class Obmen{
static var usd = 59.0
static func convert(schet: Double) -> Double{
return schet / usd }
}
class BankObmen : Obmen{
// переопределить нельзя
/*override static var usd = 59.0
override static func convert(schet: Double) -> Double{
return schet / usd
}*/
}
Впримерекласс Obmen представляет обменник, который с помощью метода convert обменивает средства одной валюты на usd. Вбанке действует обменник, в котором будет отличаться курс доллара и который будет при обмене вычитать дополнительные проценты за обмен. Однако переопределить функционал класса Obmen нельзя, так как свойство и метод являются статическими.
Вклассе могут быть определены свойства и методы класса (class properties/class methods). Они похожи на статические, то есть относятся ко всему классу в целом, а не к отдельным его объектам. Для определения свойств и методов класса применяется ключевое слово class:
class Obmen{
class var usd : Double { return 59.0 }
class func convert(schet: Double) -> Double{
return schet / usd }}
class BankObmen : Obmen{
override static var usd : Double{ return 59.1 }
override static func convert(schet: Double) -> Double{
return schet / usd - schet / usd * 0.1 }}
print(Obmen.convert(schet: 20000)) // 338.98
print(BankObmen.convert(schet: 20000)) // 304.56
К таким свойствам и методам обращение идет по имени класса, как и в случае со статическими.
При переопределении свойств класса свойства класса должны быть вычисляемыми.
Припереопределении можно определить свойства и методы как статические, они также будут недоступны для переопрееления для классов-наследников от BankObmen. Такжеможно определить их как свойства и методы класса:
class BankObmen : Obmen{
override class var usd : Double{ return 59.1 }
override class func convert(schet: Double) -> Double{
return schet / usd - schet / usd * 0.1 }
}
Типы могут быть вложенными (nested). Класс или структура может содержать определение другого класса или структуры. Например:
class Gost{ var imya: String var vozrast: Int var profile: GostProfile struct GostProfile{ var login: String var password: String func authenticate(_ login: String, _ password: String) -> Bool{ return self.login == login && self.password == password } } init(imya: String, vozrast: Int, login: String, password: String){ self.imya = imya self.vozrast = vozrast self.profile = GostProfile(login: login, password: password) } } var karl = Gost(imya: "Karl", vozrast: 23, login: "querty", password: "12345") print(karl.profile.authenticate("sdf", "456")) // false print(karl.profile.authenticate("querty", "12345")) // true |
СтруктураGostProfile является вложенной. Вложенные типы могут определять свойства, методы, инициализаторы. Внешнийтип может хранить объект вложенного типа.Вложенныетипы могут использовать вне своего типа, в котором они определены. В этом случае необходимо обращаться через имя внешнего типа:
var profile = Gost.GostProfile(login: "ssdf", password: "345") var isLoged = profile.authenticate("ssdf", "345") // true |
Полиморфизм представлять возможность взаимозаменяемости типов, которые находятся в одной иерархии классов. Например, возьмем следующую иерархию классов:
class Graj{ var imya: String var vozrast: Int init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast } func display(){ print("Имя: \(imya) Возраст: \(vozrast)") } } class Rabot : Graj{ var company: String init(imya: String, vozrast: Int, company: String) { self.company = company super.init(imya:imya, vozrast: vozrast) } override func display(){ print("Имя: \(imya) Возраст: \(vozrast) Сотрудник компании: \(company)") } } class Manager : Rabot{ override func display(){ print("Имя: \(imya) Возраст: \(vozrast) Менеджер компании: \(company)") } } |
В примерекласс Manager (менеджер компании) наследуется от класса Rabot (сотрудник компании), а класс Rabot - от класса Graj (человек). КлассManager ненапрямую наследуется от Graj.
Поскольку и сотрудник компании и менеджер компании являются людьми, то есть объектами класса Graj, то можно написать следующим образом:
1 2 3 |
let karl: Graj = Graj(imya:"Karl", vozrast: 23) let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") let alice: Graj = Manager(imya: "Alice", vozrast: 31, company: "Microsoft") |
Все три константы представляют тип Graj, первая хранит ссылку на объект Graj, вторая - на объект Rabot, а третья - на объект Manager. Переменнаяили константа одного типа может принимать многообразные формы в зависимости от конкретного объекта, на который она указывает.
Есливызовем метод display() для всех трех объектов:
let karl: Graj = Graj(imya:"Karl", vozrast: 23) let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") let alice: Graj = Manager(imya: "Alice", vozrast: 31, company: "Microsoft") karl.display() // Имя: Karl Возраст: 23 roman.display() // Имя: Roman Возраст: 28 Сотрудник компании: Artel alice.display() // Имя: Alice Возраст: 31 Менеджер компании: Microsoft |
Всетри константы представляют тип Graj, при вызове метода display будет вызываться реализация метода именно того класса, ссылку на объект которого хранит константа. Данный прием называется динамической диспетчеризацией - во время выполнения программы на основании типа объекта система решает, какую именно реализацию метода вызывать. Работатьс объектом производного типа как с объектом базового типа и использовать его требуется объект базового типа. Решениео выборе реализации принимается во время выполнения, это несколько замедляет общий ход работы программы.
При определении иерархии классов можно использовать объекты производного класса где требуеются объекты базового класса. Например, следующие классы:
class Graj{ var imya: String var vozrast: Int
init(imya: String, vozrast: Int){ self.imya = imya self.vozrast = vozrast } func display(){ print("Имя: \(imya) Возраст: \(vozrast)") } } class Rabot : Graj{ var company: String init(imya: String, vozrast: Int, company: String) { self.company = company super.init(imya:imya, vozrast: vozrast) } override func display(){ print("Имя: \(imya) Возраст: \(vozrast) Сотрудник компании: \(company)") } func work(){ print("\(self.imya) работает") } } |
Поскольку класс Rabot наследуется от класса Graj, то требуется объект Graj, можно использовать объект Rabot:
func getInfo(p: Graj){ p.display() } let karl: Rabot = Rabot(imya:"Karl", vozrast: 23, company: "Google") let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") getInfo(p: karl) // Имя: Karl Возраст: 23 Сотрудник компании: Google getInfo(p: roman) // Имя: Roman Возраст: 28 Сотрудник компании: Artel |
Впримере ошибки не возникнут. Компиляторавтоматически преобразует объекты Rabot к типу Graj.
Рассмотримдругую ситуацию:
let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") print(roman.company) // ! Ошибка: у типа Graj нет свойства company roman.work() // ! Ошибка: у типа Graj нет метода work |
В примереконстанта roman представляет тип Graj, но хранит ссылку на объект Rabot, у которого есть свойство company и метод work. Однако у типа Graj их нет. Впримереконстанта roman воспринимаетимеено объект Graj, объект Graj не должен представлять объект Rabot.
Другойпример:
func getInfo(p: Rabot){ p.display() } let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") getInfo(p: roman) // ! Ошибка: roman представляет объект Graj, а не Rabot |
Функция getInfo принимает объект Rabot, константа roman представляет объект Graj, хранит ссылку на объект Rabot, поэтому автоматически ее передать в этот метод нельзя.
Еслипеременная / константы представляет объект базового типа, необходимо его использовать как объект производного типа. В этом случае необходимо применить нисходящее преобразование типов (downcasting). Для этого применяется оператор as!:
func getInfo(p: Rabot){ p.display() } let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") print((roman as! Rabot).company) // Artel (roman as! Rabot).work() // Roman работает getInfo(p: (roman as! Rabot)) // Имя: Roman Возраст: 28 Сотрудник компании: Artel let romanEmpl = roman as! Rabot romanEmpl.work() |
Операторas! позволяет преобразовать объект одного типа в другой, и может столкнуться с ошибкой:
let karl: Graj = Graj(imya:"Karl", vozrast: 23) let karlEmpl: Rabot = karl as! Rabot |
Константаkarl хранит ссылку на объект Graj, а не Rabot. При попытке преобразования к типу Rabot получится ошибка. Чтобы избежать ошибок, следует проверять тип перед преобразованием типов с помощью оператора is:
В качестве альтернативы можно преобразовывать объект в тип Optional с помощью оператора as?, а затем проверять на nil:
let karl: Graj = Graj(imya:"Karl", vozrast: 23) let karlEmpl: Rabot? = karl as? Rabot if karlEmpl != nil{ karlEmpl!.work() } let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") let romanEmpl = roman as? Rabot if romanEmpl != nil{ romanEmpl!.work() } |
Также можно сократить код следующим образом с помощью оператора ?.:
let karl: Graj = Graj(imya:"Karl", vozrast: 23) (karl as? Rabot)?.work() let roman: Graj = Rabot(imya: "Roman", vozrast: 28, company: "Artel") (roman as? Rabot)?.work() |
Обобщения
Обобщения (generics) позволяют создавать гибкие конструкции без привязки к конкретным типам. Примеромобобщений являются массивы в виде типа Array, который не привязан к конкретному типу, а может хранить и числа, и строки, и логические значения.
Обобщения полезны для создания универсальных функций, которые могут принимать значения разных типов. Например, функция обмена значениями:
func swap(_ p: inout Int, _ q: inout Int){ let temp: Int = p p = q q = temp } var x: Int = 25 var y: Int = 14 swap(&x, &y) print(x) // 14 |
Данная функция работает для двух чисел типа Int. Еслипонадобится какой-то функционал по обмену двух чисел типа Double, то придется писать новую функцию. Хотя по своему действию она не отличалась бы от выше определенной функции. Нужно создать общую функцию для чисел Int иDouble.
Длярешения данной проблемы можно использовать обобщения. Перепишем эту функцию с использованием обобщением:
func swap<T>(_ p: inout T, _ q: inout T){ let temp: T = p p = q q = temp } |
Для создания обобщенной функции используется универсальный параметр типа, который идет после названия функции в угловых скобках. В примере универсальный параметр типа представляет букву T. В реальности это не обязательно должна быть буква T. БукваT или универсальный параметр задает некоторый тип, который применяется в функции. При этом на момент определения функции можно не знать, что это за тип. Это просто некоторый тип. И параметры функции p и q представляют значения именно этого типа.Далее можно использовать эту функцию:
var x: Int = 25 var y: Int = 14 swap(&x, &y) print(x) // 14 |
Здесьпередаем в функцию значения типа Int, то система автоматически в качестве параметра типа для этой функции будет использовать тип Int.
Вэту функциюможно передать и значения других типов. Например, типа Double:
var s1: Double = 10.2 var s2: Double = -3.6 swap(&s1, &s2) print(s1) // -3.6 |
Теперь система автоматически будет для параметра типа использовать тип Double.
Обобщенные функций также могут быть обобщенные или универсальные типы. Это типы, которые также используют универсальный параметр.
Дляидентификации объекта используется свойство id - идентификатор, который отличает один объект от других. Для идентификатора выбирается число или строка. Рассмотрим конкретный пример:
class Gost<T>{ var id: T var imya: String init(id: T, imya: String){ self.id = id self.imya = imya } } var karl: Gost = Gost(id: 12, imya: "Karl") var roman: Gost = Gost(id: "234nds", imya: "Roman") |
Для определения универсального обобщенного класса после имени класса в угловых скобках идет название универсального параметра: class Gost<T>
Впримере свойство id будет представлять значение типа T.
После определения класса создаем два объекта: karl и roman. Переменная karl в качестве id использует число, а переменная roman - строку.
Обаобъекта представляют тип Gost, но с учетом универсального параметра переменная karl будет представлять тип Gost<Int>, а переменная karl - тип Gost<String>. Можноявно указать тип универсального параметра:
var karl: Gost<Int> = Gost<Int>(id: 12, imya: "Karl") |
Для универсального параметра можно установить ограничение (constraint). Ограничения могут быть полезными еслиуниверсальный параметр можеть представлять определенный класс или один из его производных классов. Например:
class Transport{ func drive(){ print("Транспорт едет") } } class Auto: Transport{ override func drive(){ print("Машина едет") } } func driveTransport<T: Transport>(_ transport: T){ transport.drive() } var myAuto: Auto = Auto() driveTransport(myAuto) // Машина едет |
Здесь определен класс транспортного средства Transport и производный класс машины - Auto.Такжеопределена обобщенная универсальная функция driveTransport(), представляющая функцию вождения транспортного средства. Этафункция предусмотрена для любого транспортного средства, то задаем ограничение - тип Transport. Привызове этой функцииможно передать в нее любой объект класса Transport или один из его производных классов, например, объект класса Auto.
Установка ограничения позволяет использовать у объектов этого типа методы и свойства. Впримере функция driveTransport() вызывает метод drive() переданного в функцию объекта.
При наследовании от обобщенного базового типа производный тип перенимает параметр базового типа:
class Gost<T>{ let id: T init(id: T){ self.id = id } func displayId(){ print(id) } } class Rabot<T> : Gost<T>{} class GostInt : Gost<Int>{} let alice = Rabot<String>(id: "5746fgg") alice.displayId() let roman = GostInt(id: 34) roman.displayId() |
В примере производный тип Rabot также является обобщенным, и при создании его объекта можно типизировать его конкретным типом. А класс GostInt является необобщенным, он в начале типизирован типом Int.
При определении обобщений следует учитывать, что они не ковариантные. Например, в следующем случае получим ошибку:
struct Graj<t> { } class Auto { } class Truck : Auto{ } let karl : Graj<Auto> = Graj<Truck>() // ! Ошибка компиляции </t> |
КлассTruck наследуется от класса Auto и не можно присвоить переменной типа Graj<Auto> объект Graj<Truck>.
Последовательность (range) представляет набор значений, который определяется начальной и конечной точкой. Есть два способа определения последовательности с помощью специальных операторов:
v ...: этот оператор принимает начальное и конечное значения последовательности и создает набор значений, который включает оба значения
let range = 1...5 // 1 2 3 4 5 |
Спомощью выражения 1...5 будет создаваться последовательность 1 2 3 4 5. При создании последовательности используется в качестве шага число 1, которое добавляется к предыдущему значению.
Также последовательность может идти в обратную сторону, если начальное значение меньше конечного, то есть применяется шаг -1:
let range = -5 ... -1 // -5 -4 -3 -2 -1 |
v ..<: этот оператор также принимает начальное и конечное значения последовательности и создает набор значений, который не включает конечное значение.
Последовательности имееют ряд методов.Метод Reversed() возвращает перевернутую наоборот последовательность:
let range = 5 ... 8 // 5 6 7 8 let inv = range.reversed() for val in inv { print(val) } // 8 // 7 // 6 // 5 |
Метод Contains() позволяет проверить наличие элемента в последовательности. Если элемент присутствует, то метод возвращает true:
let range = 5 ... 8 // 5 6 7 8 print(range.contains(6)) // true print(range.contains(9)) // false |
Метод Start(with:) позволяет проверить, начинается ли последовательностьс подпоследовательности, которая передается через параметр with:
let range = 5 ... 8 // 5 6 7 8 let st1 = range.starts(with: 1...5) // false let st2 = range.starts(with: 5...7) // true |
Метод Overlaps возвращает true, если две последовательности частично совпадают:
let range = 5 ... 8 // 5 6 7 8 range.overlaps(3...9) // true range.overlaps(9...19) // false |
Массив представляет упорядоченную коллекцию элементов одного типа, к которым можно обратиться по индексу - номеру элемента в массиве. Индекс представляет число - объект типа Int и начинается с нуля. По сути массив представляет собой обычную переменную или константу, которая хранит несколько объектов как кортежы.
Объявление массива имеет следующие формы записи:
1 2 3 4 |
// Полная форма var имяМассива: Array<Тип> // Краткая форма var имяМассива: [Тип] |
Например:
var sifras: [Int] |
Здесь объявлен массив sifras, который хранит объекты типа Int.
Простообъявить массив недостаточно. Любуюдругую переменную, надо инициализировать перед использованием. То есть определить начальное значение. Все значения, которые входят в массив, перечисляются в квадратных скобках: [элемент1, элемент2, элемент3, ...]. Например:
var sifras: [Int] = [1, 2, 3, 4, 5] var sifras2: Array<Int> = [1, 2, 3, 4, 5] print(sifras) |
Массивыsifras и sifras2 содержат по 5 элементов. Можнопри объявлении не указывать тип массива, в этом случае система сама выведет тип исходя из входящих в него элементов:
var sifras = [1, 2, 3, 4, 5] |
также можно определить пустой массив:
var sifras = [Int]() // или так var sifras2 : [Int] = [] print("В массиве sifras \(sifras.count) элементов") // В массиве sifras 0 элементов |
В таком массиве будет 0 элементов. С помощью свойства count можно получить количество элементов в массиве.
Каждый элемент в массиве имеет определенный индекс, по которому можно получить или изменить элемент:
var sifras = [11, 12, 13, 14, 15] print(sifras[0]) // 11 sifras[0] = 21 print(sifras[0]) // 21 |
Для обращения к элементу массива после названия массива в квадратных скобках используется индекс элемента: sifras[0].
В примере пять элементов в массиве, индексация в массивах начинается с нуля, поэтому первый элемент всегда имеет индекс 0, а последний элемент в примере будет иметь индекс 4. Еслипопытаемся обратиться к элементу с большим индексом, например:
print(sifras[5]) // ошибка |
То получим ошибку.
Если надо изменить несколько элементов подряд, нужно использовать операцию последовательности для записи индексов:
var sifras = [5, 6, 7, 8, 3] sifras[1...3] = [105, 106, 103] print(sifras) // 5, 105, 106, 103, 3 |
В примере выражение 1...3 указывает на набор индексов от 1 до 3. И таким образом, элементам с этими индексами можно присвоить значения.
С помощью свойства count можно получить число элементов массива:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] print("В массиве sifras \(sifras.count) элементов") // В массиве sifras 8 элементов |
СвойствоisEmpty позволяет узнать, пустой ли массив. Если он пустой, то возвращается значение true:
var sifras: [Int] = [1, 4, 8] if sifras.isEmpty { print("массив пустой") } else { print("в массиве есть элементы") } |
С помощью цикла for можно перебрать элементы массива:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] for i in sifras { print(i) // 1, 2, 3, 4, 5, 6, 7, 8, } |
Перебор массива через индексы:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] for i in 0 ..<sifras.count { print(sifras[i]) // 1, 2, 3, 4, 5, 6, 7, 8, } |
Вместо применения цикла также можно использовать метод forEach(), которые перебирает все элементы. В качестве параметра этот метод принимает функцию, которая производит действия над текущим перебираемым элементом:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] sifras.forEach({print($0)}) |
В примере передается анонимная функция, которая с помощью функции print выводит значение элемента.
С помощью метода Enumerated() можно одновременно получить индекс и значение элемента:
var imyas: [String] = ["Karl", "Alice", "Kate"] imyas.enumerated().forEach({print("\($0) - \($1)")}) for (index, value) in imyas.enumerated() { print("\(index) - \(value)") } |
Специальная форма инициализатора в качестве параметра принимает последовательность, из которой создается массив:
var sifras = Array (1...5) // [1, 2, 3, 4, 5] var sifras2 = [Int] (3..<7) // [3, 4, 5, 6] print(sifras) // [1, 2, 3, 4, 5] print(sifras2) // [3, 4, 5, 6] |
Еще одна форма инициализатора позволяет инициализировать массив определенным числом элементов одного значения:
var sifras = [Int] (repeating: 5, count: 3) // или так var sifras2 = Array (repeating: 5, count: 3) // эквивалентно массиву var sifras: [Int] = [5, 5, 5] print(sifras) // [5, 5, 5] |
Еслиподобным образом создается массив из объектов классов - ссылочных типов, то все элементы массива будут хранить ссылку на один и тот же элемент в памяти:
class Graj{ var imya: String init(imya: String){ self.imya = imya } } let karl = Graj(imya: "Karl") var people = Array (repeating: karl, count: 3) people[0].imya = "Roman" for Graj in people{ print(Graj.imya) } // Roman // Roman // Roman |
Два массива считаются равными, если они содержат одинаковые элементы на соответствующих позициях:
var sifras: [Int] = [1, 2, 3, 4, 5] let nums = [1, 2, 3, 4, 5] if sifras == nums{ print("массивы равны")} else { print("массивы не равны")} |
Массивыsifras и nums имеют одинаковое количество элементов и все элементы на соответствующих позициях равны, поэтому оба массива равны.
Массив является значимым типом, поэтому при копировании одного массива в другой второй массив получает копию первого:
var sifras: [Int] = [1, 2, 3, 4, 5] var nums: [Int] = sifras nums[0] = 78 print(sifras) // [1, 2, 3, 4, 5] print(nums) // [78, 2, 3, 4, 5] |
С помощью последовательности можно задать набор индексов, по которым элементы копируются в другой массив:
var sifras: [Int] = [1, 2, 3, 4, 5] var nums = sifras[1...3] print(nums[1]) // 2 print(nums) // [2, 3, 4] |
В примере в массив nums копируются элементы из sifras с 1 по 3 индексы. При этом первым индексом в массиве nums будет не 0, а 1, так как с этого индекса производится копирование из sifras.
Для добавления элемента в массив применяется метод Append():
var sifras = [8, 11, 13, 14] sifras.append(20) print(sifras) // 8, 11, 13, 14, 20 |
МетодInsert() управляет вставкой элемента в определенное место массива:
var sifras = [8, 11, 13, 14] sifras.insert(10, at: 3) // вставка числа 10 на 3-й индекс print(sifras) // 8, 11, 13, 10, 14 |
Ряд операций позволяют удалять элемент:
remove(at: index): удаляет элемент по определенному индексу
removeFirst(): удаляет первый элемент
removeLast(): удаляет последний элемент
dropFirst(): удаляет первый элемент
dropLast(): удаляет последний элемент
removeAll(): удаляет все элементы массива
var sifras = [8, 11, 13, 14] sifras.remove(at: 2) // удаляем 3-й элемент print(sifras) // 8, 11, 14 |
Разница между методами removeFirst/removeLast и dropFirst/dropLast заключается в том, что первые возвращают удаленный элемент, а вторые - модифицированный массив:
var sifras = [8, 11, 13, 14] var n = sifras.removeFirst() // 8 var subSifras = sifras.dropFirst() print(subSifras) // [13, 14] |
Еслииз массива необходимо удалить все элементы, применяется метод removeAll():
var sifras = [8, 11, 13, 14] sifras.removeAll() print(sifras) // [] |
Для сортировки массива применяется метод Sort():
|
var sifras: [Int] = [10, 4, 12, 1, 3] sifras.sort() print(sifras) // [1, 3, 4, 10, 12] |
МетодSort() сортирует оригинальный массив, а метод Sorted() возвращает новый отсортированный массив, никак не изменяя старый:
var sifras: [Int] = [10, 4, 12, 1, 3] var nums = sifras.sorted() print(nums) // [1, 3, 4, 10, 12] |
Обе функции принимают параметр by, который задает принцип сортировки. Он представляет функцию, которая принимает два параметра. Оба параметра представляют тип элементов массива. На выходе функция возвращает объект Bool. Если это значение равно true, то первое значение ставится до второго, если false - то после.
var sifras: [Int] = [10, 4, 12, 1, 3] sifras.sort(by: {$0 > $1}) print(sifras) // [12, 10, 4, 3, 1] var nums = sifras.sorted(by: <) print(nums) // [1, 3, 4, 10, 12] |
Выражение {$0 > $1} представляет анонимную функцию, которая возвращает true, если значение первого параметра меньше второго, то есть в примере сортировка в обратном порядке. Можнозадать более сложную логику, но в примере просто сравниваем два значения, поэтому можно упрастить данный вызов: sifras.sort(by: >) и Swift будет применять операцию > к обоим параметрам.
С помощью операции сложения можно объединить два однотипных массива:
var sifras1 = [5, 6, 7] var sifras2 = [1, 2, 3] var sifras3 = sifras1 + sifras2 print(sifras3) // 5, 6, 7, 1, 2, 3 |
Для фильтрации массива применяется метод Filter(), который возвращает отфильтрованный массив. В качестве параметра метод принимает функцию - если перебирает все эелементы и возвращает значение типа Bool. Если это значение - true, то элемент попадает в отфильтрованный массив:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] var filteredNums = sifras.filter({$0 % 2==0}) print(filteredNums) // [2, 4, 6, 8] |
В примере метод Filter принимает анонимную функцию, которая возвращает результат условия $0 % 2==0, то есть если число делится на 2 без остатка (четное), то оно попадает в отфильтрованный массив.
Еще один способ фильтрации представляет метод Prefix(). Он возвращает отфильтрованный массив, при этом он перебирает подряд все элементы, пока условие возвращает true. Условие задается с помощью параметра while, который представляет функцию, возвращающую значение Bool:
var sifras: [Int] = [1, 2, 3, 4, 5, 6, 7, 8] var filteredNums = sifras.prefix(while: {$0 < 5}) print(filteredNums) // [1, 2, 3, 4] |
В примере пока истинно условие $0 < 5, то есть пока элементы массива меньше 5, они будут попадать в отфильтрованный массив.
Противоположным образом работает метод Drop() - он, наоборт, удаляет все элементы до того, пока они не станут соответствовать условию:
var sifras: [Int] = [1, 2, 5, 3, 4, 5, 6, 7, 8] var filteredNums = sifras.drop(while: {$0 < 5}) print(filteredNums) // [5, 3, 4, 5, 6, 7, 8] |
Причем удаляются именно первые элементы, пока не найдется элемент, который удовлетворяет условию.
Метод Map() проходит по всем элементам массива и преобразует их с помощью функции, которая передается в качестве параметра и которая возвращает преобразованный элемент. Преобразованные элементы помещаются в новый массив, который возвращается методом Map():
var sifras: [Int] = [1, 2, 5, 3, 4, 5, 6, 7, 8] var mapedNums = sifras.map({$0 * $0}) print(mapedNums) // [1, 4, 25, 9, 16, 25, 36, 49, 64] |
В примере метод Map() принимает анонимную функцию, которая возвращает квадрат числа. Созданныймассив будет содержать квадраты чисел из оригинального массива.
Выше использовали простые массивы, которые можно представить в виде строки или расположить в ряд:
var sifras = [1, 2, 3, 4, 5, 6, 7, 8, 9] |
Но можно создавать более сложные массивы, которые в качестве элементов могут включать в себя другие массивы:
var table = [[1,2,3], [4,5,6], [7,8,9]] // получаем вторую строку var row2 = table[1] // [4,5,6] // получаем первый элемент второй строки var cell1 = row2[0] // 4 // получаем второй элемент первой строки var cell2 = table[0][1] // 2 |
Внутри массива три подмассива. Данныймассив можно представить как таблицу, в которой 3 строки. Выражение table[1] позволяет получить второй элемент - второй подмассив. А выражение row2[0] возвращает первый элемент этого подмассива.
Можноиспользовать набор квадратных скобок для доступа к элементам внутри подмассивов: table[0][1]
Таким образом можно изменять элементы массива
// изменим вторую строку table[1] = [16, 25, 36] // изменим второй элемент первой строки table[0][1] = -12 |
При переборе стоит учитывать, что каждый элемент многомерного массива сам является массивом, то можно организовать вложенные циклы для их перебора:
// перебор по строкам for row in table{ print(row) } // перебор таблицы по строкам и столбцам for row in table{ for cell in row{ print(cell) } } |
6.1.13. Множества
Множества (Set) представляют собой неупорядоченные коллекции уникальных элементов. В отличие от массивов в множествах элементы должны быть уникальными, нельзя определить несколько элементов с одним и тем же значением.
Для определения множества у переменной или константы устанавливается тип Set<Element>, где Element - это тип данных:
var sifras: Set<Int> = [5, 6, 7, 8] |
При определении можно опускать тип:
var sifras: Set = [5, 6, 7, 8] |
Или можно использовать функцию инициализатора Set<Element>:
var sifras = Set<Int>(arrayLiteral: 5, 6, 7, 8) |
Создание пустого множества:
var sifras = Set<Int>() // или // var sifras: Set<Int> = [] |
Метод Insert() позволяет добавить новый элемент в множество:
var sifras: Set<Int> = [5, 6, 7, 8] sifras.insert(10); print(sifras) // [5, 6, 7, 8, 10] |
Также множества поддерживают ряд операций по удалению:
· removeAtIndex(): удаляет элемент по определенному индексу
· removeFirst(): удаляет первый элемент
· remove(): удаляет какой-то определенный элемент по значению
· removeAll(): удаляет все элементы
var sifras: Set<Int> = [5, 6, 7, 8] sifras.remove(7); print(sifras) // [5, 6, 8] |
МетодContains() позволяет проверить наличие в множестве элемента:
var sifras: Set<Int> = [5, 6, 7, 8] var isPresent = sifras.contains(7); // true - элемент есть isPresent = sifras.contains(34); // false - элемент отсутствует |
Множествопредставляет неупорядоченный набор, но с помощью метода Sorted()можно отсортировать его:
var sifras: Set<Int> = [4, 7, 2, 6] print(sifras.sorted()) // [2, 4, 6, 7] |
Есть операции предназначенные специально для множеств: объединение, пересечение, разность множеств.
intersection(): пересечение множеств, возвращает общие для обоих множеств элементы
symmetricDifference(): возвращает не общие (не пересекающиеся) для обоих множеств элементы (симметрическая разность)
union(): объединение двух множеств
razn(): разница множеств, возвращает элементы первого множества, которые отсутствуют во втором
var p: Set = [1, 2, 3, 4, 5] var q: Set = [4, 5, 6, 7, 8] // объединение p.union(q) // [1, 2, 3, 4, 5, 6, 7, 8] // пересечение p.intersection(q) // [4, 5] // разность p.razn(q) // [1, 2, 3] // симметрическая разность p.symmetricDifference(q) // [1, 2, 3, 6, 7, 8] |
6.1.14. Словари
Словарь (dictionary) представляет собой хранилище, в котором каждый элемент имеет ключ и значение. Разные элементы не могут иметь одинаковые ключи. Все ключи в словаре должны быть уникальными. По ключуможно найти элемент в словаре изменить его или удалить.Чтобы определить словарь, нужно написать в квадратных скобках несколько элементов в формате [Key: Value]. Например:
1 |
var phones = ["Artel": "iPhone 6S", "Microsoft": "Lumia 950", "Google": "Nexus X5"] |
Здесь словарь называется phones и содержит три элемента. Например, первый элемент: "Artel": "iPhone 6S". "Artel" представляет ключ, а "iPhone 6S" - значение. Иключ, и значение представляют тип String. Но это необязательно.
Также можно явным образом указать тип ключа и значения в словаре:
var phones: [String: String] = ["Artel": "iPhone 6S", "Microsoft": "Lumia 950", "Google": "Nexus X5"] |
Такжеможно использовать полное определение типа:
var phones: Dictionary<String, String> = ["Artel": "iPhone 6S", "Microsoft": "Lumia 950", "Google": "Nexus X5"] |
Создание пустого словаря:
var phones: Dictionary<String, String> = [:] // альтернативный вариант var phones2: [String: String] = [:] // с использованием инициализатора var phones3 = [String: String]() |
Проверить словарь на наличие элементов можно с помощью свойства isEmpty, которое возвращает true при отсутствии элементов:
var phones: Dictionary<String, String> = [:] if phones.isEmpty { print("Словарь phones пуст") } else { print("В словаре phones есть элементы") } |
Чтобы определить число элементов в словаре, можно использовать свойство count:
var phones: [String: String] = ["Artel": "iPhone 6S", " WinMobile": "Lumia 200", "Google": "Nexus S7"] print(phones.count) |
Используя ключ элемента, можно обратиться к этому элементу в словаре, получить его или изменить:
var phones: [String: String] = ["Artel": "iPhone 10S", "WinMobile": "Lumia 200", "Google": "Nexus S7"] // получение элемента по ключу print(phones["Artel"]) // iPhone 6S // изменение элемента phones["Artel"] = "iPhone 5SE" |
Альтернативой подобному изменению элемента в словаре является метод UpdateValue:
phones.updateValue("iPhone 5SE", forKey: "Artel") print(phones["Artel"]) // iPhone 5SE |
Для удаления элемента из словаря достаточно ему присвоить значение nil:
phones["Google"] = nil |
Либо можно использовать метод RemoveValue(), который с помощью параметра forKey принимает ключ удаляемого элемента:
phones.removeValue(forKey: "Google") |
Метод RemoveValue() возвращает значение удаляемого объекта, если объекта с таким ключом в словаре нет, то возвращается nil:
if let removedValue = phones.removeValue(forKey: "Google") { print("Удален объект \(removedValue).") } else { print("Словарь не содержит удаляемого элемента") } |
Для перебора словаря используется стандартный цикл for-in:
var phones: [String: String] = ["Artel": "iPhone 6S", "WinMobile": "Lumia 950", "Google": "Nexus X5"] for (manufacturer, model) in phones { print("\(manufacturer): \(model)") } |
При переборе каждый объект словаря возвращается в виде кортежа (key, value), в котором первый элемент представляет ключ, а второй - значение.
Можно отдельно перебирать ключи и значения. Перебор ключей:
for manufacturer in phones.keys { print(manufacturer) } |
Перебор значений:
for model in phones.values { print(model) } |
С помощью встроенной глобальной функции zip() можно соединить два массива в объект Zip2Sequence, который затем передается в инициализатор типа Dictionary:
let countries = ["Iran", "Iraq", "Syria", "Lebanon"] let capitals = ["Tehran", "Bagdad", "Damascus", "Beirut"] var seq = zip(countries, capitals) var dict = Dictionary(uniqueKeysWithValues:seq) for (key, value) in dict { print("\(key) - \(value)") } |
В примере каждый элемент из массива countries последовательно сопоставляется с соответствующим элементом из массива capitals. Потом результат через параметр uniqueKeysWithValues передается в инициализатор Dictionary. Такимобразом образуется словарь. Результат программы:
Iran - Tehran Iraq - Bagdad Syria - Damascus Lebanon - Beirut
Еслив обоих массивах есть повторяющиеся значения, то подобный способ их объединения завершится с ошибкой, в словаре все ключи должны быть уникальными. Дляэтого надо использовать другую форму инициализатора Dictionary:
let countries = ["Iran", "Iraq", "Syria", "Lebanon", "Iran"] let capitals = ["Tehran", "Bagdad", "Damascus", "Beirut", "Tehran"] var seq = zip(countries, capitals) var dict = Dictionary(seq, uniquingKeysWith:{return $1}) for (key, value) in dict { print("\(key) - \(value)") } |
В примере в инициализатор в качестве первого параметра опять же передается объединенные последовательности. А второй параметр uniquingKeysWith указывает на функцию, которая получает все значения из второго массива, которые соответствуют повторяющемуся ключу. В нашем случае это два элемента. И затем нам надо возвратить какой-нибудь результат. Здесь просто возвращается значение второго параметра.
Классы, структуры и перечисления могут определять сабскрипты (subscripts). Сабскрипты используются для доступа к элементам коллекции или последовательности. Естьпохожая концепция - индексаторы. Сабскрипты позволяют обращаться с объектом класса или структуры как с отдельной коллекцией.Для определения сабскрипта используется ключевое слово subscript:
subscript(параметры) -> тип_возвращаемых_объектов { get { // возвращаем значение } set(newValue) { // устанавливаем новое значение newValue } } |
После ключевого слова subscript в скобках идут параметры, которые используются для получения элементов. Параметрыпредставляют числовой индекс, по которому надо получить объект. Далее указывается тип элементов, с которыми работаем.
Сабскрипт может состоять из двух блоков: get и set. Блок get возвращает элемент, а блок set устанавливает новое значение, которое передается через параметр newValue.
Например, создадим класс библиотеки. Библиотекапредставляет некоторый набор книг. Можнопредставить класс библиотеки как коллекцию книг и использовать сабскрипты для получения книг по индексу:
class Book{ // класс книги var imya: String init(imya: String){ self.imya = imya } } class Library{ // класс библиотеки var books: [Book] = [Book]() init(){ books.append(Book(imya: "Утган кунлар")) books.append(Book(imya: "Тонг")) books.append(Book(imya: "Бухоро")) } subscript(index: Int) -> Book{ get{ return books[index] } set(newValue){ books[index] = newValue } }} var myLibrary: Library = Library() var firstBook: Book = myLibrary[0] // получаем элемент по индексу 0 print(firstBook.imya) // Утган кунлар myLibrary[2] = Book(imya: "Абдулла Каххор") // установка элемента по индексу 2 print(myLibrary[2].imya) // Абдулла Каххор |
Здесь сабскрипт предназначен для работы с типом Book. В блоке get происходит получение объекта Book по индексу из массива books. В блоке set устанавливаем объект Book в массиве books.
В программе можно обращаться к библиотеке как к массиву по индексу для получения нужной книги:
var firstBook: Book = myLibrary[0] |
Есть два типа сабскриптов:
· Сабскрипты, которые поддерживают чтение и запись (с блоками get и set)
· Сабскрипты только для чтения (только с блоком get)
Изменим класс библиотеки, чтобы применить сабскрипт только для чтения:
class Library{ var books: [Book] = [Book]() init(){ books.append(Book(imya: " Утган кунлар")) books.append(Book(imya: "Тонг")) books.append(Book(imya: " Бухоро")) } subscript(index: Int) -> Book{ return books[index] } } |
6.2. Разработка мобильных приложений на языке программирования Swift
Сделаем простое приложение для расчета индекса массы тела и необходимого количества калорий для поддержания веса. Для этого будем использовать формулы Гарисса Бенедикта и индекса массы тела.
1. Для начала создадим проект: запустим Xcode и нажмите комбинацию клавиш CMD+Shift+N или выберите в меню File->New->Project
2. Выберите Tabbed Application инажмите Next.
3. На этом этапе надо придумать имя для приложения (Product Imya), нажать Next и выбрать папку для сохранения. Параметры Organization Imya и Organization Identifier можете не менять.
Главное, чтобы значение в поле Language было равно Swift.
4. После того, как сохраните проект, появится рабочее пространство Xcode.
Слева будет панель Navigator, а справа — Utilities. В каждой из этих панелей есть свои вкладки, которые позволяют получить доступ к различным функциям.
Например, первая вкладка в панеле Navigator показывает список файлов проекта, третья позволяет производить поиск по проекту и так далее.
5. Найдите в списке файлов проектов файл с именем Main.storyboard и нажмите на него.
Файл Main.storyboad определяет, какие экраны (контроллеры) есть в приложении. Можнодобавить элементы на экраны, задавать связи между экранами и так далее.
Если выберите какой-нибудь контроллер, он подсветится синей рамкой:
После того, как контроллер выбран, его свойства начнут отображаться в панели Utilities. Например, контроллеру можно поменять размер, выбрав другое значение в поле Size.
6. Запустим шаблонное приложение и посмотрим, как оно выглядит в симуляторе. Для этого надо в верхнем левом углу выбрать тип симулируемого устройства и нажать комбинацию клавиш CMD+R или кнопку с икокой Play.
Как видите, приложение с двумя экранами, преключение между которым осуществляется при помощи таб-бара.
7. Нужно переименовать название табов. Для того, чтобы это сделать, надо выбрать таб, кликнув на него или выбрав его в иерархии элементов управления.
После того как таб будет выбран, он подсветится зеленым цветом.
8. Теперь в панели Utilities выбирайте Attributes Inspector и меняйте значение в поле Title на BMR/BMI для первого таба и Килокалории для второго.
Напервом контроллере размещены всякие надписи. Их надо удалить. Для этого выделите их и нажмите Delete.
9. Добавимэлементы управления. Внизу панели Utilities есть Object Library, в которой можно выбрать объекты и добавить их к сцене. Найдите объект Label и перетащите его на контроллер BMR/BMI. Перед этим не забудьте два раза тапнуть на сцене, чтобы масштаб стал стандартным.
Должно получиться примерно так.
В панели Utilities можно поменять текст, размер шрифта и так далее.
10. Перетащите еще несколько label и поставьте им такой же текст, как и на картинке. Для последнего label нужно поставить Lines в 2, чтобы текст переносился на другую строку.
11. Теперь добавьте 3 текстовых поля (Text Field).
И разместите их, как на картинке.
У текстовых полей поставьте параметр keyboard type в NumberPad.
И установите текст по умолчанию.
12. Теперь добавим элементы управления для выбора пола и кол-ва тренировок в неделю. В Object Library ищете Segmented Control идобавляйтенаэкран.
У Segmented Control можно менять количество сегментов и текст для каждого сегмента.
Сделайте так, чтобы сегменты выглядели, как на скриншоте.
13. Добавьте кнопку (button).
Установитеей заголовок.
После этого добавьте еще один label с lines равным 4.
14. Выберите контроллер BMI/BMR, нажмите два пересекающихся кольца в правом верхнем углу и откроется Assistant Editor. Он показывает код, ассоцированный с этим контроллером.
15. Протащим элементы управления.
Для этого выберите первый text field ( это будет возраст), нажмите Ctrl, кликните на него еще раз и не отпуская кнопки, перетащите внутрь класса. Если все сделано правильно, то увидите посказку “Insert Outlet, Action, …”.
Теперь отпустите курсор, и увидете окошко для создания связи. В качестве имени введите vozrastTextField и нажмите Connect.
16. Появится переменная vozrastTextField.
Проделайте эту процедуру для оставшихся text field, segmented controls и label с текстовым результатом. Должнополучиться так:
Теперь протащите кнопку, но тип соединения укажите не Outlet, а Action. И в качестве имени используйте calculateTapped.
17. Теперь скопируйте следующий код в метод calculateTapped.
Этот код выполняет расчет и выводит результат на экран.
18. Запустим и проверим приложение:
1. Когда их стоит использовать Any и AnyObject в Swift?
2. Как их использовать словари в Swift?
3. Архитектура приложения: почему это важно и что стоит учитывать?
4. Каксохранять и читать простейшие данные?
5. Какможно использовать расширения в Swift:?
6. Как передавать данные между View Controllers на Swift?
7. Область видимости в Swift и контекст определения переменных
8. Функции reduce и filter для работы с коллекциями на Swift
Abstract Window Toolkit (AWT) –это cтандартный пакет графических компонент, реализованных с использованием специфических платформенных методов. Данные компоненты поддерживают подмножество функциональных возможностей, которое присуще всем платформам.
Abstract –это определение класса с указанием на невозможность создания его экземпляров, но при этом доступного для наследования другими классами. Абстрактный класс может содержать нереализованные (абстрактные) методы, которые должны быть реализованы в его подклассах.
Абстрактныйкласс –это класс, который содержит один или более абстрактных методов, вследствие которого нельзя создавать экземпляры данного класса. Абстрактные классы определены таким образом, чтобы другие классы могли расширять и конкретизировать их, реализуя абстрактные методы.
Абстрактный метод–это метод, не имеющий реализации.
API (Application Programming Interface) – это спецификация, предназначенная для пользователей и описывающая методы доступа к свойствам и состоянию объектов и классов.
Апплет – это компонент, который исполняется в Web-браузере или в любой другой программе просмотра апплетов.
Байт-код –это машинно-независимый код, генерируемый Java-компилятором и исполняемый Java-интерпретатором.
Мобильная операционная система – это система предназначенная для управления мобильным устройством.
Linux Kernel (Базовый уровень) –это уровень абстракции между аппаратным уровнем и программным стеком.
Android Runtime–обеспечивает важнейший базовый функционал для приложений, содержит виртуальную машину Dalvik и базовые библиотеки Java необходимые для запуска Android приложений.
Application Framework –это уровень каркаса приложений, обеспечивает разработчикам доступ к API, предоставляемым компонентами системы уровня библиотек.
Applications –это уровень приложений, набор предустановленных базовых приложений.
Surface Manager – это композитный менеджер окон. Поступающие команды отрисовки собираются в закадровый буфер, где они накапливаются, составляя некую композицию, а потом выводятся на экран. Это позволяет системе создавать интересные бесшовные эффекты, прозрачность окон и плавные переходы.
Media Framework –это библиотеки, реализованные на базе PacketVideo OpenCORE. Используются для записи и воспроизведения аудио и видео контента, а также для вывода статических изображений. Поддерживаются форматы: MPEG4, H.264, MP3, AAC, AMR, JPG и PNG.
SQLite – это легковесная и производительная реляционная СУБД, используется в Android в качестве основного движка для работы с базами данных.
FreeType – это библиотека для работы с битовыми картами, для растеризации шрифтов и осуществления операций над ними.
LibWebCore –это библиотеки браузерного движка WebKit, используемого в браузерах Google Chrome и Apple Safari.
SGL (Skia Graphics Engine) – это открытый движок для работы с 2D-графикой. Графическая библиотека является продуктом Google и используется в других программах.
SSL – это библиотеки для поддержки одноименного криптографического протокола.
Libc – это стандартная библиотека языка С, настроенная для работы на устройствах на базе Linux.
Виджеты –это приложения, отображаемые в виде графического объекта на рабочем столе.
Активность –это видимая часть приложения,которая отвечает за отображение графического интерфейса пользователя.
Сервис – это компонент, который работает в фоновом режиме, выполняет длительные по времени операции или работу для удаленных процессов.
Контент-провайдер – это управление распределенным множеством данных мобильного приложения.
Приемники широковещательных сообщений (Broadcast Receivers) - это компонент, который реагирует на широковещательные извещения.
Intents – это системные сообщения, позволяющие приложениям обмениваться информацией между собой и с операционной системой.
Событие – взаимодействие пользователя с контентом, которое можно отслеживать независимо от просмотров страниц или экранов.
View –вид интерфейса, какими их видят пользователи.
ViewModel – это обработка данных и представление в UI, связывает View и Model.
Data Handlers – это подготовка данных из вида, в котором они передаются по сети или хранятся на диске, в модели, с которыми происходит работа в программе.
Мобильный web-сайт –специализированный сайт, который обладает адаптивным дизайном, который можно просматривать и на персональном компьютере и на мобильных устройствах.
Мобильное приложение –специализированное приложение, которое разработано для мобильных операционных систем. Такими операционными системами являются: ОС Windows Phone, ОС Android и ОС iOS.
XML (ExtensibleMarkupLanguage) - расширяемый язык разметки текстов или текстовых документов.
1. Wei-Meng Lee.“Beginning Android TM 4 Application Development”. Radha Offset, Delhi. 2013.
2. Wei-Meng Lee.“Android TM Application Development Cookbook”. Sharda Offset Press, Delhi. 2013.
3. Zigurd Mednieks, Laird Dornin, G. Blake Meike, Masumi Nakamura.“Programming Android Second Edition”. 2012.
4. Reto Meier.Professional Android 4 Application Development. John Wiley & Sons, 2013. – 816 p.
5. Wei-Meng Lee. Android™ Application development cookbook. John Wiley & Sons, 2013. – 410 p.
6. ГолощаповА. Л. GoogleAndroid: программированиедлямобильныхустройств. СПб.: БХВ-Петербург, 2011. - 448 с.
7. УсовВ. Swift. Основы разработки приложений под iOS. — СПб.: Питер, 2016. - 304 с.
8. П.Дейтел, Х.Дейтел, Э.Дейтел, М.Моргано. Android для программистов. Создаем приложения. PrenticeHall, -2013. – 560 c.
9. Колисниченко Д. Android для пользователя. Полезные программы и советы. СПб.: БХВ-Петербург, 2013. -256с.
10. ДощановаМ.Ю. «Программныесредствадлямобильныхустройств». Учебноепособие. – ТУИТ “Алоқачи”. 2020
Интернет сайты:
1. http://www.tutorialspoint.com/Android /index.htm
2. http://www.metanit.com
3. http://www.amazon.com
4. http://dl.e-book-free.com
5. https://flutter.su/
Оглавление
1. ОСНОВЫ РАЗРАБОТКИ И ЖИЗНЕННЫЙ ЦИКЛ МОБИЛЬНЫХ ПРИЛОЖЕНИЙ
1.1. Среды программирования для разработки мобильных приложений
1.1.1. Программные средства разработки мобильных приложений
1.2. Языкы программирования для разработки мобильных приложений
1.2.1. Язык Java для операционной системы Android
1.2.2. Язык Dart для операционной системы Android
1.3. Жизненный цикл мобильных приложений
1.3.1. Жизненный цикл приложения
1.3.2. Управление жизненным циклом
2. РАБОТА С МОБИЛЬНЫМИ УСТРОЙСТВАМИ
2.1. Понятие мобильной операционной системы
2.1.1.Понятие ОС в мобилных устройствах
2.2. Платформа и архитектура мобильных операционных систем
2.2.1. Платформа Андроид и его основа
2.2.2. Основные виды Android-приложений
2.2.3. Архитектура приложения, основные компоненты
2.2.4. Установка и настройка инструментальных средств
2.3. Языки программирования соответствующие платформам мобильных операционных систем
3. РАЗРАБОТКА ПРИЛОЖЕНИЙ НА ЯЗЫКЕ ПРОГРАММИРОВАНИЯ JAVA ДЛЯ ANDROID
3.1. Основные конструкции языка программирования Java
3.1.2. Специальные классы и функции
3.3. Разработка мобильных приложений на языке программирования Java
4. РАБОТА С БАЗАМИ ДАННЫХ В МОБИЛЬНЫХ ПРИЛОЖЕНИЯХ, РАБОТА С ГЕОЛОКАЦИЕЙ
4.1.1. Введение в базу данных в Android
4.2.1. Контент провайдеры и их использование
4.2.2. Обмен сообщениями. Messaging
4.3.Определение местоположения пользователя
4.3.1. Сетевое программирование в мобильных приложениях
4.3.2. Определение местоположения пользователя
5. РАБОТА С МОБИЛЬНЫМИ ДАТЧИКАМИ
5.1. Сенсорные возможности Android
5.1.1. Отличительные особенности смартфонов
5.2. Виды мобильных датчиков и работа с ними
5.3. Использование мобильных датчиков в мобильных приложениях
6. РАЗРАБОТКА ПРИЛОЖЕНИЙ НА ЯЗЫКЕ ПРОГРАММИРОВАНИЯ SWIFT ДЛЯ IOS
6.1. Основные констукции языка программирования Swift
6.1.2. Начало работы с Swift в XCode
6.1.3. Переменные и константы. Типы данных
6.1.4. Условная конструкция If. Тернарный оператор
6.1.7. Статические свойства и методы
6.2. Разработка мобильных приложений на языке программирования Swift