Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Разработка IM на конкурс Павла Дурова с поддержкой Xamarin

Anna | 17.06.2014 | нет комментариев
Добросердечный день.

Как многие вероятно знают, Павел Дуров разрабатывает новейший клон What’s App и прочих знаменитых мессенджеров на базе своего собственного протокола MTProto.

Незадолго заокеанская компашка выпустила iOS заказчик под данный протокол под наименованием Telegram. Параллельно с этим проводится — конкурс на разработку Android заказчика.
Незадолго завершился 2-й этап, народ отправил свои поделки и я в том числе. Скажу сразу, 2-й этап я не прошел.

В различие от многих участников, для разработки я пользовался языком C# и Xamarin о чем и хочу рассказать подробнее ниже, так как по Xamarin в рунете информации скажем прямо немножко.

Заместо введения

Я дотнетчик. Я тружусь с дотнетом со 2-й версии, когда еще был студентом, отлично знаю вероятности и особенности этой платформы. Не так давным-давно мне захотелось разрабатывать на мобильные устройства, запрос «Android C#» и вывел меня на Xamarin — MonoDroid. Но до сих пор я писал только игрался с ним, это был 1-й солидный план на Android, о нем я и хочу рассказать. Для понимания этой статьи требуется познание C#, .NET и правда бы простое осознавание Android.

Что это — в 2-х словах

Xamarin это компания (а также мобильная платформа) сделанная Нэтом Фридменом и Мигелем де Икаса — автором GNOME и Mono. Таким образом Xamarin является логичным становлением Mono.
Xamarin разрешает писать нативные приложения для Anroid и iOS на C# и это восхитительно. Я лично считаю, что за гибридной кросс-платформой грядущее. А еще Mac. А еще энергично голосуют за MonoBerry, тот, что допустимо когда-то войдет в состав Xamarin.

Задача

В задачах конкурса была реализация предоставленного протокола MTProto (1-й этап) и создание полновесного приложения (2-й этап). На третьем этапе доработка.

Протокол в целом представляет из себя реализацию RPC со каждыми плюшками типа продвинутого шифрования и любое различное.

Решение

Тут и дальше я буду рассказывать как я решал эти задачи. Выходит, приступим.

Приобретение Xamarin

Xamarin стоит 2000$. Да, это так. Если вы хотите писать в любимой студии его цена — 999$ за платформу. Если вам хватит недурной среды MonoDevelop — ее стоимость 299$ за платформу. В переписке с авторами я сумел выклянчить скидку до 799$ за платформу.
Как же дозволено получить xamarin? Ну для начала его дозволено скачать с торрентов. Xamarin предлагает академическую лицензию за 99$ за платформу которая дает все вероятности Business помимо поддержки по почте. И да, если твоя жена аспирант это тоже работает.

Создание конструкции решения

Как я теснее упоминал Xamarin создает нативный код для всякой мобильной ОС. Это значит что под всякую ОС будет своя сборка, но код между ними должен быть поделен. Создатели Xamarin предлагают целых три методакак это сделать, но для Visual Studio самый примитивный — очаровательная утилита Project Linker, встраиваемая в непринужденно в среду.
Пара кликов мышки и кроссплатформенное решение готово:

Все файлы подключены как ссылки, всякие метаморфозы в основном плане будут отображены во все привязанные планы.

Утилитка ставиться из «Диспетчера растяжений» студии.

Конструкция решения

Основные сборки это MTProto.Core и Talks.Backend. Это обыкновенные сборки под .net 4.5 и покрытые Unit-тестами.
Mono.Stub — это несколько специфических классов из Mono, в частности я использую оттуда BigInteger.
Папка Droid — содержит в себе андройдовские клоны планов, Dataflow — это исходники TPL.Dataflow с гитхаба. Я энергично использую в своем плане асинхронные вероятности C# 5.0.
В папке Platfrom — определенные реализации под всякую платформу. Пока это только Android.

MTProto.Core

