Основная задача проекта Necessitas — портирование Qt на платформу Android. Работа над проектом Necessitas была начата в 2009 году румынским программистом-энтузиастом Богданом Ватрой. Сейчас это динамически развивающийся проект, объединяющий

Описание: Технология облачных вычислений (cloud computing) требует как минимум двух компонентов: клиентской программы, которая выполняется на мобильном устройстве, и серверного программного обеспечения, работающего на сетевом сервере. В этой статье рассказывается о создании Android-сервиса, имитирующего сетевой сервер. Он откроет новые, неожиданные возможности вашего устройства. Поместив небольшое "облачко" в ваш карманный компьютер, вы превратите его в полезный локальный Web-сервер.
Описание: Android – это современная операционная система с открытым кодом, ориентированная на мобильные устройства и включающая в себя SDK, который позволяет создавать приложения с широким кругом возможностей. Возможности Android особенно привлекательны, если приложения должны взаимодействовать с Web-сервисами, поскольку в этом случае они обязаны понимать XML – своего рода язык Web. В этой статье рассказывается о нескольких различных вариантах работы с XML в Android, а также об их использовании при создании приложений.
Уровень сложности: средний
Содержание:
Прочитав эту статью, вы узнаете о создании Android-приложений, способных работать с XML в Интернете. Программы для Android пишутся на Java™, поэтому для понимания материала необходим опыт использования этого языка программирования. Кроме того, вам понадобится инструментарий разработки программного обеспечения для Android (SDK). Все примеры кода, приведенные в этой статье, могут работать с любой версией Android, хотя для их создания использовался SDK 1.5_pre. В принципе приложения для Android можно создавать, имея под рукой только SDK и текстовый редактор, но гораздо проще использовать специальный модуль ADT, подключаемый к Eclipse (Android Developer Tools - инструментарий Android-разработчика). В этой статье используется ADT 0.9 и Eclipse 3.4.2 Java Edition (ссылки на все программы приведены в разделе Ресурсы).
Android представляет собой платформу с открытым кодом для разработки приложений для мобильных устройств. С ее помощью можно получить доступ ко всем компонентам устройства, на котором выполняется эта ОС, начиная от низкоуровневого программирования графики и заканчивая использованием встроенной камеры. В Android есть столько всего интересного, что логично задать вопрос: а зачем вообще задумываться об XML? Однако интерес представляет не столько сам XML, сколько взаимодействие с другими объектами с его помощью. В частности, XML является распространенным форматом для обмена информацией в Интернете, поэтому велика вероятность, что он понадобится вам для доступа к данным в Web. Кроме того, XML может потребоваться для передачи данных, например, Web-сервису. Другими словами, если вы хотите, чтобы ваше приложение для Android работало через Интернет, вам скорее всего придется иметь дело с XML. К счастью, существует множество вариантов работы с XML в Android.
Одной из наиболее привлекательных черт платформы Android является использование языка программирования Java. SDK Android поддерживает не всю, но достаточно большую часть возможностей стандартной среды выполнения Java (Java Runtime Environment – JRE). Cама платформа Java уже долгое время поддерживает множество различных способов использования XML, причем большинство API для Java, ориентированных на XML, доступны в Android. Примерами таких API могут служить объектная модель документов (Document Object Model — DOM) и простой Java API для XML (Java's Simple API for XML – SAX), которые уже много лет являются частью технологии Java. Обратным примером является более новый потоковый API ( Streaming API for XML – StAX), который не поддерживается в Android (при этом в состав Android входит эквивалентная по своим возможностям библиотека). В Android также недоступен API для связывания с данными XML (Java XML Binding — JAXB). Его безусловно можно реализовать для данной платформы, однако он отличается некоторой тяжеловесностью, которая проявляется в том, что множество экземпляров разных классов часто требуется для представления документов XML. В связи с этим он является менее предпочтительным при создании приложений для портативных устройств, подобных тем, на которых работает Android. Далее мы рассмотрим в качестве примера простой источник XML-данных в Интернете, а также способы их разбора внутри Android-приложения при помощи перечисленных выше API. Мы начнем с рассмотрения основных компонентов простого приложения, работающего с XML-данными, полученными через Интернет.
Ниже будет рассмотрено приложение, получающее информационную ленту RSS с сайта Androidster, популярного среди Android-разработчиков, и трансформирующее ее в набор простых Java-объектов. Этот набор далее будет выступать в качестве содержимого компонента ListView (ссылка на исходный код приведена в разделе Загрузка). Приложение следует классическим принципам полиморфизма: его поведение будет оставаться внешне одинаковым при использовании различных алгоритмов разбора XML. Данная модель легко представима в Java, как показано в листинге 1.
Листинг 1. Интерфейс парсера XML-лент
package org.developerworks.android;
import java.util.List;
public interface FeedParser {
List<Message> parse();
}
|
В листинге 2 показан класс
Message, являющийся примером простого Java-объекта (Plain Old Java Object — POJO) и служащий для представления определенной структуры данных (сообщения).
public class Message implements Comparable<Message>{
static SimpleDateFormat FORMATTER =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
private String title;
private URL link;
private String description;
private Date date;
// get- и set-методы опущены для краткости
public void setLink(String link) {
try {
this.link = new URL(link);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public String getDate() {
return FORMATTER.format(this.date);
}
public void setDate(String date) {
// удлинение представления даты при необходимости
while (!date.endsWith("00")){
date += "0";
}
try {
this.date = FORMATTER.parse(date.trim());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
// реализация опущена для краткости
}
@Override
public int hashCode() {
// реализация опущена для краткости
}
@Override
public boolean equals(Object obj) {
// реализация опущена для краткости
}
// сортировка по дате
public int compareTo(Message another) {
if (another == null) return 1;
// сортировка по убывания, наиболее свежие записи выводят сверху
return another.date.compareTo(date);
}
}
|
Класс сообщения, показанный в листинге 2, достаточно прост. Он позволяет скрыть от внешних компонентов некоторые детали своего внутреннего состояния, в частности, он разрешает работать с датами и ссылками как со строковыми объектами, хотя хранятся они в строго типизированном виде (
java.util.Date и java.net.URL). Этот класс является классическим примером объекта-значения, обладающего собственной реализацией методов equals() и hashCode() на основе текущего состояния. Он также реализует интерфейс Comparable, служащий для сортировки (на практике этот метод не нужен, поскольку записи в ленте всегда упорядочены).
Каждая реализация парсера (алгоритма разбора XML) принимает на вход URL RSS-ленты Androidster и открывает HTTP-соединение с указанным сайтом. Эту функциональность логично вынести в абстрактный базовый Java-класс, как показано в листинге 3.
Листинг 3. Базовый класс парсеров лент
public abstract class BaseFeedParser implements FeedParser {
// Имена тегов XML
static final String PUB_DATE = "pubDate";
static final String DESCRIPTION = "description";
static final String LINK = "link";
static final String TITLE = "title";
static final String ITEM = "item";
final URL feedUrl;
protected BaseFeedParser(String feedUrl){
try {
this.feedUrl = new URL(feedUrl);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
protected InputStream getInputStream() {
try {
return feedUrl.openConnection().getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
|
Этот базовый класс хранит URL ленты в переменной
feedUrl и использует его для открытия потока ввода (java.io.InputStream). В случае возникновения любых ошибок он просто генерирует исключение типа RuntimeException, приводящее к быстрому аварийному завершению приложения. Кроме того, класс определяет ряд простых констант для имен тегов. Значение этих тегов иллюстрируется в листинге 4, в котором показан фрагмент информационной ленты.
<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="FeedCreator 1.7.2" -->
<rss version="2.0">
<channel>
<title>android_news</title>
<description>android_news</description>
<link>http://www.androidster.com/android_news.php</link>
<lastBuildDate>Sun, 19 Apr 2009 19:43:45 +0100</lastBuildDate>
<generator>FeedCreator 1.7.2</generator>
<item>
<title>Samsung S8000 to Run Android, Play DivX, Take Over the
World</title>
<link>http://www.androidster.com/android_news/samsung-s8000-to-run-android-
play-divx-take-over-the-world</link>
<description>More details have emerged on the first Samsung handset
to run Android. A yet-to-be announced phone called the S8000 is being
reported ...</description>
<pubDate>Thu, 16 Apr 2009 07:18:51 +0100</pubDate>
</item>
<item>
<title>Android Cupcake Update on the Horizon</title>
<link>http://www.androidster.com/android_news/android-cupcake-update-
on-the-horizon</link>
<description>After months of discovery and hearsay, the Android
build that we have all been waiting for is about to finally make it
out ...</description>
<pubDate>Tue, 14 Apr 2009 04:13:21 +0100</pubDate>
</item>
</channel>
</rss>
|
Как видно из примера в листинге 4, каждый элемент
ITEM соответствует экземпляру класса Message. При этом дочерние элементы записи (TITLE, LINK и т. д.) соответствуют свойствам Message. Теперь, когда вы получили представление о том, как выглядят ленты RSS, можно переходить к рассмотрению их разбора при помощи различных технологий, доступных в Android. В качестве первой такой технологии мы рассмотрим SAX.
В Java API SAX часто используется в тех случаях, когда требуется быстрый парсер и необходимо минимизировать расход памяти в приложении. Именно поэтому SAX очень привлекателен для использования в мобильных устройствах под управлением Android. При этом при создании приложений для Android этот API можно использовать в точности так же, как и в Java. Реализация интерфейса
FeedParser, основанная на SAX, показана в листинге 5.
Листинг 5. Реализация парсера на основе SAX
public class SaxFeedParser extends BaseFeedParser {
protected SaxFeedParser(String feedUrl){
super(feedUrl);
}
public List<Message> parse() {
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
RssHandler handler = new RssHandler();
parser.parse(this.getInputStream(), handler);
return handler.getMessages();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
|
Этот фрагмент кода должен выглядеть привычно для тех, у кого уже есть опыт использования SAX. Большая часть кода заключена в обработчике, что характерно для любого SAX-парсера. Обработчик получает уведомления о событиях от парсера по мере того, как тот разбирает документ XML. В данном случае в роли такого обработчика выступает класс
RssHandler (листинг 6).
Листинг 6. Обработчик событий SAX
import static org.developerworks.android.BaseFeedParser.*;
public class RssHandler extends DefaultHandler{
private List<Message> messages;
private Message currentMessage;
private StringBuilder builder;
public List<Message> getMessages(){
return this.messages;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null){
if (localName.equalsIgnoreCase(TITLE)){
currentMessage.setTitle(builder.toString());
} else if (localName.equalsIgnoreCase(LINK)){
currentMessage.setLink(builder.toString());
} else if (localName.equalsIgnoreCase(DESCRIPTION)){
currentMessage.setDescription(builder.toString());
} else if (localName.equalsIgnoreCase(PUB_DATE)){
currentMessage.setDate(builder.toString());
} else if (localName.equalsIgnoreCase(ITEM)){
messages.add(currentMessage);
}
builder.setLength(0);
}
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
messages = new ArrayList<Message>();
builder = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equalsIgnoreCase(ITEM)){
this.currentMessage = new Message();
}
}
}
|
Класс
RssHandler является наследником класса org.xml.sax.helpers.DefaultHandler, который предоставляет пустую реализацию по умолчанию для обработчиков всех событий, генерируемых SAX-парсером. Благодаря этому базовому классу дочерние классы могут переопределять только методы, которые соответствуют интересующим их событиям. RssHandler реализует еще один API, состоящий из метода getMessages. Этот метод возвращает список объектов типа Message, составляемый в процессе обработки событий, полученных от SAX-парсера. Кроме того, класс включает две внутренние переменные: currentMessage типа Message, представляющую собой текущее разбираемое сообщение, и builder типа StringBuilder, в которой хранится текстовое содержимое текстовых вершин. Обе эти переменные инициализируются в методе startDocument, который вызывается при начале разбора документа парсером.
Обратите внимание на метод
startElement в листинге 6. Он вызывается каждый раз, когда парсер встречает открывающий тег в документе XML. В нашем случае значение имеют только теги ITEM, для каждого из которых необходимо создать новый экземпляр типа Message. Далее рассмотрим метод characters. Он вызывается при разборе содержимого текстовых вершин в XML, которое просто добавляется в переменную builder. Наконец, взгляните на метод endElement, вызывающийся парсером при обнаружении закрывающего тега. В случае, если данный тег соответствует одному из атрибутов сообщения, например, TITLE или LINK, устанавливается значение соответствующего свойства объекта, хранящегося в переменной currentMessage. При этом значение берется из переменной builder. Если же закрывающим тегом является ITEM, то текущее сообщение (currentMessage) добавляется в список сообщений. Подобное поведение является классическим примером использования SAX, и в нем нет ничего специфичного для Android. Таким образом, если вы умеете писать SAX-парсеры на Java, то у вас не будет проблем с их созданием для Android. Более того, в SDK Android реализован ряд полезных функций в дополнение к SAX.
SDK Android включает вспомогательный класс
android.util.Xml. Его использование в парсере SAX показано в листинге 7.
Листинг 7. Реализация SAX-парсера в Android
public class AndroidSaxFeedParser extends BaseFeedParser {
public AndroidSaxFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
RssHandler handler = new RssHandler();
try {
Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8, handler);
} catch (Exception e) {
throw new RuntimeException(e);
}
return handler.getMessages();
}
}
|
Обратите внимание, что этот класс по-прежнему способен работать с любыми обработчиками SAX, поэтому в нем используется ранее созданный
RssHandler. Возможность повторного использования обработчиков является несомненным преимуществом, однако они могут оказаться чересчур сложными. Нетрудно представить, что если придется анализировать значительно более сложные документы XML, обработчик может легко превратиться в неиссякаемый источник ошибок. Например, взгляните еще раз на метод endElement в листинге 6 и обратите внимание на проверку переменной currentMessage на null перед установкой значения свойств. Теперь вернитесь к фрагменту XML, приведенному в листинге 4. Как видите, он включает элементы TITLE и LINK, находящиеся вне элемента ITEM. Именно этим объясняется проверка на null, поскольку иначе обработка первого же элемента TITLE приведет к исключению типа NullPointerException. К счастью, Android включает свой собственный вариант API SAX, благодаря которому можно не писать собственные обработчики. Пример приведен в листинге 8.
Листинг 8. Упрощенная реализация SAX-парсера для приложения Android
public class AndroidSaxFeedParser extends BaseFeedParser {
public AndroidSaxFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
final Message currentMessage = new Message();
RootElement root = new RootElement("rss");
final List<Message> messages = new ArrayList<Message>();
Element channel = root.getChild("channel");
Element item = channel.getChild(ITEM);
item.setEndElementListener(new EndElementListener(){
public void end() {
messages.add(currentMessage.copy());
}
});
item.getChild(TITLE).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setTitle(body);
}
});
item.getChild(LINK).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setLink(body);
}
});
item.getChild(DESCRIPTION).setEndTextElementListener(new
EndTextElementListener(){
public void end(String body) {
currentMessage.setDescription(body);
}
});
item.getChild(PUB_DATE).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setDate(body);
}
});
try {
Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8,
root.getContentHandler());
} catch (Exception e) {
throw new RuntimeException(e);
}
return messages;
}
}
|
Как и было обещано, в этом фрагменте кода не используются классы-обработчики SAX. Вместо этого парсер использует классы из пакета android.sax, входящего в состав SDK. Эти классы помогают моделировать структуру документов XML и добавлять слушатели событий по мере необходимости. В приведенном выше примере декларируется, что документ XML имеет корневую вершину
rss, у которой есть дочерняя вершина channel. Внутри нее располагаются элементы ITEM, для которых добавляются слушатели. Каждый из слушателей реализуется в виде анонимного внутреннего класса, реализующего специальный интерфейс (в данном случае либо EndElementListner, либо EndTextElementListener). Обратите внимание, что в этом подходе не приходится отдельно хранить текстовое содержимое, что не только проще, но и эффективнее. При этом обработчик, который передается в вспомогательный метод Xml.parse, автоматически определяется корневым элементом документа.
Подход, продемонстрированный в листинге 8, не является обязательным. Если вас устраивает стандартный способ работы с SAX в Java, вы можете использовать его. В противном случае вы можете работать с SAX через вспомогательные классы, предоставляемые SDK Android. Однако что делать, если вы вообще не хотите использовать SAX? В этом случае есть еще несколько вариантов, одним из которых является DOM.
Разбор документов XML по принципам DOM полностью поддерживается в Android. Этот API работает точно так же, как в Java-приложениях для серверов и настольных компьютеров. Реализация интерфейса парсера лент RSS на основе DOM показана в листинге 9.
Листинг 9. Реализация парсера XML-лент на основе DOM
public class DomFeedParser extends BaseFeedParser {
protected DomFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
List<Message> messages = new ArrayList<Message>();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document dom = builder.parse(this.getInputStream());
Element root = dom.getDocumentElement();
NodeList items = root.getElementsByTagName(ITEM);
for (int i=0;i<items.getLength();i++){
Message message = new Message();
Node item = items.item(i);
NodeList properties = item.getChildNodes();
for (int j=0;j<properties.getLength();j++){
Node property = properties.item(j);
String name = property.getNodeName();
if (name.equalsIgnoreCase(TITLE)){
message.setTitle(property.getFirstChild().getNodeValue());
} else if (name.equalsIgnoreCase(LINK)){
message.setLink(property.getFirstChild().getNodeValue());
} else if (name.equalsIgnoreCase(DESCRIPTION)){
StringBuilder text = new StringBuilder();
NodeList chars = property.getChildNodes();
for (int k=0;k<chars.getLength();k++){
text.append(chars.item(k).getNodeValue());
}
message.setDescription(text.toString());
} else if (name.equalsIgnoreCase(PUB_DATE)){
message.setDate(property.getFirstChild().getNodeValue());
}
}
messages.add(message);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return messages;
}
}
|
Как и в случае c SAX, в данном коде нет ничего специфичного для платформы Android. Парсер DOM считывает содержимое всего документа в память и предоставляет методы API для обхода дерева XML, позволяющие находить нужную информацию. Этот подход весьма очевиден и в некотором смысле проще реализаций, основанных на SAX. Однако использование DOM, как правило, требует больше памяти, поскольку в нее считываются все узлы XML. Это может представлять собой реальную проблему для портативных устройств под управлением Android, за исключением случаев разбора заведомо небольших документов XML. Учитывая это, нетрудно было предположить, что SAX должен быть значительно популярнее среди разработчиков Android-приложений, поэтому именно для него были созданы вспомогательные классы. Кроме SAX и DOM, платформа поддерживает и третий тип парсеров, а именно принимающие парсеры (pull parsers).
Как было замечено выше, Android не поддерживает API StAX. Однако в состав Android входит принимающий парсер, работающий аналогично StAX. Он позволяет вашему приложению принимать события от парсера в отличие от SAX-парсеров, которые автоматически передают события обработчику. Реализация принимающего парсера для анализа лент RSS показана в листинге 10.
Листинг 10. Реализация принимающего парсера
public class XmlPullFeedParser extends BaseFeedParser {
public XmlPullFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
List<Message> messages = null;
XmlPullParser parser = Xml.newPullParser();
try {
// автоматическое определение кодировки потока
parser.setInput(this.getInputStream(), null);
int eventType = parser.getEventType();
Message currentMessage = null;
boolean done = false;
while (eventType != XmlPullParser.END_DOCUMENT && !done){
String name = null;
switch (eventType){
case XmlPullParser.START_DOCUMENT:
messages = new ArrayList<Message>();
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(ITEM)){
currentMessage = new Message();
} else if (currentMessage != null){
if (name.equalsIgnoreCase(LINK)){
currentMessage.setLink(parser.nextText());
} else if (name.equalsIgnoreCase(DESCRIPTION)){
currentMessage.setDescription(parser.nextText());
} else if (name.equalsIgnoreCase(PUB_DATE)){
currentMessage.setDate(parser.nextText());
} else if (name.equalsIgnoreCase(TITLE)){
currentMessage.setTitle(parser.nextText());
}
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(ITEM) &&
currentMessage != null){
messages.add(currentMessage);
} else if (name.equalsIgnoreCase(CHANNEL)){
done = true;
}
break;
}
eventType = parser.next();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return messages;
}
}
|
Принцип работы принимающего парсера похож на SAX. Он оперирует теми же событиями (начало элемента, конец элемента), однако их необходимо запрашивать явным образом при помощи метода (
parser.next()). События имеют числовые идентификаторы (коды), поэтому для их выбора можно использовать оператор case-switch. Следует отметить, что вместо отслеживания закрывающих тегов, как в SAX, в случае принимающего парсера проще обрабатывать содержимое элемента при обнаружении открывающего тега. В листинге 10 приведен пример вызова метода parser.nextText() при обработке начала элемента для получения его текстового содержимого. Это существенно упрощает разбор документов по сравнению с SAX. Кроме того, обратите внимание на установку булева флага done, сигнализирующего об окончании обработки интересующего фрагмента документа. Это позволяет остановить процесс чтения документа XML если вы уверены, что в оставшейся его части нет ничего интересного для вашего приложения. Это весьма полезная возможность, особенно в тех случаях, когда требуется считать лишь малую часть документа. Подобные оптимизации играют весьма важную роль на мобильных устройствах, которые зачастую работают через медленные соединения. Таким образом, использование принимающего парсера может быть не только проще, но и выгоднее с точки зрения производительности. Наконец, он может использоваться для редактирования документов XML.
До этого момента мы рассматривали варианта разбора документов XML, полученных из Интернета. Однако существуют ситуации, в которых вашему приложению необходимо отправлять данные в формате XML удаленному серверу. Разумеется, вы можете просто создавать текстовое представление XML при помощи
StringBuilder или аналогичного класса. В качестве альтернативного решения можно воспользоваться принимающим парсером, как показано в листинге 11.
Листинг 11. Формирование документа XML при помощи принимающего парсера
private String writeXml(List<Message> messages){
XmlSerializer serializer = Xml.newSerializer();
StringWriter writer = new StringWriter();
try {
serializer.setOutput(writer);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "messages");
serializer.attribute("", "number", String.valueOf(messages.size()));
for (Message msg: messages){
serializer.startTag("", "message");
serializer.attribute("", "date", msg.getDate());
serializer.startTag("", "title");
serializer.text(msg.getTitle());
serializer.endTag("", "title");
serializer.startTag("", "url");
serializer.text(msg.getLink().toExternalForm());
serializer.endTag("", "url");
serializer.startTag("", "body");
serializer.text(msg.getDescription());
serializer.endTag("", "body");
serializer.endTag("", "message");
}
serializer.endTag("", "messages");
serializer.endDocument();
return writer.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
|
Класс
XmlSerializer находится в том же пакете, что и XmlPullParser, использовавшийся в предыдущем разделе. Однако вместо того, чтобы запрашивать события, он помещает их в поток или передает классу-писателю (в примере выше таковым является java.io.StringWriter). Парсер предоставляет простой API, содержащий методы начала и завершения документа, создания элементов, а также формирования текстового содержимого элементов и атрибутов. Этот подход обладает существенным преимуществом по сравнению со StringBuilder: с его помощью легче гарантировать синтаксическую корректность документа XML.
Вне зависимости от того, какое приложение вы создаете для Android, если ему требуется принимать или отправлять данные через Интернет, оно скорее всего должно будет уметь работать с XML. Как было продемонстрировано в этой статье, Android поддерживает множество технологий для обработки документов XML, поэтому вы можете делать свой выбор как на основе личных предпочтений, так и основываясь на конкретной ситуации. В большинстве случаев можно смело выбирать SAX и использовать этот API либо традиционным образом, либо через тонкий вспомогательный класс. Если вы работаете с небольшими документами XML, то, возможно, проще использовать DOM. В противном случае, а также если требуется обработать лишь часть документа, использование принимающего парсера XML может быть выгоднее из соображений производительности. Кроме того, принимающий парсер предоставляет удобные возможности для создания и редактирования документов. Таким образом, каковы бы ни были потребности вашего приложения в отношении работы с XML, вы всегда сможете найти нужную технологию в SDK Android.
| Имя | Размер |
|---|---|
| AndroidXml.zip | 70 KБ |
Научиться
Получить продукты и технологии
Описание: В этой статье мы научимся взаимодействовать через Android с различными датчиками в целях мониторинга окружающей среды.
Уровень сложности: средний
Содержание:
Платформа Android идеально подходит для создания инновационных приложений с использованием аппаратных датчиков, особенно Java-программистами. Мы рассмотрим некоторые варианты интерфейсов для приложений Android, включая использование подсистемы датчиков и записи аудиофрагментов.
Какое приложение лучше построить, чтобы задействовать аппаратные возможности устройства на платформе Android? Подойдет любое, в котором требуются электронные глаза и уши. На ум приходит электронная няня, система безопасности или даже сейсмограф. Хотя с метафизической точки зрения одновременно присутствовать в двух местах невозможно, Android может предложить некоторые практические способы преодоления этого ограничения. В рамках этой статьи под Android-устройством понимается не просто "мобильный телефон", а установленное в определенном месте устройство с беспроводным соединением с сетью, таким как EDGE или Wi-Fi. Загрузите исходный код примера приложения для этой статьи.
Один из приятных аспектов работы с платформой Android заключается в возможности получить доступ к некоторым полезным компонентам самого устройства. До сих пор разработчиков мобильных устройств разочаровывала невозможность доступа к их внутреннему оборудованию. Хотя между вами и металлом все же остается прослойка Java-среды Android, команда разработчиков Android вывела многие возможности аппаратуры на поверхность. А так как это платформа с открытым исходным кодом, можно засучить рукава и написать собственный код для решения своих задач.
Загрузите SDK Android, если вы этого еще не сделали. Рекомендуем также изучить содержимое пакета android.hardware и следить за примерами этой статьи. Пакет android.media package содержит классы, которые предоставляют разработчикам новые полезные функции.
Ниже описаны некоторые аппаратно-ориентированные функции, содержащиеся в SDK Android.
Таблица 1. Аппаратно-ориентированные функции SDK Android
| Функция | Описание |
|---|---|
|
Класс, позволяющий приложениям взаимодействовать с видеокамерой в целях фотосъемки, записи изображений с экрана предварительного просмотра или для изменения параметров настройки. |
|
Класс, обеспечивающий доступ к внутренним датчикам платформы Android. Не каждое устройство на платформе Android поддерживает все датчики из , однако интересно обдумать такие возможности. (Краткое описание имеющихся датчиков приведено ниже.) |
|
Интерфейс реализован с помощью класса, который используется для ввода значений датчиков по мере их изменения в режиме реального времени. Приложение реализует этот интерфейс для мониторинга одного или нескольких имеющихся аппаратных датчиков. Например, код из этой статьи содержит класс, который использует этот интерфейс для контроля ориентации устройства и показаний встроенного акселерометра. |
|
Класс, используемый для записи медиафрагментов, который можно применять для записи звуков в определенном месте (например, в детской комнате). Можно также анализировать аудиофрагменты для контроля доступа в помещение и в целях безопасности. Например, можно открывать дверь собственным голосом в обычное время своего прихода, вместо того, чтобы обращаться к консьержу за ключом. |
|
Класс, который позволяет распознавать лицо человека по хранящейся в памяти фотографии. Ничто не удостоверяет личность лучше, чем лицо. Если использовать его для блокировки устройства, вам больше не придется запоминать пароли – достаточно биометрических возможностей мобильного телефона. |
| android.os.* | Пакет, содержащий несколько полезных классов для взаимодействия с операционной средой, включая управление питанием, поиск файлов, обработчик и классы для обмена сообщениями. Как и многие другие портативные устройства, телефоны на базе Android могут потреблять достаточно много электроэнергии. Обеспечение "бодрствования" устройства в нужный момент, чтобы проконтролировать нужное событие, - важный аспект проектирования, заслуживающий особого внимания. |
|
При измерении событий реального мира часто имеют значение дата и время. Например, класс позволяет получить метку времени, когда происходит какое-либо событие или возникает определенное состояние. и можно использовать соответственно для выполнения периодических действий по расписанию или разового действия в определенный момент времени. |
Android.hardware.SensorManager содержит несколько констант, которые характеризуют различные аспекты системы датчиков Android, в том числе:
Центром сенсорных приложений служит интерфейс
SensorListener. Он включает в себя два необходимых метода:
onSensorChanged(int sensor,float values[]) вызывается всякий раз, когда изменяется значение датчика. Этот метод вызывается только для датчиков, контролируемых данным приложением (подробнее об этом ниже). В число аргументов метода входит целое, которое указывает, что значение датчика изменилось, и массив значений с плавающей запятой, отражающих собственно значение датчика. Некоторые датчики выдают только одно значение данных, тогда как другие предоставляют три значения с плавающей запятой. Датчики ориентации и акселерометр дают по три значения данных каждый.onAccuracyChanged(int sensor,int accuracy) вызывается при изменении точности показаний датчика. Аргументами служат два целых числа: одно указывает датчик, а другое соответствует новому значению точности этого датчика.Для взаимодействия с датчиком приложение должно зарегистрироваться на прием действий, связанных с одним или несколькими датчиками. Регистрация осуществляется с помощью метода
registerListener класса SensorManager. Пример кода для этой статьи демонстрирует, как приложение регистрируется и отменяет регистрацию с помощью SensorListener.
Помните, что не каждое устройство Android поддерживает тот или иной датчик, указанный в SDK. Если на конкретном устройстве тот или иной датчик отсутствует, ваше приложение должно обрабатывать эту ситуацию аккуратно.
Этот пример приложения просто следит за изменениями значений датчиков ориентации и акселерометра (исходный код приведен в разделе Загрузки). При изменении значений датчиков они отображаются на экране в виджете
TextView. Рисунок 1 демонстрирует приложение в действии.
Приложение создано в среде Eclipse с плагином Android Developer Tools. (Более подробную информацию о разработке Android-приложений с помощью Eclipse см. в разделе Ресурсы.) В листинге 1 приведен код этого приложения.
package com.msi.ibm.eyes;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.hardware.SensorManager;
import android.hardware.SensorListener;
public class IBMEyes extends Activity implements SensorListener {
final String tag = "IBMEyes";
SensorManager sm = null;
TextView xViewA = null;
TextView yViewA = null;
TextView zViewA = null;
TextView xViewO = null;
TextView yViewO = null;
TextView zViewO = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get reference to SensorManager
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
setContentView(R.layout.main);
xViewA = (TextView) findViewById(R.id.xbox);
yViewA = (TextView) findViewById(R.id.ybox);
zViewA = (TextView) findViewById(R.id.zbox);
xViewO = (TextView) findViewById(R.id.xboxo);
yViewO = (TextView) findViewById(R.id.yboxo);
zViewO = (TextView) findViewById(R.id.zboxo);
}
public void onSensorChanged(int sensor, float[] values) {
synchronized (this) {
Log.d(tag, "onSensorChanged: " + sensor + ", x: " +
values[0] + ", y: " + values[1] + ", z: " + values[2]);
if (sensor == SensorManager.SENSOR_ORIENTATION) {
xViewO.setText("Orientation X: " + values[0]);
yViewO.setText("Orientation Y: " + values[1]);
zViewO.setText("Orientation Z: " + values[2]);
}
if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
xViewA.setText("Accel X: " + values[0]);
yViewA.setText("Accel Y: " + values[1]);
zViewA.setText("Accel Z: " + values[2]);
}
}
}
public void onAccuracyChanged(int sensor, int accuracy) {
Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
}
@Override
protected void onResume() {
super.onResume();
// register this class as a listener for the orientation and accelerometer sensors
sm.registerListener(this,
SensorManager.SENSOR_ORIENTATION |SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onStop() {
// unregister listener
sm.unregisterListener(this);
super.onStop();
}
}
|
Это обычное приложение на основе действий, поскольку оно просто выводит на экран данные, получаемые от датчиков. Для ситуаций, когда устройство должно одновременно выполнять другие высокоприоритетные действия, приложение лучше было бы реализовать в форме сервиса.
Метод действия
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. Этот код не включает элементы пользовательского интерфейса приложения (полный исходный код см. в разделе загрузок).
Листинг 2. Запись аудиофрагмента
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.getName());
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).AMR_NB - аудиоформат с частотой дискретизации 8 кГц. NB означает узкую полосу частот. Различные форматы данных и имеющиеся кодеры рассматриваются в документации SDK.Аудиофайл хранится не во внутренней памяти, а на отдельной карте.
External.getExternalStorageDirectory() возвращает имена карты памяти и временного файла, созданного в этом каталоге. Затем этот файл связывается с экземпляром MediaRecorder, обращаясь к методу setOutputFile. Аудиоданные будут храниться в этом файле.
Вызов метода
prepare завершает инициализацию MediaRecorder. Когда нужно начать процесс записи, вызывается метод start. Запись в файл на карте памяти ведется до тех пор, пока не будет вызван метод stop. Этот метод освобождает ресурсы, выделенные экземпляру MediaRecorder.
Когда аудиофрагмент записан, можно выполнить несколько действий:
В примере кода метод
processaudiofile добавляет аудиозапись в медиатеку. Для уведомления встроенного приложения о том, что доступна новая информация, используется Intent.
И последнее замечание об этом фрагменте кода: сразу после создания он не будет записывать аудио. Вы увидите созданный файл, но не услышите звука. Нужно добавить разрешение в файл AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission> |
Теперь вы кое-что знаете о взаимодействии с датчиками Android и записи аудио. В следующем разделе приводится более широкий обзор архитектуры приложений, связанных со сбором данных и системами передачи сообщений.
Платформа Android располагает широкими возможностями по измерению параметров окружающей среды. При таком многообразии входных параметров и датчиков в сочетании с мощными вычислительными и сетевыми функциями Android становится привлекательной платформой для создания систем взаимодействия с реальным миром. На рисунке 2 упрощенно показано соотношение между входами, логикой приложений и методами уведомления или выходами.
Это очень гибкая архитектура; логику приложения можно распределять между локальным Android-устройством и серверными ресурсами, которые могут подключаться к более крупным базам данных и вычислительным мощностям. Например, аудиотрек, записанный на локальном Android-устройстве, можно отправить методом
POST на Web-сервер, где данные сопоставляются с базой данных образцов голоса. Ясно, что это только самое поверхностное знакомство с возможностями платформы. Надеюсь, что вы заинтересовались и начнете копать глубже, чтобы использовать платформу Android не только для целей мобильной телефонии.
Из этой статьи вы получили первое представление о работе с датчиками Android. Приведенные примеры приложений определяют ориентацию и ускорение, а также взаимодействуют со средствами звукозаписи с использованием класса
MediaRecorder. Android - гибкая, удобная платформа для создания систем взаимодействия с реальным миром. Сфера влияния Android очень быстро расширяется, и эта платформа осваивает все новые области применения. Не упускайте ее из вида.
| Описание | Имя | Размер |
|---|---|---|
| Исходный код примера Eyes | os-android-sensorEyes.zip | 28 КБ |
| Исходный код примера IBMAudio | os-android-sensorIBMAudio.zip | 33 КБ |
Научиться
Получить продукты и технологии
Обсудить
Когда Фрэнк Эйблсон (Frank Ableson) закончил карьеру баскетболиста в команде своего колледжа, не заключив многолетнего контракта с «Лос-Анджедес Лейкерс», он занялся разработкой компьютерных программ. Он любит решать сложные задачи, особенно из области связи и интерфейсов с аппаратурой. Свободное время Фрэнк проводит со своей женой Никки и детьми. С ним можно связаться по адресу: frank@cfgsolutions.com.
Описание: Android — это полноценная операционная среда, основанная на ядре Linux® V2.6. На начальном этапе областью распространения Android был сегмент мобильных телефонов, включая смартфоны и более дешевые раскладные устройства. Однако полный спектр вычислительных сервисов и богатые функциональные возможности Android позволяют выйти за рамки рынка мобильных телефонов. Android может быть полезен для других платформ и приложений. Настоящая статья дает введение в платформу Android и учит программированию простых приложений для этой платформы.
Уровень сложности: простой
Содержание:
BlackBerry и iPhone, две удобные и массовые мобильные платформы, олицетворяют собой противоположные концы спектра мобильных устройств. BlackBerry незаменим для корпоративного пользователя. В классе же потребительских устройств у iPhone мало конкурентов в части удобства обращения и «крутизны». Android, молодая и еще не утвердившаяся платформа, способна проявить себя на обоих концах спектра мобильных телефонов и, возможно, даже стать мостом через пропасть между работой и игрой.
Сегодня многие сетевые или поддерживающие сеть устройства работают на том или ином варианте ядра Linux. Это солидная платформа: недорогая в развертывании и поддержке и хорошо принимаемая благодаря удобному подходу к развертыванию. Пользовательский интерфейс таких устройств зачастую основан на HTML и может просматриваться с помощью браузера для РС или Mac. Но не всяким устройством нужно управлять посредством компьютера. Рассмотрим обычные бытовые приборы, такие, как плита, микроволновая печь или хлебопечка. Что если у ваших бытовых приборов была бы ОС Android и цветной сенсорный экран? Будь у плиты интерфейс Android, автор и сам, возможно, смог бы что-нибудь приготовить.
Из этой стати вы узнаете о платформе Android и о том, как ее можно использовать для мобильных и стационарных приложений. Мы установим SDK Android и создадим простую программу. Загрузите исходный код примера приложения для этой статьи.
Платформа Android является продуктом группы Open Handset Alliance, ставящей себе целью создание более совершенного мобильного телефона. Эта группа во главе с Google включает операторов мобильных сетей, производителей телефонов и компонентов, разработчиков программных решений и поставщиков услуг, а также маркетинговые компании. С точки зрения разработки программного обеспечения Android находится в самом центре мира открытого ПО.
Первым выпущенным на рынок телефоном на платформе Android стало устройство G1 производства HTC, которое распространяла T-Mobile. Это устройство появилось почти через год после того, как о нем пошли слухи, и единственным инструментом разработки программ для него были постепенно совершенствуемые последовательные выпуски SDK. Накануне выпуска G1 команда Android представила SDK v1.0, и начали появляться приложения для новой платформы.
Чтобы стимулировать инновации, Google спонсировала два «Конкурса разработчиков для Android», победители которых получили миллионы долларов. Через несколько месяцев после выхода G1 открылся сайт Android Market, откуда пользователи могли загружать приложения прямо в свой телефон. Всего за полтора года новая мобильная платформа вышла на арену.
По широте возможностей платформа Android не уступает операционным системам настольных ПК. Это многоуровневая среда на основе ядра Linux с богатыми функциональными возможностями. В подсистему пользовательского интерфейса входят:
Android содержит встраиваемый браузер на базе WebKit - того же механизма с открытым исходным кодом, который лежит в основе браузера Safari мобильного телефона iPhone.
Android обладает широким спектром возможностей подключения, охватывающим Wi-Fi, Bluetooth и протоколы передачи данных через сотовую сеть (GPRS, EDGE, 3G и др.). Популярным приемом в приложениях для Android является ссылка на Google Maps для отображения адреса непосредственно в приложении.В стек программного обеспечения Android входит и поддержка сервисов, основанных на определении местоположения (например, GPS), и акселерометров, хотя не все устройства на этой платформе оснащены необходимым оборудованием. Есть также поддержка видеокамеры.
Исторически двумя областями, где мобильные приложения отставали от своих настольных собратьев, были графика/мультимедиа и способы хранения данных. Android решает проблему графики благодаря встроенной поддержке 2-D и 3-D графики, включая библиотеку OpenGL. Задача хранения данных упрощается благодаря наличию в платформе Android популярной базы данных с открытым исходным кодом SQLite. На рисунке 1 показана упрощенная схема уровней программного обеспечения Android.
Как уже говорилось, Android работает поверх ядра Linux. Android-приложения пишутся на языке программирования Java и выполняются в виртуальной машине (VM). Важно отметить, что виртуальная машина – это не JVM, как можно было бы ожидать, а открытая технология Dalvik Virtual Machine. Каждое приложение Android запускается внутри экземпляра Dalvik VM, который, в свою очередь заключен в пределах управляемого ядром Linux процесса, как показано на рисунке 2.
Android-приложение содержит элементы одного или нескольких перечисленных ниже типов:
Приложение для Android развертывается на устройстве вместе с файлом AndroidManifest.xml. Этот файл содержит необходимую информацию о конфигурации, которая позволяет правильно установить приложение на устройстве. Он включает также необходимые имена классов и типы событий, которые может обрабатывать приложение, и разрешения, требуемые для его работы. Так, если приложению нужен доступ к сети – например, чтобы загрузить файл, - соответствующее разрешение должно быть явно указано в файле манифеста. Это конкретное разрешение могут иметь многие приложения. Такая защита путем декларирования помогает уменьшить вероятность повреждения устройства по вине некорректно написанного приложения.
В следующем разделе рассматривается среда разработки, необходимая для создания Android-приложений.
Самый простой способ приступить к разработке приложений для Android - это загрузить SDK Android и Eclipse IDE (см. Ресурсы). Разработку Android-приложений можно вести на платформах Microsoft® Windows®, Mac OS X или Linux.
В этой статье предполагается, что вы используете Eclipse IDE и плагин Android Developer Tools для Eclipse. Android-приложения пишутся на языке Java, но компилируются и выполняются в Dalvik VM (не в виртуальной машине Java). Кодирование на языке Java в рамках Eclipse – интуитивно понятный процесс; Eclipse предоставляет богатую среду Java, включая контекстно-зависимую справку и подсказки к коду. Когда ваш Java-код будет безошибочно скомпилирован, Android Developer Tools сам позаботится о том, чтобы приложение был надлежащим образом упаковано, в том числе снабдит его файлом AndroidManifest.xml.
Android-приложение можно написать и без Eclipse и плагина Android Developer Tools, но для этого нужно хорошо разбираться в Android SDK.
Android SDK распространяется в виде файла ZIP, который распаковывается в папку на жестком диске. Так как вышло несколько обновлений SDK, мы рекомендуем вам поддерживать среду разработки в порядке, чтобы можно было легко переключаться между разными установками SDK. В SDK входят:
adb (Android Debug Bridge).Android-приложения могут работать как на реальном устройстве, так и на эмуляторе Android, который прилагается к SDK Android. На рисунке 3 показан главный экран эмулятора Android.
Утилита
adb поддерживает несколько дополнительных аргументов командной строки, которые обеспечивают мощные функции, такие как копирование файлов в устройство и из него. Аргумент оболочки командной строки позволяет подключаться к самому телефону и подавать простые команды оболочки. Рисунок 4 иллюстрирует команду оболочки adb, подаваемую реальному устройству, подключенному к ноутбуку под Windows с помощью кабеля USB.
В рамках этой консоли можно:
lo - это локальное (петлевое) соединение.tiwlan0 - это соединение WiFi с адресом, предоставленным локальным сервером DHCP.PATH.su, чтобы стать суперпользователем.В этой же среде командной строки можно взаимодействовать с базами данных SQLite, запускать программы и решать многие другие задачи системного уровня. Это довольно примечательная функция с учетом того, что речь идет о подключении к телефону.
В следующем разделе мы создадим простое приложение для Android.
Этот раздел содержит краткий обзор процесса создания Android-приложения. Наш пример приложения предельно прост: это несколько видоизмененное приложение «Hello Android». Мы добавим незначительные изменения, чтобы сделать цвет фона экрана белым – тогда телефон можно будет использовать в качестве фонарика. Не очень оригинально, но как пример полезно. Загрузите весь исходный код.
Чтобы создать приложение в Eclipse, выберите File > New > Android project, что приведет к запуску мастера нового проекта Android (рисунок 5).
Теперь создадим простое приложение с одним действием, а также макет пользовательского интерфейса, который будет храниться в папке main.xml. Макет содержит текстовый элемент, который мы заменим на Android FlashLight (фонарик Android). Этот простой макет приведен в листинге 1.
<?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"
android:background="@color/all_white">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" android:textColor="@color/all_black"
android:gravity="center_horizontal"/>
</LinearLayout>
|
Создайте пару ресурсов цвета в strings.xml (листинг 2).
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Android FlashLight</string>
<string name="app_name">FlashLight</string>
<color name="all_white">#FFFFFF</color>
<color name="all_black">#000000</color>
</resources>
|
Цвет фона основного экрана определяется как
all_white. В файле strings.xml видно, что all_white соответствует значению триплета RGB #FFFFFF, что означает чисто белый цвет.
Макет содержит одно поле
TextView, которое на самом деле - просто нередактируемый фрагмент статического текста. Текст будет черным и отцентрованным по горизонтали с помощью атрибута gravity.
Приложение содержит исходный файл Java с именем FlashLight.java, как показано в листинге 3.
package com.msi.flashlight;
import android.app.Activity;
import android.os.Bundle;
public class FlashLight extends Activity {
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
|
Код создается непосредственно в мастере новых проектов:
onCreate, который передается в savedInstanceState. Не сомневайтесь по поводу использования этого пакета для наших целей; он применяется, когда действие приостановлено, а затем возобновляется.onCreate переопределяет метод класса действия с таким же именем. Он обращается к методу суперкласса onCreate.setContentView() относится к макету UI, определенному в файле main.xml. Содержимое main.xml и strings.xml, автоматически отображается на константы, определенные в исходном файле R.java. Никогда не редактируйте этот файл напрямую, так как он изменяется после каждой сборки.Запуск приложения приводит к отображению белого экрана с черным текстом (рисунок 6).
Настройка файла AndroidManifest.xml для приложения FlashLight показана в листинге 4.
Листинг 4. AndroidManifest.xml для приложения FlashLight
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.msi.flashlight"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".FlashLight"
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>
|
Этот файл создается автоматически плагином Android Developer Tools для Eclipse. Вам не нужно ничего делать.
Разумеется, это не очень мощное приложение. Но оно может пригодиться, если вы хотите что-нибудь почитать, не нарушая сна супруги, или если погас свет и надо найти путь к предохранителям в подвале.
При чтении этой статьи вы получили самые общие сведения о платформе Android и создали небольшое приложение. Надеюсь, этот пример произвел на вас достаточное впечатление, чтобы захотелось узнать о платформе побольше. Android обещает стать ведущей платформой с открытым исходным кодом, выходящей далеко за пределы рынка сотовых телефонов.
Исходный код FlashLight (zip, 24 Кб)
Научиться
Получить продукты и технологии
Обсудить
Когда Фрэнк Эйблсон (Frank Ableson) закончил карьеру баскетболиста в команде своего колледжа, не заключив многолетнего контракта с «Лос-Анджедес Лейкерс», он занялся разработкой компьютерных программ. Он любит решать сложные задачи, особенно из области связи и интерфейсов с аппаратурой. Свободное время Фрэнк проводит со своей женой Никки и детьми. С ним можно связаться по адресу: frank@cfgsolutions.com.
Источник: megadarja.blogspot.com
Вот мы и добрались до конца. Осталось сделать только главное меню приложения, а также сделать игре настройки (собственно, меню только для того и нужно, чтобы было откуда настройки вызывать). Ну первое мы еще с прошлой статьи умеем, так что особых сложностей быть не должно. А вот второе следует рассмотреть подробнее. Итак, приступим.
В одной из прошлых статей подробно рассматривался вопрос, как создавать формы в приложении для Android и делать переходы между ними. Так что особо останавливаться я не буду, и так все ясно.
На нашей форме приветствия будет какая-нибудь картинка и три кнопки: "Начать игру", "Настройки" и "Выход". Картинку в формате png, которую мы назовем
start.png нужно положить в папку /res/drawable. Названия кнопок нужно вынести в strings.xml, добавив следующие строки:
<resources> <string name="app_name">PingPong</string>
<string name="start_title">Start Game</string>
<string name="settings_title">Settings</string>
<string name="exit_title">Exit</string>
Тогда разметка для новой формы будет выглядеть так:
<?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" android:gravity="bottom" android:background="@drawable/start" android:padding="8dip"> <Button android:id="@+id/StartButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/start_title" /> <Button android:id="@+id/SettingsButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/settings_title" /> <Button android:id="@+id/ExitButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/exit_title" /> </LinearLayout>
Фоновое изображение можно задать экрану с помощью полезного свойства
android:background. Собственно, так можно задавать фон и кнопкам, и вообще чему угодно. Получили вот такую разметку:
Добавим соответствующий этой разметке класс
StartScreen.java. Сразу обработаем нажатия всех кнопок:
public class StartScreen extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.start); // Кнопка "Start" Button startButton = (Button)findViewById(R.id.StartButton); startButton.setOnClickListener(this); // Кнопка "Exit" Button exitButton = (Button)findViewById(R.id.ExitButton); exitButton.setOnClickListener(this); // Кнопка "Settings" Button settingsButton = (Button)findViewById(R.id.SettingsButton); settingsButton.setOnClickListener(this); } /** Обработка нажатия кнопок */ public void onClick(View v) { switch (v.getId()) { case R.id.StartButton: { Intent intent = new Intent(); intent.setClass(this, GameScreen.class); startActivity(intent); break; } case R.id.SettingsButton: { break; } case R.id.ExitButton: finish(); break; default: break; } } }
По нажатию на кнопку Start происходит переход на экран с игрой. Обратите внимание, что
StartScreen при этом не закрывается. Это значит, что, когда закроется StartScreen, мы попадем обратно на экран приветствия. По нажатию на Settings пока что ничего не происходит, по Exit — приложение закрывается.
Осталось только зарегистрировать эту форму в приложении и сделать ее главной. Для этого идем в
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.pingpong" android:versionCode="1" android:versionName="1.0.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".GameScreen" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.SAMPLE_CODE" /> </intent-filter> </activity>
<activity android:name=".StartScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Теперь приложение начинается с
StartScreen, все кнопки работают.
Я сделаю в настройках два параметра — максимальное количество очков и сложность. Сложность игры будем менять, варьируя скорость мячика и ракеток.
Сама форма с настройками делается достаточно просто. Есть специальный наследник класса
Activity — PreferenceActivity, который именно для этого и предназначен. Когда мы хотим сделать форму с настройками, нужно унаследоваться именно от него, и он возьмет на себя большую часть рутины.
Разметка для формы с настройками выглядит несколько необычно:
<?xml version="1.0" encoding="UTF-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceCategory android:title="@string/prefs_title"> <ListPreference android:key="@string/pref_difficulty" android:title="@string/difficulty_title" android:entries="@array/difficulty" android:entryValues="@array/difficulty" android:defaultValue="1" /> <EditTextPreference android:key="@string/pref_max_score" android:title="@string/score_title" android:defaultValue="10" /> </PreferenceCategory> </PreferenceScreen>
Настолько необычно, что, если поместить этот XML в папку layout, eclipse начнет ругаться, что не может разрезолвить такие классы. Собственно, это не просто разметка: там также содержатся ключи настроек и значения по умолчанию. Поэтому мы создадим отдельную папку xml, и поместим этот файл туда. А теперь обо всем по порядку.
Ну,
PreferenceScreen все понятно, а что такое PreferenceCategory? Как ни удивительно, это категория настроек. Например, у какой-нибудь игры могут быть настройки графики, настройки звука, настройки сети и т.д.. Удобно отобразить их сгруппированными, вот так:
А можно обойтись без групп:
PreferenceScreen уже сам по себе является контейнером для настроек. В нашем случае, например, настроек мало и группировать нечего. Но это я так, для полноты картины.
Как видно даже не прошлой картинке, настройки могут быть разными. И флажки, и текстовые поля, и списки. Все они происходят от одного класса
Preference, и наследуют от него всякие полезные атрибуты, которые можно задавать в разметке, как то:
Ну и еще кое-что. Рассмотрим некоторые конкретные виды настроек.
Вот такой флажок:
Редактор текста.

