Новый 2ГИС под Windows Phone: архитектура и стек технологий
Шел 2013 год. За доллар давали 30 рублей, а я устроился в компанию 2ГИС разрабатывать под Windows Phone. Мне удалось поучаствовать в запуске почти готового к тому времени приложения 2ГИС, которое в скором времени стало доступно нашим пользователям в Marketplace.
Была у этого приложения одна досадная особенность: оно работало на нашем WebAPI, и, соответственно, требовало подключения к Интернету. Поэтому почти сразу возникла необходимость научить 2ГИС под WP работать офлайн. А заодно решить другие насущные проблемы.
Старое приложение, несмотря на то что его опубликовали совсем недавно, не очень хорошо справлялось с требованиями пользователей и бизнеса. Требования выглядели примерно так.Быстрая доставка данных Предположим, 2ГИС узнал какую-нибудь новую информацию о вашем городе. Для того чтобы поделиться этой информацией с пользователями, мы проделывали такие вот действия.Учили приложение отображать новые данные. Все тестировали. Публиковали новую версию приложения в сторе. Выпускали обновление данных для города. Только после этого вы скачивали новую версию приложения, и узнавали, наконец, сколько стоит расчесать бороду в ближайшем барбершопе. Это я еще не говорю о проблемах версионирования данных и приложения, совместимости и всем таком. Быстрая доставка данных означает, что мы хотели бы вообще исключить из этого процесса пункты 1—3, и сделать тем самым обладателей бороды чуть более счастливыми.Быстрое появление новых фич Допустим, мы придумали новый алгоритм поиска, который ищет лучше и быстрее. Хотелось, чтобы этот алгоритм появился в продукте без существенных трудозатрат со стороны команды Windows Phone.Офлайн Как я уже говорил, для прежней версии приложения нужен был Интернет.
Некоторых наших пользователей это слегка расстраивало, и поэтому новый 2ГИС для WP обязательно должен был работать офлайн.
Ну вот, наша немногочисленная, но дружная команда уяснила основные требования, зарядилась с утра пораньше позитивом от свежих отзывов в сторе и начала разрабатывать. Первым делом, конечно же, архитектуру.Стоит сказать, что в 2ГИС мобильные приложение делают довольно давно, и для других платформ их сменилось уже несколько версий. Когда мы разрабатывали архитектуру, учли накопленный компанией опыт и требования, озвученные выше. Вот что получилось.
Кроссплатформенное ядро Верхний блок на картинке — это кроссплатформенное ядро, основа всего, точка входа во все наши алгоритмы и сервисы. Наш офлайновый бэкенд, которому мы задаем вопросы и получаем ответы. Эта штука обеспечивает нам работу без подключения к сети. Мы называем его кроссплатформенным, потому что можем собирать его не только под WP, но и под Windows, OS X, Linux, iOS и Android.В соответствии с требованием быстро добавлять новые фичи, все новое, что появляется в 2ГИС, появляется в ядре и автоматически попадает в новый 2ГИС для WP (как и во все продукты, которые это ядро используют). Кроссплатформенное ядро разрабатывается на C++ очень умными ребятами из специальной команды по разработке кроссплатформенного ядра. Они молодцы, но больше я про них ничего писать не буду, потому что статья не про них, а про про Windows Phone.
UI Самый нижний блок на диаграмме — это UI, фронтэнд, та часть, с которой работает пользователь. Она разрабатывается с помощью нативных для платформы инструментов (C# и XAML), чтобы максимально легко и качественно обеспечить пользователю привычный опыт взаимодействия с его любимой платформой. На момент написания этой статьи разработчикам было доступно несколько типов приложений под WP: Silverlight Application, Windows Phone (не Silverlight!) Application, Universal Application. Но когда мы только начинали работать над новым 2ГИС, был только Silverlight, поэтому неудивительно, что мы выбрали его.Промежуточный слой Для того чтобы подружить C++ и C#, научить ядро общаться с приложением, а приложение с ядром, необходим некий промежуточный слой в виде Windows Runtime Component, написанный на C++/CX. Продолжая разговор об офлайне, нельзя не сказать о нашей карте, которая тоже работает без подключения к интернету. Карта в 2ГИС — это довольно узнаваемый элемент. Она трехмерная, кроссплатформенная (тоже собирается под разные ОС) и написана на OpenGL.К сожалению, OpenGL не поддерживается на WP, поэтому нам приходится использовать Angle для трансляции OpenGL вызовов в DirectX. Хочу отметить, что с интеграцией карты мы натерпелись много разного, но в итоге удалось-таки ее запустить на WP. Конечно, использование Angle накладывает свой отпечаток на производительность, но мы стараемся свести это влияние к минимуму.
Как в любом другом приложении, написанном на C#/XAML, все внутреннее устройство фронтэнда 2ГИС дл WP подчиняется паттерну MVVM.У любого программиста, использующего MVVM, рано или поздно возникает потребность написать свой начать использовать какой-нибудь MVVM фрэймворк. Представляю вам очень краткую инструкцию по выбору MVVM фрэймворка, не претендующую на истину в последней инстанции.
Открываем любимый браузер. Например, Internet Exporer.
Заходим на сайт caliburnmicro.com.
Качаем Caliburn.Micro, устанавливаем и радуемся.
Если серьезно, то в 2013 году популярных MVVM фрэймворков, работающих на WP8 было не очень много. Фактически выбор стоял между Prism, MVVM Light и Caliburn.Micro. Prism слишком монструозный, подходящий больше для больших энтерпрайз приложений, MVVM Light, напротив, слишком уж light, и хотелось чего-то большего. А вот Caliburn.Micro нам пришелся по душе по следующим причинам.Поддержка навигации
Обычно навигация между страницами в silverlight приложении выглядит примерно так:
NavigationService.Navigate (new Uri (»/GroupPage.xaml? name=Administrators», UriKind.Relative));
Это не очень красиво, легко ошибиться при вводе строк, т.к. отсутствует типизация. Сам параметр name необходимо доставать внутри события onNavigated страницы в таком виде.
var name = NavigationContext.QueryString[«name»];
Caliburn.Micro позволяет ту же самую задачу решить следующим образом.
NavigationService.UriFor
Я не уверен, что кто-то еще так делает, но, по большому счету, здесь нет ничего фантастического. Мы какое-то время экспериментировали с различными вариантами и остановились на очень простой, на мой взгляд, схеме, о которой я постараюсь вам сейчас очень упрощенно рассказать.
Предположим, что отдельно от приложения мы распространяем вот такие данные, которые хотим отобразить.
{ «data»: «Windows Phone» } Тут ничего особенного — это просто json.Также мы распространяем (тоже отдельно от приложения) XAML-шаблон, необходимый для отображения этих данных.
Здесь стоит отметить несколько моментов.Этот шаблон на стороне приложения мы будем загружать в виде строки и парсить с помощью XamlReader.Load (). Из этого естественным образом вытекает то, что не любой XAML можно так использовать. Правильный XAML не должен содержать никакого code behind, никаких ссылок на x: Class, подписок на события и т.п.
Под предыдущее требование отлично подходят DataTemplates, поэтому мы будем использовать их.
Мы используем ResourceDictionary как контейнер для нескольких шаблонов.
Обратите внимание, как мы распространяем иконку TestIcon, — тоже в виде DataTemplate. А отображаем ее с помощью ContentPresenter.
Свойство Text текстового блока привязано к некоторому свойству data посредством привязки к индексатору некоторой ViewModel«и (о которой после). Обратите внимание, что в нашем json«е есть элемент с точно таким же именем, и это неспроста.
Допустим, уже в самом приложении у нас есть вот такая View.
В этой View элемент ContentControl является точкой входа для отображения динамических данных. Здесь есть важное соглашение: ContentControl знает, что должен отобразить шаблон с ключом EntryPoint, и именно такой шаблон есть в нашем XAML«е, который мы распространяем отдельно. Собственно, имя ключа — это единственное, что приложение знает о шаблоне, который будет показывать.Соответственно, для View определена ViewModel, которая реализует некоторую тестовую магию по загрузке динамического содержимого.
public class MainPageViewModel { public MainPageViewModel () { // Загружаем с секретных серверов 2ГИС данные о городе. string json = LoadDynamicData (); DynamicData = (JObject)JsonConvert.DeserializeObject (json);
// Загружаем шаблон для отображения данных. string xaml = LoadDynamicXaml ();
// Парсим xaml. var resources = (ResourceDictionary)XamlReader.Load (xaml);
// Добавляем шаблоны в ресурсы приложения. foreach (DictionaryEntry entry in resources) { Application.Current.Resources.Add (entry.Key, entry.Value); } }
public JObject DynamicData { get; private set; } } Поясню несколько моментов.В примере я использовал Json.Net для того, чтобы распарсить json и получить из него некоторый объект, пригодный для привязки данных. На самом деле для этих целей мы используем DynamicDataContext, но в данном примере для простоты используется JObject. У JObject есть индексатор, принимающий строку — имя json-элемента, и возвращающий значение этого элемента. Именно поэтому в шаблоне текст текстового блока привязывается к индексатору [data], который возвращает значение элемента data из json. Так обычный json становится вьюмоделью для нашего шаблона. Если собрать этот пример и запустить его на своем любимом телефоне под управлением Windows Phone, можно увидеть такую картинку.
Вот так легко и непринужденно мы только что отобразили в приложении данные, о которых приложение вообще ничего не знает. И как отображать эти данные приложение тоже не знает — вся информация загружается с серверов 2ГИС.
Мы сделали настоящий 2ГИС для WP: у нас есть трехмерная карта, подробный справочник организаций, кроссплатформенное ядро, а xaml-шаблоны мы распространяем независимо от приложения. И все это работает без подключения к Интернету. Если вы по каким-то причинам все еще не установили новый 2ГИС на ваши смартфоны с Windows Phone, самое время это сделать.