Это реализация протокола. Протокол в целом представляет из себя RPC с продвинутым шифрованием и некоторыми дополнительными вероятностями, типа образования контейнера либо отправки/получения файлов ломтиками. Таким образом для реализации IM заказчика нам нужно обучиться исполнять RPC запрос, получать результат, а так же обрабатывать входящие системные сообщения и обновления состояний.

Из особенностей: async all the way и Dataflow.

async all the way

C# 5.0 ввел пару ключевых слов которые Исключительно упрощают разработку асинхронного кода на основании Task Asynchronous Pattern (TAP). Дюже отлично они описаны в MSDN.
Все операции IO обязаны быть асинхронными, это должно быть как заповедь.

public async Task RunAsync()
{
	await _cl.LoadSettings().ConfigureAwait(false);
	if (await _cl.CheckAndGenerateAuth().ConfigureAwait(false))
	{
		await _cl.RunAsync().ConfigureAwait(false);
	}

	if ((_cl.Settings.DataCenters == null) || (_cl.Settings.DataCenters.Count == 0))
	{
		await _cl.GetConfig().ConfigureAwait(false);
	}

	_db = await TalksDatabase.GetDatabase().ConfigureAwait(false); 
	_ldm = new LocalDataManager(_db);
	_cl.ProcessUpdateAsync = ProcessUpdateAsync;
}

Стивен Клири — один из ведущих экспертов по асинхронному программированию на C# — написал несколькотезисов применения async-await которые теснее де-факто стали эталонами его применения. Если вы еще не читали, советую.

Суть подхода «async all the way» в том что все способы по дереву вызовов асинхронны начиная с event и заканчивая непринужденно операцией IO (в данном случае).

Скажем если нужно асинхронно обработать клик по кнопке:

async void button_Click(object sender, EventArgs e)
{
     _button.Enabled = false;
     await _presenter.SendMessage();
}

То вы делаете асинхронными все способы по дереву вызовов в Presenter:

Код

public Task<bool> SendMessage()
{            
    return SendMessageToUser();
}
public async Task<bool> SendMessageToUser()
{
   ...
    try
    {
        _imv.AddMineMessage(msg);
        string msgText = _imv.PendingMessage;
        _imv.PendingMessage = "";

        // messages.sendMessage#4cde0aab peer:InputPeer message:string random_id:long = messages.SentMessage;
        var result = await _model.PerformRpcCall("messages.sendMessage",
            InputPeerFactory.CreatePeer(_model, PeerType.inputPeerContact, _imv.ChatId),
            msgText,
            LongRandom(r));

        if (result.Success)
        {
            // messages.sentMessage#d1f4d35c id:int date:int pts:int seq:int = messages.SentMessage;
            msg.Id = result.Answer.ExtractValue<int>("id");
            ...
            msg.State = BL.Messages.MessageState.Sent;

            _imv.IvalidateList();                        
            await _model.ProcessSentMessage(result.Answer, _imv.ChatId, msg);
            return true;
        }
        else
        {
            msg.State = BL.Messages.MessageState.Failed;
            _imv.SendSmallMessage("Problem sending message: "   result.Error.ToString());
            return false;
        }
    }
    catch (Exception ex)
    {
         ...
    }
}

И в Core:

public Task<RpcAnswer> PerformRpcCall(string combinatorName, params object[] pars)
{
	return _cl.PerformRpcCall(combinatorName, pars);
}
public async Task<RpcAnswer> PerformRpcCall(string combinatorName, params object[] pars)
{
	try
	{
                /*...*/
		var confirm = CreateConfirm();

		// Буфер для результата
		WriteOnceBlock<RpcAnswer> answer = new WriteOnceBlock<RpcAnswer>(e => e);

		IOutputCombinator oc;
		if (confirm != null)
		{
			var cntrn = new MsgContainer();
			cntrn.Add(rpccall);

			// прикрепим к RPC Call все ждущие подтверждения
			cntrn.Add(confirm);
			cntrn.Combinator = _tlc.Decompose(0x73f1f8dc);

			// Добавим в всеобщую очередь 
			oc = new OutputMsgContainer(uniqueId, cntrn);
}
		else // не используем контейнер
		{
			oc = new OutputTLCombinatorInstance(uniqueId, rpccall);
		}

		var uhoo = await SendRpcCallAsync(oc).ConfigureAwait(false);

		_inputAnswersBuffer.LinkTo(answer, new DataflowLinkOptions { MaxMessages = 1 },
			i => i.SessionId == _em.SessionId);

		return await answer.ReceiveAsync(TimeSpan.FromSeconds(60)).ConfigureAwait(false); // таймаут если результата нет слишком долго				
	}
	catch (Exception ex)
	{
                ...
	}
}