Честно говоря, у редактора текста очень хотелось бы валидатор, а еще лучше маску. Например, IP-адрес имеет специфический формат, и хотелось бы запрещать пользователю вводить там что попало. Но мне такую функциональность так и не удалось обнаружить.
Своеобразная реализация Dropdown-а. Хотя, на телефоне наверно и вправду так удобнее.

На этом контроле хотелось бы остановиться подробнее. А конкретнее, рассказать, откуда он, собственно, берет элементы списка. А берет он их из ресурсов с помощью таких атрибутов.
Здесь хранится ссылка на ресурс, в котором хранятся отображаемые элементы списка. Все значения, которые хочется вынести в ресурсы, хранятся в папке values. До сих пор там была только одна XML-ка —
strings.xml. Но теперь надо добавить еще одну — arrays.xml. И добавить в узел resources следующее:
<string-array name="performance"> <item>Best performance</item> <item>Normal performance and appearance</item> <item>Best appearance</item> </string-array>
После этого можно смело указывать в
android:entries этот ресурс, список будет загружен.
Кстати говоря, в
item-ах может быть не непосредственно строка, а ссылка на строку в strings.xml. Например, в нашем случае будет так (разумеется, стоит добавить соответствующие значения в strings.xml):
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="difficulty"> <item>@string/difficulty_easy</item> <item>@string/difficulty_normal</item> <item>@string/difficulty_hard</item> </string-array> </resources>
А еще мне никак не удалось победить у
ListPreference атрибут adnroid:defaultValue. Не работает и все.
Настройка звукового сигнала. В нашем приложении, например, можно было бы добавить звук, с которым мячик ударяется о стенку, и выбирать этот звук с помощью такого контрола. Выглядит это так:

Класс для формы с настройками будет выглядеть так:
public class SettingsScreen extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Настройки и их разметка загружаются из XML-файла addPreferencesFromResource(R.xml.preferences); } }
Кстати, настройки необязательно загружать из XML-ки, можно добавлять все эти настройки прямо в коде конструктора. В сэмплах, которые идут с Android SDK, такие примеры есть.
Добавляем в
StartScreen код для открытия формы настроек, прописываем SettingsScreen в AndroidManifest.xml. (Все выглядит точно так же, как и для GameScreen, так что листинги не привожу). И увидим мы следующее:

Не знаю, кому как, а мне нравится, когда рядом с названием опции написано ее значение. Но как это сделать автоматически, я так и не нашла, пришлось все делать руками, используя для этого поле
summary. Итак, summary настройки должно обновляться при изменении значения. По счастью, есть такое событие OnPreferenceChange. Итак, пишем:
public class SettingsScreen extends PreferenceActivity implements Preference.OnPreferenceChangeListener { /* Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.preferences);
ListPreference difficulty =
(ListPreference)this.findPreference("pref_difficulty");
difficulty.setSummary(difficulty.getEntry());
difficulty.setOnPreferenceChangeListener(this);
EditTextPreference maxScore =
(EditTextPreference)this.findPreference("pref_max_score");
maxScore.setSummary(maxScore.getText());
maxScore.setOnPreferenceChangeListener(this);
Думаю, все понятно без слов. Теперь мы видим такую картину:

И, если мы будем менять настройки, изменения сразу же будут отображаться в
summary.
Ну все, настройки мы сделали, они как-то сами где-то сохранились. Теперь возникла необходимость их прочитать и что-нибудь с ними сделать. Читать и делать мы будем в классе
GameManager, а конкретно, в конструкторе. Для работы с сохраненными настройками приложения используется класс SharedPreferences. Вся работа по чтению и применению настроек выглядит так:
public GameManager(SurfaceHolder surfaceHolder, Context context) { ... // стили для рисования игрового поля ... // игровые объекты ...
// применение настроек
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
String difficulty = settings.getString(res.getString(R.string.pref_difficulty),
res.getString(R.string.difficulty_normal));
setDifficulty(difficulty);
int maxScore = Integer.parseInt(settings.getString(
res.getString(R.string.pref_max_score), "10"));
setMaxScore(maxScore);
Метод
setDifficulty приводить не буду, там ничего особо умного не написано.
SharedPreferences можно читать с помощью методов getString, getInt, getBoolean и т.п. Все они принимают два параметра &mdahs; ключ к настройке (то, что мы задавали с помощью атрибута android:key) и значение по умолчанию. Однако, воспользоваться чем-то кроме getStringмне так и не удалось.
Итак, мануал закончен. Получился он огромным, но при этом собственно про андроид оказалось не так уж и много, что самое-то обидное :( Спасибо, если кто дочитал до конца. Буду рада услышать любые мнения.
Отдельное спасибо xeye и std.denis