Сетевая недокроссплатформенность



    Здравствуйте! В этой статье я хотел бы поделиться своим опытом с начинающими разработчиками, которые учатся писать мобильные приложения, но еще не очень далеко продвинулись на этом поприще. Если быть точным — я бы хотел рассказать, как писать переносимый код и проектировать приложения, которые будут работать как на «родных» .NET-платформах (Windows Phone 7 и настольные приложения Windows), так и на портированных версиях .NET для мобильных платформ, таких как Monotouch и Monodroid.

    Немного о Mono и Portable Class Library


    Для того чтобы в деталях разобраться, как правильно писать переносимый код, сначала необходимо понять — как работает .NET на iOS и Android. Тема эта очень большая, поэтому, как говорит Владимир Владимирович, буду краток.

    Monotouch

    Mono на iOS работает следующим образом: никакого JIT-компилятора и никакого .NET на самой iOS. Весь код, абсолютно весь, в том числе и вся реализация .NET, — компилируется в нативный байт-код и «тянется» с дистрибутивом приложения.

    Отсюда вытекают следующие «плюсы, минусы и подводные грабли». Плюс — быстродействие (нативный код по определению быстрее). Минус № 1: объем дистрибутива приложения — минимум 6 Мбайт. Минус № 2: портирован, к сожалению, только профиль Silverlight 4, и то не полностью.

    Главная неожиданность: никаких генериков и никакой рефлексии. И следовательно — никаких игр с коллекциями, кастомными прокси-классами для WCF, биндингом данных а-ля WPF.

    Monodroid

    О .NET на Android нужно сказать следующее: на первый взгляд тут не все так печально: и JIT имеется, и генерики. Однако… Генерики — неполноценные (классы-параметры должны наследоваться от Java.Lang.Object), да и JIT — не совсем JIT. Тут тоже можно выделить тему для отдельной статьи, если Хабрасообществу будет интересна эта тема.

    Portable Class Library и Windows Phone 7

    Самыми широкими возможностями по использованию C# обладает, разумеется, мобильная платформа от Microsoft. Специально для нее была разработана версия Silverlight. Однако сборки, которые были скомпилированы для Windows Phone — не будут работать на десктопном приложении. Это понятно, хотя и очень неудобно: целевые платформы все-таки разные.

    Для адекватной поддержки переносимости кода между Windows Phone и полноценным .NET был реализован профиль Portable Class Library (PCL) — набор базовых классов, которые присутствуют на всех платформах, поддерживающих официальную версию .NET — Windows, Metro, Windows Phone, Silverlight и Xbox.

    Нюанс

    При разработке под Mono* вы можете прямо ссылаться на сборки, которые были скомпилированы для полноценных версий .NET до 3.5. И если ваша сборка использует только классы из поддерживаемых пространств имен — все отлично сработает. Если же вы попытаетесь обратиться к нереализованному классу, то просто получите исключение NotImplementedException; такова белая магия компилятора Monotouch.

    Впрочем, проект для Windows Phone 7 просто не позволит добавить ссылку на подобную сбору (что вполне логично). «А как же Portable Class Library?..» — спросите вы. Вот тут как раз и начинается черная магия: при попытке использовать в проектах Mono* сборки, скомпилированные под PCL или WP7, вы получите сообщение об ошибке: невозможно загрузить сборку, ссылающуюся на библиотеки для WP7 или PCL.

    Мне до сих пор непонятно, почему это реализовано именно так; на все вопросы по этому поводу техподдержка отвечает: «Coming soon». Радует лишь, что не так давно вышла новая версия MonoDevelop (среды разработки под Monotouch на Mac), в описании которой было явно указано, что частичная поддержка PCL осуществляется, однако я не смог понять — где именно.

    Реализация


    Итак, имея реализацию необходимых нам библиотек на всех интересующих нас платформах, мы не можем создать одну сборку для всех платформ, которая бы использовала необходимые классы. Нам придется компилировать две сборки — для Windows-платформ и для Mono-платформ.

    Но здесь мы снова сталкиваемся с небольшой проблемой. Самым удобным классом при реализации клиента для веб-сервиса является WebClient. Но его — по каким-то загадочным причинам — из второй редакции PCL убрали, хотя он реализован и на Windows Phone 7, и в настольных приложениях. Разработчики аргументируют это тем, что WebClient не поддерживается в Metro-приложениях. Поэтому если вы хотите разрабатывать приложение в том числе и для Metro — вам придется воспользоваться более низкоуровневыми классами WebHttpRequest и WebHttpResponse. Я же для простоты покажу пример реализации прокси-класса для работы с WebClient.

    Что нам потребуется:

    • Visual Studio 2010 SP 1;
    • Windows Phone SDK для Visual Studio 2010;
    • Monodroid для Visual Studio 2010;
    • MonoDevelop 3.0.2 на Mac;
    • Newtonsoft.Json.

    Приступаем к работе.

    1. Устанавливаем Windows Phone SDK и Monodroid. Создаем два проекта: первый проект — Windows Phone Library, второй — Android Class Library. Если вы захотите разрабатывать универсальную Windows-библиотеку — создайте Portable Class Library вместо Windows Phone Class Library. Но в этом случае для работы с веб-сервисом придется использовать все те же менее удобные HttpWebRequest и HttpWebResponse.

    2. Далее основную работу мы будем вести с Android Class Library. Для обеспечения совместимости этой сборки с проектом Monotouch необходимо убедиться, что в этой сборке нет лишних ссылок, специфичных для Monodroid. В проекте оставьте следующие ссылки:

    • System;
    • System.Net;
    • Newtonsoft.Json (библиотека для десериализации JSON).

    3. Предположим, что у нас есть некий сферический REST-сервис в вакууме, который расположен по адресу http://webservice:47154/rest/vendor и возвращает нам список производителей ПО.



    Для использования этого сервиса достаточно написать следующий код (создать класс, в который будет десериализоваться наш ответ веб-сервиса, и собственно сам прокси-класс, который будет запрашивать сервис).

     //Контракт производителя ПО
    
        public class Vendor
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
        }
    
    
        //Сам прокси-класс для работы с веб-сервисом
    
        public class VendorsProxy
        {
            private string _host;
    
            public VendorsProxy(string host)
            {
                _host = host;
            }
    
            public event VendorsEventHandler GetVendorsCompleted;
    
    
            public void GetVendors()
            {
                var client = new WebClient();
    
                client.DownloadStringCompleted += (s, e) =>
                                                      {
                                                          var vendors = JsonConvert.DeserializeObject<IEnumerable<Vendor>>(e.Result);
                                                          if (GetVendorsCompleted != null) GetVendorsCompleted(new VendorsEventArgs(vendors));
                                                      };
                client.DownloadStringAsync(new Uri(_host + @"rest/vendor"));
            }
        }
    
        public delegate void VendorsEventHandler(VendorsEventArgs vendors);
    
        public class VendorsEventArgs : EventArgs
        {
            public IEnumerable<Vendor> Vendors { get; private set; }
    
            public VendorsEventArgs(IEnumerable<Vendor> vendors)
            {
                Vendors = vendors;
            }
        }
    

    4. Теперь в проект WP Library достаточно добавить не сами файлы, а ссылки на файлы из проекта Android Class Library.


    Вот, собственно, и все. Теперь, если вы полностью соберете решение, на выходе окажутся две сборки, которые одинаково хорошо будут работать как в Windows Phone, так и на устройствах с iOS и Android. Вы сможете использовать ваш веб-сервис на любой платформе следующим образом.

    var proxy = new VendorsProxy("http://mywebservice:8080/");
    
                proxy.GetVendorsCompleted += (vendors) =>
                {
                    BindVedorsToVoewModel(vendors.Vendors);
                };
    
                proxy.GetVendors();
    

    Сомнения


    Конечно, можно поспорить о необходимости таких «танцев с бубном» для достижения подобной универсальности: наверняка ведь у каждой платформы будет своя специфика и свои оптимальные методы работы. Однако, если вы уверенный .NET-программист и вам необходимо быстро создать простое мобильное приложение, работающее с веб-сервисом, — данное решение может быть полезно.

    Если Хабрасообщество проявит интерес к этой теме, то в следующей статье я расскажу о методах защиты соединений в кроссплатформенных приложениях.

    Автор: Сергей Шулик, старший программист Positive Technologies.

    Comments 19

      +1
      А почему бы просто для общего ядра для wp7, monotouch, monodroid не написать разные *.csproj для одного и того же кода и референсить их? (без всяких add as link).
        0
        Как один из вариантов, но, по-моему, предложенный в посте более просто реализуем.
          0
          Мы, например, так и делаем. Правда приходится не забывать добавлять файлы во все проекты сразу, но в целом особых неудобств не доставляет.
          +3
          >Главная неожиданность: никаких генериков и никакой рефлексии.
          Это не так. Есть и дженерики и рефлексия.
          В дженериках не поддерживаются виртуальные методы.
          Рефлексия работает, кроме рантайм генерации кода.
            0
            Что касается дженериков – согласитесь, возможность переопределять виртуальные методы generic-типов – одно из их главных преимуществ в .NET. И отсутствие возможности реализовать IEnumerable очень сильно огорчает. Хотя тут да, вы правы, я погорячился, говоря что нет «никаких» генериков. Правильнее было бы сказать «большинства возможностей генериков».

            На счет рефлексии – вы правы, тут я то же погорячился как и в предыдущем примере :) Однако согласитесь, вряд ли в клиентских приложениях потребуется сложная логика по анализу классов без возможности работы с их экземплярами. Лично мне не приходит в голову сценарий использования этого функционала.
              0
              Работать с их экземплярами тоже можно будет. Не стоит путать Reflection и Emit.
              Reflection позволяет как инспектировать типы, так и создавать объекты нужного типа и вызывать произвольные методы. Все это возможно и на monotouch.
              Emit же дает возможность комплировать код на лету в monotouch он не доступен, но в mono for android есть.
            +3
            А можно попробовать разжевать для тупых? :) Ну т.е. вот у меня проект на C# под Win Phone, скажем. Какая должна быть последовательность шагов чтобы его заставить работать на других платформах? Какие встретятся подводные камни (ну, типа, «Если вы использовали ObservableCollection, то фиг вам а не моно»). Как могут себя повести сторонние компоненты? Как устроен GUI в портах моно и работает ли XAML (если работает, то как)?

            Вот такая статья была бы очень полезна.
              0
              GUI придётся полностью переписывать. На платформах доступны адаптированные фреймворки этих самых платформ.
                0
                Это огорчает больше всего.
                  +4
                  А меня, как пользователя, это радует. Я не хочу в своём телефоне (будь то iPhone, wp7, android) видеть интерфейс от другой платформы. У каждой из платформ свои гайдлайны, свои особенности навигации, свой пользовательский опыт.

                  Как разработчику, конечно хочется написать один раз, если учесть предыдущий факт, то оптимально было бы один раз написать бизнес-логику и работу с сервером, а экраны уже на каждую раскидать.
                  эх мечты, мечты...
                    –2
                    Особенности интерфейса можно было бы задать стилями, а заново писать все контролы + логику взаимодействия это значит писать с нуля 50% приложения. Учитывая, что у похожих сущностей на разных платформах могут быть свои милые особенности внутренних интерфейсов, которые не позволят осуществить даже простой порт привязки данных.
                      +1
                      Задать стилями? Я не знаю как панораму WP7 поменяв стили, можно было бы превратить в связку из ТабБарКонтроллера и НавигейшенКонтроллеров в iOS. Это не десктоп, где такой подход с примитивными окошками мог бы работать.
                        0
                        Особенности интерфейса может и можно было бы задать стилями, но это значит что вы забываете об особенностях UX. Вам уже ответили про навигацию в панораме wp7, могу привести пример гайдов для андроида, где описана навигация не существующая ни на iPhone, ни на wp7.
                        Теоритически, вы можете создать интерфейс, который при помощи стилизации будет органично смотреться на всех трех платформах, но практически у вашего приложения будет ограниченный UX и вы заведомо проиграете в этом вопросе конкурентам, которые заточили приложение под конкретную платформу.
                          0
                          В том то и дело, что проиграю с чужим UI я не заведомо, а лишь с какой-то вероятностью. Зато в принципе не выпустив приложение на других платформах (т.к. портирование стоит очень много усилий) я проиграю 100%
                            0
                            Если вы делаете очередное никому не нужное прилжение-рекламку, типа нафиг нам самим оно не надо, но это модно и что бы было, то да, вам интереснее использовать какой-нибудь phoneGap, а не писать все по 10 раз. Как только вы пишете что-то действительно нужное, вас начнут копировать. Понятно что в каждой конкретной ситуации нужно принимать отдельные решения, но ненативный интерфейс выглядит как издёвка над пользователем, а «стилизованный» попросту беден.
                        0
                        Почему мечты?
                        Моно какраз под такой сценарий и заточен — и подразумевает использование нативных компонентов на каждой системе.
                        Это не кроссплатформа в стиле Write Once — Run Everywhere, но я, как разработчик, готов платить эту цену за доступ ко всем возможностям каждой системы.
                          0
                          Честно, я только начал знакомиться с этой платформой, но уже заметил несколько не приятных моментов в реализации. Сказки все равно не получается, возникают вопросы по работе с БД, по winRT и много чего. Сказки нет, поэтому и мечты.
                    0
                    Можно и разжевать :)
                    Смотрите, при реализации кроссплатформенных приложений – главное, правильная архитектура, разделение кода на логические слои: GUI, бизнес-логика, слой коммуникации. В данной статье я по сути описал именно алгоритм создания такого слоя коммуникации.

                    Что же касается GUI – тут все плохо: для каждой платформы пишется свой GUI. Но если грамотно спроектировать приложение, то вам по сути и придется, что только перерисовать несколько вьюшек, если конечно не шибко замудренный интерфейс.

                    ObservableCollection — можно, но осторожно, все упирается в отсутствие нормальной поддержки generic-типов. :) Так что если вы будете работать с ObservableCollection как с необобщенной коллекцией – то попробовать можно, хотя я этого делать крайне не рекомендую. Лично я для этого написал свой класс, который реализует все не типизированные интерфейсы ObservableCollection, и горя не знаю:)
                    На счет XAML – должен вас огорчить. По сети давно ходят слухи, что некие энтузиасты работают над таким решением, но рабочего решения я так и не видел. Однако не все так печально: нативные библиотеки iOS и Andorid весьма богаты функционально и просты в использовании. А учитывая гайды по построению пользовательских интерфейсов – для каждой платформы должен быть свой интерфейс, учитывающий специфику платформы (поверьте, iOS пользователь, увидев на Айпаде Metro-интерфейс на долго впадет в ступор и врядли заъочет использовать ваше приложение).

                    Если эта тема в самом деле интересна – я могу написать по ней еще несколько статей.
                    –1
                    Не очень понял в чем смысл статьи если честно!
                    У разработчиков на net есть большой опыт написание библиотек для разных платформ WPF,SL,WP7. Вроде для тех кто писал кроссплатформенные сборки все очевидно

                    Делали сборку под каждую версию и определяли в них conditional compilation symbols специфичные для платформы а далее писали ровно следующее
                    #if NET
                    //net framework spec code
                    #endif
                    #if SL
                    //sl spec code
                    #endif

                    Так было с появления sl. Все прекрастно понимали что эти фреймворки не совсем одно и тоже. Что есть определенные множества пересекающиеся и не пересекающиеся.

                    Фактически Вы написали очень много текста, который легко сворачивается в 1 абзац

                    Написать 100% переносимую сборку не реально, если она сложнее hello world хоть на миллиметор

                    Only users with full accounts can post comments. Log in, please.