Как видите вдалеке не все способы помечены ключевыми словами async-await. Всеобщая практика такова: если вам не необходимо ничего делать позже асинхронного вызова и если у вас один асинхронный вызов то имеет толк легко воротить его как Task из способа.
Еще одна практика (так же описанная в статье Клири) — асинхронные способы внутри библиотек не обязаны захватывать контекст и пытаться возвратиться к нему позже выполнения. Т.е. все асинхронные вызовы обязаны содержать .ConfigureAwait(false) Сделано это Дабы недопустить deadlock’и. Подробнее об этом дозволено прочитать в статье выше.

Dataflow

TPL.Dataflow — это библиотека разработанная для реализации образца проектирования Data Flow либо конвейера обработки. Начальный код библиотеки доступен на гитхабе что разрешает применять ее и на мобильных устройствах. Желающих поподробнее узнать о вероятностях этой библиотеки отправляю в MSDN.

В 2-х словах, библиотека разрешает возвести конвейер состоящий из блоков хранения либо обработки данных, связав их по какому-либо условию. Первоначально в моем плане таких конвейеров было две штуки: для входных и для выходных пакетов. Позже рефакторинга я решил оставить только один для входящих пакетов.

Выглядит оно так:

image
а процесс создания выглядит так:

BufferBlock<byte[]> _inputBufferBytes = new BufferBlock<byte[]>();
BufferBlock<InputTLCombinatorInstance> _inputBuffer = new BufferBlock<InputTLCombinatorInstance>();
ActionBlock<byte[]> _inputBufferParcer;

ActionBlock<TLCombinatorInstance> _inputUpdates;
ActionBlock<TLCombinatorInstance> _inputSystemMessages;

TransformBlock<InputTLCombinatorInstance, RpcAnswer> _inputAnswers;

BufferBlock<RpcAnswer> _inputAnswersBuffer = new BufferBlock<RpcAnswer>();
BufferBlock<RpcAnswer> _inputRejectedBuffer = new BufferBlock<RpcAnswer>();
BufferBlock<InputTLCombinatorInstance> _inputUnsorted = new BufferBlock<InputTLCombinatorInstance>();

// --
// выходная сетка
_inputBufferParcer = new ActionBlock<byte[]>(bytes => ProcessInputBuffer(bytes));

_inputSystemMessages = new ActionBlock<TLCombinatorInstance>(tlci => ProcessSystemMessage(tlci));
_inputUpdates = new ActionBlock<TLCombinatorInstance>(tlci => ProcessUpdateAsync(tlci));
_inputAnswers = new TransformBlock<InputTLCombinatorInstance, RpcAnswer>(tlci => ProcessRpcAnswer(tlci));

// from [_inputBufferBytes] to [_inputBufferTransformer]
_inputBufferBytes.LinkTo(_inputBufferParcer);
// from [_inputBufferTransformer] to [_inputBuffer]
//_inputBufferTransformer.LinkTo(_inputBuffer);

