Платформа Android Ведущий семинара: Максим Лейкин, компания «МЕРА НН» План семинара 1. Android - FAQ 2. Инструменты Android-разработчика 3. Примеры приложений • Жизненный цикл приложения + AsyncTasks, Threads • Простые ресурсы, размещения • Элементы управления • Хранилища данных • Content Providers • Intents, receivers • Сервисы • LBS-приложения План семинара Часть 1. Android - FAQ 1.Что такое Android? 2.Кто его разрабатывает? 3.Какие версии Android существуют? 4.Какие аппаратные платформы поддерживаются? 5.Под какой лицензией распространяется Android? 6.В чем ключевые особенности Android? 7.Из чего состоит Android? 8.Какова доля Android на рынке мобильных платформ? 9.Какие существуют устройства на платформе Android? 10.Что такое Google Play? 11.В чем преимущества и недостатки платформы Android? Android Activity Lifecycle В любой момент времени всякая активность может находиться в одном из 4-х состояний: 1) Active – видна на экране и может взаимодействовать с пользователем 2) Paused – видна на экране, но не может взаимодействовать с пользователем (не имеет фокуса ввода), например по причине перекрытия всплывающим окном. Состояние сохраняется. Может быть уничтожена системой в случае серьезной нехватки памяти 3) Stopped – запущена, но не видна пользователю и не может взаимодействовать с ним. Состояние сохраняется. Может быть уничтожена системой в случае нехватки памяти 4) Dead – активность не была запущена, либо была снята с выполнения системой из-за нехватки ресурсов . Состояние не сохраняется. Android Activity Lifecycle Foreground lifetime Entire lifetime Visible lifetime Android Activity Lifecycle public class Activity extends ApplicationContext { protected void onCreate(Bundle savedInstanceState); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onPause(); protected void onStop(); protected void onDestroy(); } Внимание! Сохранять состояние активности надо в методе onPause(), а не в onSaveInstanceState(Bundle), последний может в некоторых случаях не вызываться. Android Activity Lifecycle Intents Intent – специальный объект, который используется для передачи сообщений между компонентами как внутри одного приложения так и между разными приложениями. С помощью Intents системе передается намерение совершить какоелибо действие и данные, с которыми надо совершить это действие. В ОС Android с помощью Intents можно делать следующее: - стартовать другие активности (явно или неявно) - передавать «широковещательные» сообщения операционной системе - получать «широковещательные» сообщения от операционной системы Intents – рекомендованный способ взаимдействия между компонентами даже внутри одного приложения. Intents Структура Intent 1) Обязательные параметры: • action – действие, которое нужно произвести, например ACTION_VIEW, ACTION_EDIT, etc. • data – элемент данных, над которым надо произвести action, задается с помощью Uri Intents Структура Intent 2) Необязательные параметры: • category – дополнительная информация о действии, которое нужно произвести (например, CATEGORY_LAUNCHER, СATEGORY_ALTERNATIVE) • type – позволяет явно указать MIME type данных, поставляемых с Intent (обычно извлекается из data) • component - позволяет явно указать имя класса, для выполнения Intent (обычно извлекается из action, data/type, categories). Если указан – перекрывает действие всех остальных атрибутов. • extras – дополнительные данные, в виде набора пар «ключзначение» Intents: запуск активностей Явно (explicitly): Intent intent = new Intent(MyActivity.this, MyOtherActivity.class); startActivity(intent); Внимание! Вызываемая активность д.б. прописана в AndroidManifest.xml Intents: запуск активностей Неявно (implicitly): Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(“tel:555-2368”)); startActivity(intent); Приложение SimpleCaller /res/values/strings.xml: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, SimpleCallerActivity!</string> <string name="app_name">SimpleCaller</string> <string name="call">Call</string> <string name="number">Number:</string> </resources> Приложение SimpleCaller /src/layout/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"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/number" /> <EditText android:id="@+id/Number" android:layout_width="103dp" android:layout_height="wrap_content" /> <Button android:layout_height="wrap_content" android:text="@string/call" android:layout_width="wrap_content" android:id="@+id/btnCall"> </Button> </LinearLayout> Приложение SimpleCaller /src/com/nnsu/mobileweek/SimpleCallerActivity.java: package com.nnsu.mobileweek; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; public class SimpleCallerActivity extends Activity implements OnClickListener{ /** Called when the activity is first created. */ Button btnCall; EditText numberField; Приложение SimpleCaller /src/com/nnsu/mobileweek/SimpleCallerActivity.java: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnCall = (Button)findViewById(R.id.btnCall); numberField = (EditText)findViewById(R.id.Number); btnCall.setOnClickListener(this); } private void phoneCall() { String phoneCallUri = "tel:" + numberField.getText().toString(); Intent phoneCallIntent = new Intent(Intent.ACTION_CALL); phoneCallIntent.setData(Uri.parse(phoneCallUri)); startActivity(phoneCallIntent); } public void onClick(View v) { phoneCall(); } } Приложение SimpleCaller AndroidManifest.xml: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.nnsu.mobileweek" android:versionCode="1" android:versionName="1.0"> <application android:label="@string/app_name" android:debuggable="true"> <activity android:name=".SimpleCallerActivity" 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> <uses-permission android:name="android.permission.CALL_PHONE" /> </manifest> Приложение SimpleCaller Приложение SimpleCaller Intents: Intent Filters Intent Filters предназначены для того, чтобы сообщить операционной системе, что компонент (активность, сервис) может принимать и обрабатывать Intents, c параметрами определенного типа. В приложении создается Intent, заполняются параметры action, data, category. С помощью startActivity() этот Intent отправляется на поиски подходящей Activity, которая сможет выполнить то, что определено параметрами Intent. В системе есть разные приложения, и в каждом из них несколько Activity. Для некоторых Activity определен Intent Filter т.д.), для некоторых нет. Метод startActivity() сверяет набор параметров Intent и наборы параметров Intent Filter для каждой Activity. Если наборы совпадают – Activity считается подходящей. Если в итоге нашлась только одна Activity – она и отображается. Если же нашлось несколько подходящих Activity, то пользователю выводится список, где он может сам выбрать какое приложение ему использовать. Intents: Intent Filters Для задания Intent Filter используется тег intent-filter в файле AndroidManifest.xml: <activity android:name=".TourViewActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.item/vnd.com.niit.android" /> </intent-filter> </activity> 1) action – задает действие, которое надо свершить или компонент, который надо запустить 2) category – определяет, при каких условиях надо совершить action 3) data – набор дополнительных данных, который используется при выборе компонента, выполняющего Intent, и передается ему для обработки Можно считать, что Intent – ключ, IntentFilter - замок Intents: Resolution Rules 1) Собирается список всех доступных Intent Filters 2) Intent Filters, которые не соответствуют действию или категории удаляются из списка. a) Совпадение происходит только в том случае, если Intent Filter содержит указанное действие (или если действие для него вовсе не задано). Совпадения не произойдет, только если ни одно из действий Intent Filter не будет эквивалентно тому, которое задано в Intent b) Для категорий процесс соответствия более строгий. Intent Filter должен включать в себя все категории, заданные в полученном Intent. Фильтр, для которого категории не указаны, может соответствовать только таким же Intent без категорий. 3) Каждая часть пути URI из Intent сравнивается с тегом data Intent Filter. Если в Intent Filter указаны схема (протокол), сервер/принадлежность, путь или тип MIME, все эти значения проверяются на соответствие пути URI из Intent. При любом несовпадении Фильтр будет удален из списка. Если в Intent Filter не указано ни одного параметра data, его действие будет распространяться на любые данные. Intents: Resolution Rules a) MIME — тип данных, который должен совпасть. При сравнении типов данных вы можете использовать маски, чтобы охватывать все подтипы (например, cats/*). Если в Intent Filter указан тип данных, он должен совпасть с тем, который значится в намерении, при отсутствии тега data подойдет любой тип. b) Схема — это протокольная часть пути URI, например http:, mailto: или tel:. c) Имя сервера (или принадлежность данных) — часть URI между схемой и самим путем (например, www.google.com). Чтобы совпало имя сервера, схема Intent Filter также должна подойти. d) После имени сервера идет путь к данным (например, /ig). Путь пройдет проверку только после схемы и имени сервера, содержащихся в теге. 4) Если вышеописанный процесс возвращает более одного совпадения, пользователю выводится список со всеми вариантами Sample 15 Intents: чтение Intent Если активность была запущена в результате совпадения Intent с IntentFilter, то она может получить Intent, с которым ее вызывали: public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Intent intent = getIntent(); String action = intent.getAction(); Uri data = intent.getData(); } Intents: передача Intent Можно передать Intent в активность, следующую в списке подходящих (предварительно выполнив проверки\обработки): Intent intent = getIntent(); if (isAfterMidnight) { startNextMatchingActivity(intent); } Intents: возврат результата из активности Основная активность: private static final int SHOW_SUBACTIVITY = 1; Intent intent = new Intent(this, MyOtherActivity.class); startActivityForResult(intent, SHOW_SUBACTIVITY); ... private static final int PICK_CONTACT_SUBACTIVITY = 2; Uri uri = Uri.parse(“content://contacts/people”); Intent intent = new Intent(Intent.ACTION_PICK, uri); startActivityForResult(intent, PICK_CONTACT_SUBACTIVITY); Intents: возврат результата из активности Вспомогательная активность Button okButton = (Button) findViewById(R.id.ok_button); okButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Uri data = Uri.parse(“content://vendors/” + vendor_id); Intent result = new Intent(null, data); result.putExtra(IS_INPUT_CORRECT, inputCorrect); setResult(RESULT_OK, result); finish(); } }); Intents: возврат результата из активности Обработка возвращенного результата: public void onActivityResult(int requestCode, int resultCode, Intent data) - requestCode – код, с которым была запущена активность (переданный в кач-ве второго параметра в startActivityForResult()) - resultCode – код, возвращенный из активности, обычно Activity.RESULT_OK или Activity.RESULT_CANCELLED - data – Intent, сорфмированный по результатам запуска sub-Activity Intents: возврат результата из активности private static final int SHOW_SUB_ACTIVITY_ONE = 1; private static final int SHOW_SUB_ACTIVITY_TWO = 2; public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch(requestCode) { case (SHOW_SUB_ACTIVITY_ONE) : if (resultCode == Activity.RESULT_OK) { Uri vendor = data.getData(); boolean inputCorrect = data.getBooleanExtra(IS_INPUT_CORRECT, false); } break; case (SHOW_SUB_ACTIVITY_TWO) : if (resultCode == Activity.RESULT_OK) { // TODO: Handle OK click. } break; } } Отладка Android-приложений Отладка из Eclipse: - The Debug Perspective - The DDMS Perspective (Dalvik Debug Monitor Server ) Отладка Android-приложений: Breakpoints Отладка Android-приложений: запуск в debug Отладка Android-приложений: Debug Perspective Variables – значения переменных (работает если есть установленные breakpoints) Debug – показывает Breakpoints – расставленные отлаживаемые приложения точки останова и выполняющиеся в данный момент потоки LogCat – Системные сообщения платформы (в т.ч. exceptions) Отладка Android-приложений: Debug Perspective Отладка Android-приложений: DDMS Perspective Threads – выполняющиеся потоки Tracker Emulator Allocation Tracker – – Allocation – Control выделение эмулятором памяти выделениеуправление памяти Heap – использование Devices – список динамической памяти подключенных эмуляторов и устройств Android-приложения работающие с картами и GPS Все устройства на платформе Android включают датчик GPS и встроенные средства работы с данными местоположения и картами. Устройства на платформе Android позволяют определять местоположение одним из 3-х способов: - датчик GPS - Cell-ID (триангуляция) - Wi-Fi spots Типовая задача: 1) Получить координаты с датчика GPS 2) Показать местоположение на карте Google Maps Google API vs. Android API Приложение LocationMaps /res/values/strings.xml: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, AndroidMapsActivity!</string> <string name="app_name">AndroidMaps</string> <string name="lat">Latitude</string> <string name="lon">Longitude</string> </resources> Приложение LocationMaps /src/layout/main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/bFindMe" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Find Me!" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/lat" /> Приложение LocationMaps <EditText android:id="@+id/etLatitude" android:layout_width="103dp" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/lon" /> <EditText android:id="@+id/etLongitude" android:layout_width="103dp" android:layout_height="wrap_content" /> <Button android:id="@+id/bShowMe" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show Me!" /> </LinearLayout> Приложение LocationMaps /src/com/nnsu/mobileweek/AndroidMapsActivity.java: package com.nnsu.mobileweek; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; Приложение LocationMaps /src/com/nnsu/mobileweek/AndroidMapsActivity.java: public class AndroidMapsActivity extends Activity implements LocationListener { EditText lat, lon; Button btnShow, btnFind; LocationManager locMgr; /** Called when the activity is first created. */ public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btnShow=(Button)findViewById(R.id.bShowMe); Button btnFind=(Button)findViewById(R.id.bFindMe); lat=(EditText)findViewById(R.id.etLatitude); lon=(EditText)findViewById(R.id.etLongitude); Приложение LocationMaps /src/com/nnsu/mobileweek/AndroidMapsActivity.java: btnShow.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { String _lat=lat.getText().toString(); String _lon=lon.getText().toString(); Uri uri=Uri.parse("geo:"+_lat+","+_lon); startActivity(new Intent(Intent.ACTION_VIEW, uri)); } }); btnFind.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { getFix(); } } ); } Приложение LocationMaps /src/com/nnsu/mobileweek/AndroidMapsActivity.java: public void getFix() { locMgr = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); locMgr.requestLocationUpdates (LocationManager.GPS_PROVIDER, 0L, 0, this); } public void onLocationChanged(final Location location) { this.runOnUiThread(new Runnable() { public void run() { lat.setText(new Double(location.getLatitude()).toString()); lon.setText(new Double(location.getLongitude()).toString()); } } ); } Приложение LocationMaps /src/com/nnsu/mobileweek/AndroidMapsActivity.java: public void onProviderDisabled(String provider) {} public void onProviderEnabled(String provider) {} public void onStatusChanged(String provider, int status, Bundle extras) {} } Приложение LocationMaps AndroidManifest.xml: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.nnsu.mobileweek" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="9" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".AndroidMapsActivity" 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> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> </manifest> Приложение LocationMaps Приложение LocationMaps