Создание сайтов для мобильных устройств с помощью Google App Engine
Август 5th, 2008Оценивается, что на сегодняшний момент в мире насчитывается около 1,3 миллиардов мобильных устройств. Можно ли использовать платформу App Engine для работы с ними? Коротким ответом на этот вопрос будет – да, конечно мы можем задействовать GAE для работы с мобильным контентом. Но для того, чтобы создавать мобильные приложения, которые действительно имеют хороший пользовательский интерфейс, необходимо будет использовать алгоритм по определению возможностей мобильных устройств.
Определение возможностей осуществляется для двух целей:
- Различение мобильных и десктопных браузеров. Скорее всего вы захотите иметь различные представления сайтов для пользователей, работающих с обычных компьютеров и с мобильных устройств.
- Отладить работу поведения сайта в каждом конкретном случае в зависимости от возможностей устройства.
На персональных компьютерах различия довольно минимальны: необходимо поддерживать 3 или 4 популярных браузера, все компьютеры имеют клавиатуру и мышь, и мы можем с большой степенью вероятности утверждать, что пользователи работают с разрешением 1024×768 и выше. При сравнении характеристик мобильных устройств существуют колоссальные различия: бесконечное количество различных браузеров, разрешений экрана и устройств ввода.
Чтобы уверенно поддерживать работу сайта на всем этом многообразии, необходимо иметь универсальный инструмент для определения возможностей мобильных устройств. Эта статья описывает интеграцию Google App Engine и базы данных устройств DeviceAtlas от dotMobi. Почему именно DeviceAtlas?
- Он содержит точную и всеобъемлющую информацию. База данных DeviceAtlas включает в себя информацию из различных источников: WURFL, Volantis, ArgoGroup и прочих
- Он достаточно быстрый – несколько тысяч запросов на умеренном аппаратном обеспечении
- Он бесплатен для разработчиков
Наша статья описывает процесс создания простейшего приложения для определения типа мобильного устройства и соответствующего ему вывода информации. Приложение будет выводить месторасположение офиса компании dotMobi в Дублине с помощью Google Maps, учитывая возможности целевого устройства:

Начинаем
Первое, что необходимо сделать – создать как обычно каталог нового приложения Google App Engine и его конфигурационный файл app.yaml. Мы назовем наше приложение – «contact-dotmobi».
Далее, необходимо добавить к приложению код определения мобильных устройств. Эта статья описывает использование интерфейса API DeviceAtlas для языка Python. Достаточно положить в каталог с приложением модули библиотеки и JSON-файл с базой данных устройств:
- api.py – основной модуль DeviceAtlas
- daExceptions.py -определения исключений
- DeviceAtlas.json – данные устройств
Обратите внимание, что библиотека зависит от модуля SimpleJson, который производит обработку файла данных. Так как он не включен в стандартную поставку платформы, то его необходимо добавить вручную. Наиболее простой способ это сделать – скачать его с сайта http://www.undefined.org/python/ и поместить каталог с модулем SimpleJson в каталог нашего приложения. После этого он будет выглядеть так:
api.py app.yaml daExceptions.py DeviceAtlas.json index.yaml simplejson
Теперь необходимо задать схему URL в конфигурации приложения и иконку нашего приложения favicon.ico. Содержимое файла app.yaml:
application: contact-dotmobi
version: 1
runtime: python
api_version: 1
handlers:
- url: /
script: contact-dotmobi.py
- url: /favicon.ico
static_files: favicon.ico
upload: favicon.ico
Пишем первый код
Конфигурация завершена, теперь необходимо создать файл с кодом contact-dotmobi.py. Начнем со стандартных импортов:
import wsgiref.handlers from google.appengine.ext import webapp from google.appengine.ext.webapp import template
Далее мы импортируем модуль DeviceAtlas:
И читаем данные устройств из файла данных:
TREEFILE = ‘DeviceAtlas.json’
da = api.DaApi()
tree = da.getTreeFromFile(TREEFILE)
Этот код создает экземпляр интерфейса библиотеки и загружает базу данных с конфигурациями мобильных устройств. Он выполняется лишь один раз и создает доступные для всех остальных запросов объекты.
Теперь когда данные готовы для работы, мы можем перейти к созданию обработчика запроса:
class MainPage(webapp.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/html' ua = self.request.user_agent
Здесь мы просто создаем потомок класса RequestHandler, устанавливаем HTTP заголовок ответа и определяем тип браузера пользователя по переменной, полученной из запроса. Самое интересное здесь как раз и является переменная ua, значение которой мы будем передавать интерфейсу DeviceAtlas:
props = da.getPropertiesAsTyped(tree, ua)
Это обращение приведет к поиску в данных объекта tree нашей строки с определением браузера пользователя. Метод getPropertiesAsTyped возвращает словарь значений, которые сообщают наиболее подробную информацию об устройстве пользователя. Теперь мы можем принять решение о формате вывода данных, прежде всего, является ли удаленное устройство мобильным:
mobileDevice = False if props.has_key('mobileDevice'): if props['mobileDevice']: mobileDevice = True
Далее мы определяем, какого размера изображение мы можем вывести на экран пользователя:
mapWidth = 512 # по умолчанию максимум для Google maps mapHeight = 512 if mobileDevice: mapWidth = 128 # используем, если нет информации о размерах экрана mapHeight = 128 if props.has_key('displayWidth'): # иначе определяем по ширине mapWidth = mapHeight = props['displayWidth'] - 25
Так как большинство мобильных устройств имеют портретную ориентацию, мы используем ширину экрана как наименьшую величину, устанавливая его высоту такой же. Уменьшая определенное значение на 25 пикселей, с учетом различных скроллеров, мы гарантируем что изображение не будет вылазить за пределы экрана.
Теперь мы знаем всю необходимую для отображения информацию и готовы сформировать словарь этих параметров и передать в шаблон:
template_values = { 'props': props, 'mapWidth': mapWidth, 'mapHeight': mapHeight, 'zoom': zoom, 'ua': ua, }
Шаблоны
В большинстве случаев предпочтительнее генерировать разметку в формате XHTML для мобильных устройств и обычный HTML для «настоящих» компьютеров. Обычно их стандартные браузеры меньше следуют стандартам и позволяют использовать больше вольностей в разметке страниц, что позволяет создавать богатый интерфейс. В нашем случае мы хотим использовать раздельные шаблоны для мобильных и стационарных устройств:
if mobileDevice: self.response.out.write(template.render('index.xhtml', template_values)) else: self.response.out.write(template.render('index.html', template_values))
Этот код передает параметры в шаблон, который создает ответ для устройства. Ниже приведен пример шаблона для мобильных устройств:
<?xml version=«1.0″ encoding=«ISO-8859-1″?>
<!DOCTYPE html PUBLIC «-//WAPFORUM//DTD XHTML Mobile 1.0//EN» «http://www.wapforum.org/DTD/xhtml-mobile10.dtd»>
<html xmlns=«http://www.w3.org/1999/xhtml»>
<head>
<title>Contact dotMobi</title>
</head>
<strong>Phone: </strong>
{% if props.uriSchemeTel %} <a href=«tel:+35318541100″>+353 (1) 854 110</a> {% else %} +353 (1) 854 1100 {% endif %}
<img src=«http://maps.google.com/staticmap?center=53.348592,-6.247315&zoom={{zoom}}&maptype=mobile&size={{mapWidth}}x{{mapHeight}}&markers=53.348592,-6.247315,blued&key=xxx» width=«{{mapWidth}}» height=«{{mapHeight}}» alt=«map» />
Detected UA string: {{ua}}
DeviceAtlas version: {{apiRevision}}
Vendor: {{props.vendor}} Model: {{props.model}>
В этом шаблоне есть три интересные вещи:
- мы используем атрибут uriSchemeTel из свойств, полученных от интерфейса DeviceAtlas, чтобы определить поддерживает ли устройство ссылку на номер телефона. Если да – отображаем ее
- размер изображения с картой Google определяется по шаблонной переменной mapWidth
- внизу таблицы мы выводим некоторую отладочную информацию: строка UA, версия библиотеки DeviceAtlas, производитель и модель мобильного устройства.
Версия шаблона для стационарных компьютеров (index.html) в отличии от мобильной очень простая и мы не будем ее здесь рассматривать. Что у нас получилось:

Запуск приложения
Теперь все что осталось, добавить основной код приложения:
application = webapp.WSGIApplication( [('/', MainPage)], debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == «__main__»:
main()
Теперь каталог с приложением выглядит так:
api.py
app.yaml
daExceptions.py
DeviceAtlas.json
favicon.ico
contact-dotmobi.py
index.html
index.xhtml
index.yaml
simplejson
Это все. Работу этого приложения «наживую» можно посмотреть на сайте http://contact-dotmobi.appspot.com/.
Другой пример
Разработчики, которые экспериментировали с DeviceAtlas предложили другой пример приложения, отображающего все доступные возможности текущего устройства:

Код этого приложения очень простой:
def get(self):
self.response.headers['Content-Type'] = ‘text/html’
ua = self.request.user_agent
apiRevision = da.getApiRevision()
props = da.getPropertiesAsTyped(tree, self.request.user_agent)
if props.has_key(‘vendor’) and props.has_key(‘model’):
imgSrc = ‘http://deviceatlas.com/sites/deviceatlas.com/themes/eris/img/device_images/%s/%s.jpg’ % (props['vendor'].lower(), props['model'].lower())
else:
imgSrc = False
template_values = {
‘apiRevision’: apiRevision,
‘props’: props,
‘imgSrc’: imgSrc,
‘ua’: ua,
‘t’: True,
‘f’: False,
}
Соответствующий файл с шаблоном выглядит так:
{% if imgSrc %}
<img src=«{{ imgSrc }}» alt=«Device Image» />
{% else %}
<p>No image available</p>
{% endif %}
<strong>Available Properties</strong>
<table style=«font-size:0.9em;»>
{% for prop in props.items %}
<tr style=«background-color: #{% cycle fff,f8f8f8 %};»>
<td>
<strong>{{ prop.0 }}</strong>
</td>
<td>
{% ifequal prop.1 t %}
<span style=«color: #0c0;»> {{ prop.1 }}</span>
{% else %}
{% ifequal prop.1 f %}
<span style=«color: #c00;»> {{ prop.1 }}</span>
{% else %}
{{ prop.1 }}
{% endifequal %}
{% endifequal %}
</td>
</tr>
{% endfor %}
</table>
Аналогично, посмотреть работу приложения можно по адресу http://deviceatlas.appspot.com/. Исходный код обоих приложений доступен здесь и здесь соответственно. После загрузки необходимо переименовать расширения файлов в обычные .zip.
Скачать библиотеку можно после регистрации на сайте производителя