// if System then from [_inputBuffer] to [_inputSystemMessages]
_inputBuffer.LinkTo(_inputSystemMessages, tlciw => _systemCalls.Contains(tlciw.Combinator.Name));
// if Updates then from [_inputBuffer] to [_inputUpdates]
_inputBuffer.LinkTo(_inputUpdates, tlciw => tlciw.Combinator.ValueType.Equals("Updates"));
// if rpc_result then from [_inputBuffer] to [_inputRpcAnswers]
_inputBuffer.LinkTo(_inputAnswers, tlciw => tlciw.Combinator.Name.Equals("rpc_result"));
// if rpc_result then from [_inputBuffer] to [_inputRpcAnswers]
//_inputBuffer.LinkTo(_inputUnsorted);

// and store it  [_inputAnswers] to [_inputAnswersBuffer] to process it
_inputAnswers.LinkTo(_inputAnswersBuffer);
_inputRejectedBuffer.LinkTo(_inputAnswersBuffer);

Как видите входные байтовые массивы разбираются, систематизируются и раскладываются по буферам, откуда теснее разбираются по необходимости. В частности updates и systemMessages обрабатываются сразу по приходу в ActionBlock, а rpcAnswers вначале преобразуется с поддержкой TransformBlock а потом складывается в BufferBlock. Систематизация типа пакета происходит внутри BufferBlock на основании условий связывания блоков.

Непринужденно позже вызова способа мы создаем WriteOnceBlock — блок куда дозволено записать только 1 значение:

WriteOnceBlock<RpcAnswer> answer = new WriteOnceBlock<RpcAnswer>(e => e);

И линкуем его к буферу RPC результатов:

_inputAnswersBuffer.LinkTo(answer, new DataflowLinkOptions { MaxMessages = 1 },
			i => i.SessionId == _em.SessionId);

А дальше асинхронно ожидаем пока придет результат:

return await answer.ReceiveAsync(TimeSpan.FromSeconds(60)).ConfigureAwait(false); // таймаут если результата нет слишком длинно

Отдельно хочу подметить что до этого момента я не написал ни строчки кода под Android. Каждая разработка и тестирование велось для традиционной сборки под .net 4.5

Talks.Backend

Бэкэнд заказчика. Я решил реализовывать заказчик по образцу проектирования MVP с IoC, причем первоначально я нацеливался на Passive View вариацию, когда представление не содержит никакой логики, но в выводе я пришел к пониманию что Supervising Controller сработал бы гораздо отменнее.

Какие же задачи появились передо мною при создании бэкэнда? Доступ к записной книжке, доступ к базе данных, доступ к файловой системе (для хранения фоточек). Остальная часть бэкэнда это обычная реализация MVP: комплект Presenter’ов и IView’шек

Доступ к записной книжке

Для доступа к записной книжке команда Xamarin все теснее придумала за нас. Они разработали библиотеку Xamarin.Mobile которая инкапсулирует комплект функций на мобильном устройстве — записная книжка, GPS, камера, в кроссплатформенной повадке. К тому же с полной помощью async-await.

Таким образом доступ к записной книжке получить Исключительно легко:

