На сегодняшний день существует немалое количество обзорных статей о PhoneGap, но к сожалению, написаны они или front-end разработчиками, которые решили заняться мобильными платформами, или нативными программистами, которые решили попробовать себя в кроссплатформенной разработке. И именно с этих позиций рассматриваются достоинства и недостатки PhoneGap'а, возникают статьи о том, «насколько крута кроссплатформа», или об «ущербности кроссплатформенных решений».
В качестве затравки — видео демо-приложения, написанного за 6 часов; готовым был взят UI-бутстрап, наверстанный за 3,5 часа; использовались библиотеки iScroll, backbone, underscore, Jquery, и небольшая обертка на backbone (RAD.js — rapid application development, архитектурный фреймворк, берущий на себя часть оптимизации, связанной с мобильной средой выполнения).
Еще 2 часа было потрачено на фикс движка. Но сегодня речь не о том, что что-то тормозит, дергается, или самописный свайп не всегда вовремя отрабатывает на 14000 объектах данных; речь о том, что на PhoneGap можно и нужно писать.
Выносим на Ваше рассмотрение мнение людей, которые занимаются кроссплатформеной разработкой на PhoneGap, дабы рассказать о том, какие тонкости мы находим нужными и важными при разработке на PhoneGap, и почему они так важны.
PhoneGap позиционируется как платформа для кроссплатформенной разработки на HTML/JavaScript под семь мобильных операционных платформ.
Кроссплатформенность в PhoneGap достигается тем, что код Вашего HTML/JavaScript-приложения выполняется в компоненте webView (представляющего собой, встроенный в приложение браузер).
Фактически разработка PhoneGap-приложения представляет собой разработку HTML5-приложения с учетом особенностей среды выполнения (мобильное устройство, ограниченная процессорная мощность, память, тачскрин и т.д.) и… зоопарка браузеров.
Точно так, как при разработке под десктопные браузеры, при разработке мобильных сайтов или PhoneGap-приложений Вам необходимо знать и учитывать специфику каждой операционной системы и ее дефолтного браузера, на базе которого построен компонент webView.
И главное, на что влияет ограниченность мобильной среды выполнения PhoneGap-приложения и ресурсов, — это отзывчивость пользовательского интерфейса.
Для рядового пользователя не страшно, если на время выполнения каких-либо длительных операций программа заблокирует интерфейс, показав, к примеру, диалог с прогрессом выполнения задачи. Намного хуже, если пользователь подсознательно испытает дискомфорт, когда при нажатии кнопка отреагирует не сразу. Речь в данном случае идет даже не о секундах; человеку для ощущения подсознательного дискомфорта достаточно задержки более 150 миллисекунд между нажатием и реакцией программы. В этом случае наше подсознание просигнализирует о том, что интерфейс программы реагирует не так, как мы ожидаем.
Поэтому мы в своих программах пытаемся акцентировать внимание на отзывчивости пользовательского интерфейса. И в данный статье не будем говорить об оптимизации JavaScript-кода или CSS, так как это не специфично для PhoneGap и мобильных приложений, и, кроме этого, об этом уже немало написано(тут, тут, тут или тут).
Нам бы хотелось, наоборот, сделать акцент на специфике и тонкостях, на которые часто не обращают внимания, а в итоге это начинает играть решающую роль для пользователя.
Статьи, которые комплексно рассматривают взаимосвязь интерфейса и отзывчивости мобильного веб-приложения, найти непросто. Для примера можно привести эту статью, или же эту. Вторая статья достаточно свежая, и указывает направление «движения» для создания хорошего приложения на PhoneGap, но автор в ряде случаев использует «canvas based UI», что не всегда возможно).
Но и в этих статьях, к сожалению, рассмотрены не все проблемы.
Выделим проблемы, которые мы считаем характерными, и которые хотели бы рассмотреть сегодня:
- Задержка в 300 миллисекунд события «click» на «тач»-устройствах;
- Проблема касания
- Оптимизация DOM-структуры;
- Проблема больших списков.
Возможно в чем-то мы повторимся, но всеже начнем.
Проблема задержки в 300 мсек:
Большинство мобильных операционных систем в своем браузере (и как следствие, в webView) для решения проблемы ложных касаний вводят специальную задержку около 300 миллисекунд между непосредственным нажатием и запуском события «click» в DOM'e.
Подход к решению этой проблемы нетрудно найти на просторах интернета. Существует как минимум несколько десятков JavaScript-библиотек, решающих эту проблему. Принцип действия всех их одинаков — отслеживать события «touchstart» и «touchend» — и в момент окончания последнего вызывать событие «click» или аналогичное кастомное (например, «tap»). Примеры можно найти и как решение на stackoverflow, и как плагин к JQuery, и как отдельную библиотеку, причем даже в реализации от Mozilla или просто в качестве советов.
К сожалению, всегда есть небольшое «но» — если генерировать событие «click» (например, как советует компания google в своем решении), то через некоторую задержку Вам придется отловить и прекратить распространение стандартного «click», сгенерированного самим браузером. Что в старых версиях android может привести к несовместимости со сторонними библиотеками. При этом, если генерировать кастомное событие, то фокус будут передаваться с задержкой, и эта задержка будет видна при выборе полей ввода.
Найти же библиотеку, которая корректно передает и устанавливает фокусы при клике для элементов, и в то же время корректно работает с различными типами полей ввода, достаточно трудно и впридачу она будет довольно объемной. Намного проще и безболезненней использовать общепринятый подход — генерировать не событие «click», а другое любое кастомное событие — и подвязывать слушателя непосредственно к нему.
И хотя, в этом случае Ваше приложение и становится зависимым от данной библиотеки, этот недостаток с лихвой перекрывается простотой решения и наименьшим количеством багов.
Следует также предостеречь Вас от привязки отдельных экземпляров «fastclick» на каждый элемент UI, так как делают некоторые библиотеки. Это решение, хоть и работоспособное, но крайне затратное с точки зрения потребления ресурсов мобильного устройства. Необходимо использовать делегирование и привязку, например, на «body» слушателя и генерацию всплывающего кастомного события по координатам нативного.
Вторая проблема — точка касания:
Еще одна часто встречающаяся проблема в неправильно спроектированных интерфейсах мобильных устройств (в том числе и на PhoneGap) звучит из уст пользователей так: «я нажимаю, а оно не всегда срабатывает» или «я нажимаю, а оно не работает».
Давайте внимательно рассмотрим следующий рисунок, отображающий, как именно происходит взаимодействие пальца пользователя с экраном устройства.
Несложно заметить, что точка реального контакта подушечки пальца (зеленая) и точка визуального контакта (красная) отличаются. Во-первых, мы, как правило, смотрим на мобильное устройство под углом. Во-вторых, сам экран генерирует координаты касания как средневесовые координаты по площади касания подушечки пальца. Как бы мы ни старались, идеального круга от подушечки пальцев у нас не получится.
Именно по этим причинам, реальная точка касания никогда не совпадет с визуальной.
Большую часть решения этих проблем берет на себя операционная система, а именно, непосредственно сам драйвер тачскрина.
Но если не знать этих тонкостей, и не учитывать их при разработке интерфейса программы, то при достаточно мелких деталях интерфейса это становится для пользователя приложения ощутимой проблемой — нажатие происходит не всегда. И хотя пользователь абсолютно уверен, что он нажал точно на кнопку, реально же он просто нажал мимо.
Исправляется этот момент достаточно просто — грамотной версткой элементов управления приложения; например, когда кнопка имеет невидимый падинг в 4-6 пикселей если она недостаточно большая по дизайну: то есть ее бэкграунд имеет невидимые внешние границы, которые увеличивают ее физический размер до необходимого и удобного пользователю (розовое поле на рисунке).
Очень подробно эта проблема рассмотрена в здесь, а в рекомендациях компании Microsoft можно увидеть рекомендуемые размеры элементов интерфейса, в зависимости от размера экрана.
А теперь о самом нетривиальном — об оптимизации DOM-структуры
Если посмотреть на более-менее «стандартный» экран «среднего» приложения, то можно насчитать от 200-300 html тегов (узлов DOM), с помощью которых наверстан экран приложения. В данном конкретном примере страница наверстана с помощью 225 HTML-элементов. Далеко, не самая оптимальная верстка, но, как бы мы ни старались, в любом случае, в среднем экране PhoneGap-приложения можно насчитать от 100 до 250 элементов HTML. И попросим учесть, что мы пока не рассматриваем экраны со списками (об этом поговорим чуть-чуть позже).
Среднее приложение, аналогичное нативному, содержит от пяти до полутора десятков экранов. В случае PhoneGap, все эти старницы являются частью single-page приложения.
Существует общепринятый подход к архитектуре такого приложения: все страницы верстаются в одном html файле как блоки, а потом, в ходе выполнения программы, в зависимости от того, какая страница необходима, стилями
display: none;
display: block;
отображается нужная страница (так, например, делается в JqueryMobile ).
Но этот подход, к сожалению, приводит к задержкам в приложении при переключении страниц(и не только). И чем сложнее приложение, тем длительнее эти задержки.
Связано это с тем, что даже для самой обычной JavaScript-функции запроса к DOM, например, document.querySelector('selector'), время выполнения зависит от количества узлов в объектной модели документа. К примеру, указанная операция при трехуровневом селекторе выполняется за 0.003 мсек при 100 элементах и 0.36 мсек при 10000 элементах (ориентировочно 40 страниц приложения). Можно возразить, что это мелочи: 40 страниц не часто встречаются в реальной жизни, да и 0.36 мсек — копейки.
Но чистым JavaScript никто никогда практически не пользуется, а при использовании JQuery(до 2 версии) указанное время вырастает до 2.46 мс. А если учитывать, что кроме простого запроса необходимо также провести какие-либо манипуляции с DOM, то это уже может привести к заметным даже на глаз задержкам в достаточно сложном приложении.
Приведем небольшой СИНТЕТИЧЕСКИЙ тест, генерирующий документ с различным количеством элементов, и выводящий результат выборки двухуровнего CSS в консоль.
Результат его выполнения на Chrome 28.0.1500.63
start create 1 items index.js:12
none 1 items: 0.078ms index.js:38
none jquery 1 items: 0.774ms index.js:43
block 1 items: 0.131ms index.js:38
block jquery 1 items: 0.234ms index.js:43
jquery show1 items: 1.768ms index.js:52
start create 10 items index.js:12
none 10 items: 0.071ms index.js:38
none jquery 10 items: 0.138ms index.js:43
block 10 items: 0.139ms index.js:38
block jquery 10 items: 0.090ms index.js:43
jquery show10 items: 0.865ms index.js:52
start create 100 items index.js:12
none 100 items: 0.071ms index.js:38
none jquery 100 items: 0.099ms index.js:43
block 100 items: 0.103ms index.js:38
block jquery 100 items: 0.105ms index.js:43
jquery show100 items: 1.418ms index.js:52
start create 1000 items index.js:12
none 1000 items: 0.178ms index.js:38
none jquery 1000 items: 0.163ms index.js:43
block 1000 items: 0.156ms index.js:38
block jquery 1000 items: 0.176ms index.js:43
jquery show1000 items: 9.709ms index.js:52
start create 10000 items index.js:12
none 10000 items: 2.333ms index.js:38
none jquery 10000 items: 2.434ms index.js:43
block 10000 items: 1.810ms index.js:38
block jquery 10000 items: 1.810ms index.js:43
jquery show10000 items: 106.425ms
Как видно из теста, решение с «display: none» не помогает, так как это убирает работу только по отрисовке документа, но не меняет времени при запросах к «избыточному» DOM.
Это решение помогает нам избавиться только от reflow и repaint, поведение браузера при этих событиях великолепно рассмотрено в статье.
Еще раз напоминаем, что тест синтетический, не соответствующий реальным условиям, и некоторые позиции были специально «завалены». Но он прекрасно дает представление, что может произойти в реальном проекте с single-page приложением при большом количестве страниц и сложной верстке.
И хотя, время будет различаться на различных платформах (в различных браузерах), нетрудно увидить, что в мобильных браузерах ситуация будет еще хуже — в сложных одностраничных приложениях со стандартным подходом вы вряд ли получите плавную анимацию на мобильном устройстве.
В этом случае есть два варианта решения:
- Грузить содержимое страниц ajax-запросами;
- Если страница уже наверстана стандартным подходом, после ее загрузки в DOM нужно отсоединить ветви DOM, отвечающие за невидимые страницы от основного дерева DOM, сохранив ссылки на эти ветви в JavaScript (например с помощью api.jquery.com/detach/), в этом случае GC не очистит отсоединенные ветви.
Максимальной же оптимизации можно добиться, комбинируя эти два варианта. Тогда загружать страницу или шаблон Вам прийдется только в первый раз, в дальнейшем просто присоединяя или отсоединяя DOM ветви к основному дереву документа.
Не следует также забывать и об известных местах оптимизации.
И снова о DOM — проблема больших списков
Проблема больших списков является частью проблемы описанной в предыдущем разделе.
Чем больше элементов списка вы имеете, тем больше элементов HTML будет в странице, и тем медленнее будет происходить обновление и отрисовка списка.
В нативных компонентах это реализовано через кэш (пример реализации нативного компонента на Android).
Проблему можно решить или заменой данных в уже скрытом элементе и также перестановкой его позиции или удалением скрытого элемента списка и генерации нового. То есть, фактически на экране существует не список из 1000 элементов, а список из 10 видимых на экране элементов, и возможно несколько скрытых элементов, с которыми в данный момент происходит работа.
В веб-приложениях такую реализацию списков практически невозможно увидеть; как правило, все делается максимально просто: список из 1000 элементов. И если на десктопе это не настолько критично, то на мобильных платформах большие списки могут сыграть решающую роль в отказе от PhoneGap как платформы разработки.
В качестве решения данной проблемы можно порекомендовать «пейджинацию», то есть отображение списка по частям. Данное архитектурное решение реализуют множество js-библиотек; его можно очень красиво обыграть — например, свайпом.
Или же можно написать или найти JavaScript-библиотеку, реализующую функциональность аналогичную нативным компонентам; например, как сделано в InfiniteWall или здесь.
Напоследок хотелось бы дать несколько полезных советов, которые, как мы надеемся, помогут Вам сэкономить время и нервы:
- Чем меньше сторонних библиотек Вы используете, тем лучше, связано это с ограниченными ресурсами мобильного устройства. Для сравнения могу привести эскадру из «звездолетов» — сторонних библиотек. Вы хотите, чтобы эта эскадра перевезла один-единственный «груз» — выполнила бизнес-кейс Вашего приложения. Но топливо будет потреблять вся эскадра. Поэтому экономьте ресурсы (Ваше «топливо»), выбирайте библиотеки наиболее оптимально и пытайтесь наиболее полно использовать их возможности. К примеру, JQuery, начиная с версии 2, не на много уступает в производительности zepto, а функциональности намного больше.
- Ваше приложение совсем не обязательно должно выглядеть одинаково на всех платформах и версиях ОС. Возможно, стоит пойти на некоторые уступки в стилях и оформлении, чтобы сохранить быстродействие и функциональность программы. Вспомните ситуацию с IE;
- В списках не используйте атрибут «src» для тегов img, используйте CSS background для вывода изображения; в этом случае изображение будет подгружено только в том случае, если элемент списка виден на экране;
- Не всегда методы оптимизации, работающие в случае веб-страниц с клиент-серверной архитектурой, важны и оптимальны для приложения на PhoneGap. Например, нет смысла обращать внимание на на размер загружаемого JavaScript-файла или на минимизацию невидимых пикселей в изображении, так как в нашем случае нет долгих сетевых запросов для загрузки этих файлов. К сожалению, задержку в 250 миллисекунд перед загрузкой каждого файла никто не отменял, и Вам решать, как с этим бороться. Мы для себя выбрали ленивую предзагрузку и тайлы;
- Располагайте поля ввода в верхней части экрана — это позволит избежать различного поведения верстки страницы при отображении клавиатуры на мобильных устройствах;
- Избегайте больших списков — это сложная задача; беритесь за нее, когда у Вас уже есть хотя бы наметки для ее решения;
- Избавляйтесь по максимуму от теней, градиентов и полупрозрачности. Вся эта красота требует дополнительной мощности от мобильного устройства, поэтому используйте это только там, где действительно необходимо;
- Используйте мощность графического процессора через CSS, например так:
transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
В частности, можно почитать о конкретной реализации.
Или более подробно с тестами.
И еще несколько ссылок на статьи с советами: тут, тут или тут.
Но самое главное, что мы хотим сказать: На PhoneGap'e можно писать.
Хотя он не является панацеей для разработки. Каждый инструмент предназначен для своих задач. С помощью PhoneGap можно написать отзывчивое приложение и/или приложение с нестандартным дизайном (причем это даже легче сделать, чем при реализации нативным кодом). Но если Вам нужна обработка больших объемов данных, то скорее всего, PhoneGap Вам не подойдет, хотя иногда и с этим можно бороться.
Надеемся наши советы пригодятся.
P.S. Спустя 11 часов оптимизации UI баги остались, с теми же размерами кнопок, но приложению реально «полегчало».
Автор: YuriyLuchaninov