#if __ANDROID__
public async Task GetAddressbook(Android.Content.Context context)
{
	contacts = new AddressBook(context);
#else
public async Task GetAddressbook()
{
	contacts = new AddressBook();
#endif
	if (!await contacts.RequestPermission())
	{
		Trace.WriteLineIf(clientSwitch.TraceInfo, "Permission for contacts denied", "[ContactsPresenter.PopulateAddressbook]");
		_view.SendSmallMessage("CONTACTS PERMISSON DENIED");
		return;
	}
	else
	{
		_icv.PlainContacts = new ListItemCollection<ListItemValue>( (from c in contacts
								where (c.Phones.Count() > 0)
								select new ListItemValue(c)).ToList());
	}
}

Константа компиляции __ANDROID__ введена потому, что для приобретения списка контактов на Android контекст требуется, а на других ОС — нет.

Здесь виден один из недостатков Passive View для кроссплатформенного решения. По заданию нам было нужно группировать контакты по первой букве фамилии. Для Android это делается через создание класса ListItemCollection, тот, что осуществляет группировку, типичный пример этого доступен в интернете. На iOS безусловно иной подход к созданию такой группировки, что на WinPhone — я не знаю. Так что здесь целесообразно получать и группировать контакты непринужденно во View.

Это и есть основная задача в гибридной кроссплатформенной разработке на мой взор. Нужно Отчетливо понимать где тебе нужно отвлекаться от платформы, а где не стоит. Думаю, это приходит с навыком.

Доступ к базе данных

Доступ к базе данных Xamarin рекомендует через примитивную ORM SQLite.Net. Когда-то я пробовал игнорировать эти рекомендации и трудиться с базой напрямую, через драйвер, но в результате осознал что отменнее слушать советы больше опытных разработчиков.

Описывать как трудиться с SQLite.Net я не вижу специального смысла, скажу только что для тестирования сборки с подключенным SQlite.Net нужно иметь в плане бинарники sqlite, которые доступны на официальном сайте www.sqlite.org/download.html

Отдельно подмечу что SQLite.Net всецело поддерживает TAP и async-await.
Класс SQLite.SQLiteAsyncConnection я рекомендую расширить комплектом Generic классов для облегчения доступа к БД:

#region Public Methods
public Task<List<T>> GetItemsAsync<T>() where T : IBusinessEntity, new()
{
    return Table<T>().ToListAsync();
}

public Task<T> GetItemAsync<T>(int id) where T : IBusinessEntity, new()
{
    return GetAsync<T>(id);
}

public async Task<bool> CheckRowExistAsync<T>(int id) where T : IBusinessEntity, new()
{
    string tblName = typeof(T).Name;
    return await ExecuteScalarAsync<int>("select 1 from "   tblName   " where Id = ?", id).ConfigureAwait(false) == 1;
}

public async Task<int> SaveItemAsync<T>(T item) where T : IBusinessEntity, new()
{
    if (await CheckRowExistAsync<T>(item.Id))
    {
        return await base.UpdateAsync(item).ConfigureAwait(false);
    }
    else
    {
        return await base.InsertAsync(item).ConfigureAwait(false);
    }
}

public Task<int> DeleteItemAsync<T>(int id) where T : IBusinessEntity, new()
{
    return DeleteAsync(new T() { Id = id });
}
#endregion

Так же стоит помнить, что правила доступа к файловой системе на всякой ОС различаются.

Следственно путь к базе данных дозволено получить дальнейшим образом:

public static string DatabaseFilePath
{
    get
    {
        var sqliteFilename = "TalksDb.db3";
#if SILVERLIGHT
// Windows Phone expects a local path, not absolute
var path = sqliteFilename;
#else
#if __ANDROID__
// Just use whatever directory SpecialFolder.Personal returns
string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); 
#else
        // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms
        // (they don't want non-user-generated data in Documents)       
        string documentsPath= Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); // Documents folder 
        string libraryPath = Path.Combine(documentsPath, "..", "Library"); // Library folder
#endif
        var path = Path.Combine(libraryPath, sqliteFilename);
#endif
        return path;
    }
}
Доступ к файловой системе

Одна из задач конкурса было приобретение и хранение картинок. Для решения этой задачи мной был взят и доработан кроссплатформенный класс дискового кэша отсель. Вообще, работа с файловой системой одна из наменее портируемых частей, так как требования к работе с файлами на всех ОС различные. Отчасти особенности файловых систем описаны в официальных доках Xamarin

Talks.Droid

Андройд версия приложения. В безукоризненном варианте к моменту создания плана на определенную платформу у вас может быть всецело рабочий и протестированный бэкэнд. В моем варианте так не получилось, но в последующем я буду к этому тяготиться.
Основные трудности начинаются тут.

В основе приложения лежит Bound Service к которому биндится класс App — синглтон реализующий «приложение». Сделано это для того Дабы всякое Activity могло получить доступ к сервису с поддержкойApp.Current.MainService.

Внутри обслуживания отдельным потоком создается Model, так же присутствует класс с поддержкой которого Activity забирают свои Presenter’ы, приблизительно так:

_presenter = App.Current.MainService.CreatePresenter<ChatListPresenter>(typeof(ChatListPresenter), this);

Cледует помнить что Xamarin формирует AndroidManifest самосильно и не дает напрямую редактировать его. Все параметры Activity записываются в виде аттрибутов:

    [Activity(Label = "Settings", Theme = "@style/Theme.TalksTheme")]
    [MetaData("android.support.PARENT_ACTIVITY", Value = "talks.ChatListActivity")]
    public class SettingsActivity : SherlockActivity, IView

В основном код Activity немного чем отличается от java варинта, CamelCase, да некоторые геттеры/сеттеры обернуты в свойства

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.MessagesScreen);

            AndroidUtils.SetRobotoFont(this, (ViewGroup)Window.DecorView);

            _presenter = App.Current.MainService.CreatePresenter<MessagePresenter>(typeof(MessagePresenter), this);
            _presenter.PlatformSpecificImageResize = AndroidResizeImage; 

            this.ChatId = Intent.GetIntExtra("userid", 0);
            userName = Intent.GetStringExtra("username");

            _button = FindViewById<ImageButton>(Resource.Id.bSendMessage);
            _button.Click  = button_Click;
            _button.Enabled = false;

            _message = FindViewById<EditText>(Resource.Id.etMessageToSend);
            _message.TextChanged  = message_TextChanged;

            _lv = FindViewById<ListView>(Resource.Id.lvMessages);
            _lv.Adapter = new Adapters.MessagesScreenAdapter(this, this.Messages);
        }
Login Activity

Login Activity должно содержать 3 пункта — ввод телефона, приобретение кода и его ввод, регистрация. Для комфорта это сделано с поддержкой фрагментов.

image

Проще каждого это сделать с поддержкой фрагментов. Впрочем, применение фрагментов и MVP безусловно неочевидно!

В выводе я пришел к тому, что сделал один presenter, а LoginActivity легко оборачивал реализации IView фрагментов:

PhoneFragment _pf = null;
CodeFragment _cf = null;
SignUpFragment _suf = null;

public string PhoneNumber
        {
            get 
            {
                if (_pf != null)
                {
                    return _pf.Phone;
                }
                else
                {
                    return "";
                }
            }
        }

        public string AuthCode
        {
            get { return _cf.Code; }
        }

        public string Name
        {
            get { return _suf.FirstName; }
        }

        public string Surname
        {
            get { return _suf.Surname; }
        }
Съемка фото/видео

Увлекательный и не явственный момент. Одной из задач было приобретение фото/видео с камеры для отправки его собеседнику либо установки в качестве аватарки.

Осуществляется это через меню с поддержкой Xamarin.Mobile.

  public override bool OnMenuItemSelected(int featureId, Xamarin.ActionbarSherlockBinding.Views.IMenuItem item)
        {
            switch (item.ItemId)
            {
                // Respond to the action bar's Up/Home button
                case Android.Resource.Id.Home:
                    NavUtils.NavigateUpFromSameTask(this);
                    return true;

                case Resource.Id.messages_action_takephoto:
                    _presenter.TakePhoto(this);
                    return true;

                case Resource.Id.messages_action_gallery:
                    _presenter.PickPhoto(this);
                    return true;

                case Resource.Id.messages_action_video:
                    _presenter.TakeVideo(this);
                    return true;
            }
            return base.OnMenuItemSelected(featureId, item);
        }

Впрочем event отвечающий за выбор пункта меню возвращает bool, следственно мы не можем применить к нему конструкцию async-await. Решается это дюже легко, следует помнить что async-await это каждого лишь синтаксический сахар тот, что в результате генерирует все те же Continuation. И ничего не воспрещает нам написать это как прежде:

#if __ANDROID__
        /// <summary>
        /// Взятие фото с камеры
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public bool TakePhoto(Android.Content.Context context)
        {
            var picker = new MediaPicker(context);

#else
        public bool TakePhoto()
        {
            var picker = new MediaPicker();
#endif
            if (picker.IsCameraAvailable)
            {
                picker.TakePhotoAsync(new StoreCameraMediaOptions
                {
                    Name = String.Format("{0:dd_MM_yyyy_HH_mm}.jpg", DateTime.Now),
                    Directory = "TalksPictures"
                })
                .ContinueWith((prevTask) =>
                {
                    if (prevTask.IsCanceled)
                    {
                        _imv.SendSmallMessage("User canceled");
                        return;
                    }
                    if (PlatformSpecificImageResize != null)
                    {
                        string path = PlatformSpecificImageResize(prevTask.Result);                     
                        // Сделать сообщение
                        DomainModel.Message msg = new DomainModel.Message(r.Next(Int32.MaxValue), 0, _imv.ChatId, _imv.PendingMessage, "", 0);                            
                        _imv.AddMineMessage(msg);
                    }
                })
                .ContinueWith((prevTask) =>
                {
                    if (!prevTask.IsCanceled)
                    {
                        Console.WriteLine("User ok");
                    }
                }, TaskScheduler.FromCurrentSynchronizationContext());

                return true;               
            }
            return false;
        }

Xamarin помимо кроссплатформенности предлагает Component Store, где находятся порты знаменитых Android и/или iOS библиотек и компонентов, как бесплатные так и платные. В частности там присутствует ActionBar.Scherlok и незадолго возник Android.Support.v7, причем компоненты дозволено ставить прямо из среды, как в NuGet, что дюже комфортно

image

Таким образом в два клика дозволено получить поддержку ActionBar на устройствах с Android 2.3 и выше.

Публикация

Публикация приложения осуществляется по утвержденной Google схеме
image
Это включает достаточно много действий. Но намеренно для нас команда из Xamarin сделала мастер встроенный в VS тот, что разрешает подготовить приложение для публикации в несколько шагов.
image
и готово
image
Правда у меня не получилось сразу сделать KeyStore с поддержкой этого мастера. Что то с временем жизни ключа было. Пришлось создавать ручками.

Тестирование

Малое примечание по тестированию. Тестировать на эмуляторе — это жутко и немыслимо. От этого следует отказаться как дозволено стремительней. Самый недорогой андройд стоит теперь 3000 руб, китайский планшет дозволено обнаружить по схожей цене. Я с началом конкурса сразу приобрел жене Fly с Android 4.0.1, т.к. у меня был только ветхий HTC с 2.3.

По поводу тестирования и разработки под iOS труднее. Безусловно наилучший вариант — взять самый недорогой макбук, этого будет довольно.
Но приобретать пару iPhone и iPAD для тестирования… не знаю, не самый наилучший вариант. Теперь я рассматриваю вероятность MacInCloud и если там все будет отлично, я детально опишу каждый процесс.

Вывод

Теперь трудно подводить вывод. В процессе разработки я отлично изучил особенности Android платформы,
разработал отличный, покрытый тестами и, основное, кроссплатформенный бэкэнд.
Говорят, впереди будет конкурс под WinPhone и iPad. Что же, мне остается только нарисовать интерфейсы.

Работа над ошибками

«Note to self» как говорится. Легко примечания на грядущее что я делал не так.
1. Неимение проектирования. Я двукратно рефакторил MTProto.Core фактически целиком. Повод этого в том что я не сел с бумажкой и не нарисовал всецело как должно это ядро выглядеть. Многие решения принимались самопроизвольно и без расчета на грядущее.
2. Дрянное осознавание Android платформы. Я длинно пытался осознать каким образом организовывать взаимодействие с сервисом Android. Сознаться, я и сейчас не знаю лучшего метода обеспечить это взаимодействие. Нужно понимать что гайды d.android.com здесь непотребны, сервис штука для андройда специфичная, а нам нужно отвязаться от платформы и сделать что-то кроссплатформенное.
3. Упрямость и жадность. У меня была вероятность привлечь еще одного программиста и допустимо на пару мы бы показали больше наилучший итог. Но чай я сам, все сам.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB