Компания
55,77
рейтинг
27 февраля 2014 в 09:25

Разработка → Кроссплатформенная разработка на Titanium – это ужасный конец или ужас без конца?

Image and video hosting by TinyPicВ этом топике мы хотим поделиться нашим опытом создания мобильных приложений на платформе для разработки кроссплатформенных приложений Titanium. Примерно с 2011 мы начали работы с кроссплатформенными фреймворками. Сначала это был PhoneGap, потом Titanium. Сделали десяток приложений, работающих и по сей день, как в России, так и в США. Мы сознательно хотим отойти от оценок — плохо это или хорошо разрабатывать кроссплатформенные приложения, а сосредоточиться на тех трудностях, с которыми предстоит столкнуться с точки зрения разработки и сопровождения этих приложений.

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

Итак, начнем со списка проблем, с которыми вам придётся столкнуться.

Проблемы:

  1. Проблема дабл-кликов.
  2. If’ный код.
  3. Управление памятью на Android.
  4. Недостаточная реализация отдельных функций, в том числе стандартных.
  5. Javascript — отсутствие типизации замедляет процесс написания кода и усложняет сопровождение.
  6. Отсутствие InterfaceBuilder — замедляет процесс написания приложения, весь UI пишется в коде.
  7. Titanium SDK обновляется позже SDK операционных систем.
  8. Каждая версия SDK содержит исправление старых ошибок и привносит новые ошибки.


Примеры, на которых мы столкнулись с этими проблемами.

Проблема дабл-кликов

Мобильное приложение содержит различные элементы управления – кнопки, поля ввода текста, переключатели и т.п. Когда пользователь нажимает на какой-либо из них, приложение получает сигнал об этом в виде события – объекта, содержащего информацию о том, с каким элементом было произведено действие, какое это действие, например, долгое или короткое нажатие, и другое. В нативных приложениях, т.е. в приложениях, написанных с использованием стандартных средств разработки (iOS SDK для iPhone и Android SDK для Android), во время обработки события соответствующий элемент управления блокируется, и новое событие от него прийти не может. Наверное, все замечали, что если нажать на кнопку отправки сообщения в стандартном приложении, то на какое-то время кнопка становится серой и нажать на нее второй раз нельзя. Такая блокировка в нативных приложениях происходит автоматически и не требует от программиста написания кода или других действий. В приложении, написанном с использованием Titanium SDK, такой блокировки нет, поэтому каждый элемент управления может отправить несколько однотипных событий. Если, например, по нажатию на кнопку открывается новый экран приложения, то может открыться два или больше экранов.

Это неправильно и неудобно для пользователя. Apple, скорее всего, даже не пропустит такое приложение в AppStore. Приходится блокировать UI или использовать флажки для того, чтобы игнорировать последующие события до окончания обработки первого. Наши тестировщики назвали эту проблему проблемой дабл-кликов.

Мы придумали несколько способов решения этой проблемы:

1. В первом варианте мы сделали блокировку экрана: после первого нажатия весь экран приложения закрывается сообщением «Пожалуйста, подождите...» на полупрозрачном сером фоне. Для простых ситуаций сообщение скрывается через определенное время (около полсекунды). Для более долгих операций, требующих, например, загрузки данных, необходимо заблокировать экран на неопределенный срок, и тогда в конце обработки события вызывается специальная функции разблокировки экрана, которая скрывает сообщение. Само сообщение служит щитом для последующих действий пользователя.
Выглядело это примерно так:

Image and video hosting by TinyPic


2. В другом варианте выполняется проверка, что со времени последнего нажатия прошло не меньше заданного интервала времени. Если полсекунды с последнего нажатия прошло, то выполняется обработка события, иначе оно игнорируется.
В обоих случаях для пользователя это менее понятно, чем поведение нативного приложения, в котором кнопка просто не может нажаться в процессе обработки предыдущего события.

If’ный код

Многие вещи для разных операционных систем реализуются с помощью различных функций, свойств и модулей, в коде программы постоянно приходится писать «если это Android, то делаем так, а если iPhone, то вот так». Сначала заводится переменная, чтобы писать меньше кода, ее значение заполняется в начале работы приложения:

if(Titanium.Platform.name.indexOf('iPhone') >= 0) {
  isIPhone = true;
  isAndroid = false;
} 


И вы начинаете везде вставлять проверки.
получение объектов из базы данных:

if(isAndroid) {
  fieldCount = resultSet.fieldCount;
} else {
  fieldCount = resultSet.fieldCount();
}


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

Image and video hosting by TinyPic

Если под параметром нарисован Android, значит он работает в приложениях на Android, если нарисован iPhone или iPad, значит это параметр для iOS. И таких пометок в документации очень много.

Секции в таблицах

Если обновить данные в таблице с секциями на Android, то приложение упадет. Поскольку секции на Android не умеют вести себя так красиво, как на iOS, оставаясь в верхней части экрана при прокручивании списка под секцией, то мы вставляли представление секции в первый элемент списка для каждой секции. Тогда приложение не падает.

Отображение текста – лейблы, и поля ввода текста

Android делает отступы внутри текстовых полей, и эти отступы зависят от версии Android, от размера текстового поля и размера шрифта в нем. Обычная проблема – это невидимый текст, который вроде бы должен войти в текстовое поле, но не входит, из-за этих внутренних отступов. Если вы хотите, чтобы все выглядело одинаково хорошо, вам придется написать метод с большим количеством if'ов, который будет определять, как сейчас нужно отобразить текст.

Скрытый текст
var createLabel = function(properties, linesCount) {
  var fontSize = isNotEmpty(properties.font) && (properties.font.fontSize) ? properties.font.fontSize : defaultFontSize;

  var offset = Math.floor(fontSize/8);
  var heightOffset = 2 * offset;
  var lineHeight = fontSize + heightOffset;

  var androidTopOffset = 0;
  if (isAndroid) {
    androidTopOffset = (fontSize <= getControlSize(18)) ? Math.floor(fontSize / 4) : Math.floor(fontSize / 11);
  }

  if(isNotEmpty(properties.top)) {
    properties.top = properties.top - offset - androidTopOffset;
    if(isNotEmpty(properties.height) && properties.height != Ti.UI.SIZE) {
      properties.height = properties.height + heightOffset;
    } else if(isNotEmpty(linesCount)) {
      properties.height = lineHeight * linesCount;
    }
  }
  if (isEmpty(properties.font)) {
    properties.font = {};
  }

  properties.font.fontFamily = fontName;
  properties.font.fontWeight = 'normal';

  if (isEmpty(properties.wordWrap)) {
    if (isNotEmpty(linesCount) && linesCount == 1) {
      properties.wordWrap = false;
    } else {
      // properties.wordWrap = true;
    }
  }

  // DEBUG
  if (isBlank(properties.color)) {
    properties.color = '#ff0000';
    properties.font.fontWeight = 'bold';
    Ti.API.error('Error in createCommonLabel: not specified label color. Label text = ' + properties.text);
  }

  var label = Ti.UI.createLabel(properties);
  return label;
};



Отдельное внимание стоит уделить секции DEBUG: если у текста не задан цвет, мы ставим красный цвет. Это сделано, потому что значение цвета по умолчанию для iPhone и Android разные и необходимо писать цвет каждому лейблу всегда – если вы хотите, чтобы приложение выглядело одинаково на разных устройствах. Также необходимо посчитать размер каждого элемента, ведь экраны Android’ов бывают очень разные, и размер шрифта и элементов управления должен соответствовать размеру экрана. Из-за таких несоответствий между платформами приложение необходимо тестировать на всех устройствах и операционных системах, причем даже тогда, когда исправляется что-то для одной из платформ – предсказать, как исправление отразится на других устройствах, иногда невозможно.

Управления памятью на Android

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

Если в приложении необходима загрузка данных из интернета, то тут тоже может крыться проблема. Если приложению не хватает памяти, то при загрузке данных приложение упадет. В нативном приложении для Android разработчик может добавить обработку исключений, ошибок, возникающих в ходе работы приложения, можно сделать даже отображение сообщения о недостатке памяти пользователю (хотя предпочтительнее разрешить проблему каким-либо другим способом). Когда вы пишете на Titanium, этой возможности нет. При загрузке данных ошибка происходит глубоко внутри Titanium, и программными средствами обработать ее нельзя.

Недостаточная реализация отдельных функций, в том числе стандартных

Наверное, самое большое количество подобных проблем мы собрали при реализации возможности добавить фото в приложении на Android. Первая проблема, с которой мы столкнулись, это невозможность написать код для выхода из режима фотографирования. Т.е. если приложение должно показать экран фотографирования с парой кнопок «Сделать фото» и «Отмена», то по нажатию на кнопку отмены вы не можете ничего отменить. Вторая проблема – это невозможность узнать о том, что пользователь вышел из режима фотографирования нажатием кнопки «Назад» на телефоне. Т.е. если по выходу из режима фотографирования необходимо обновить интерфейс – вы не можете этого сделать.

Многие подобные недоделки и проблемы стоят в списке багов Титаниума в течение нескольких версий.
jira.appcelerator.org/browse/TIMOB-16182
jira.appcelerator.org/browse/TIMOB-16199

Общий список проблем:
jira.appcelerator.org/secure/IssueNavigator.jspa?

Javascript — отсутствие типизации, замедляет процесс написания кода и усложняет сопровождение

Большинство современных разработчиков мобильных приложений привыкли писать программы на высокоуровневых языках, таких как Java и Objective-C, однако на Titanium приходится писать на Javascript. Это привычнее для веб-разработчиков, но они не знают основ мобильной разработки, не сталкивались с ограничениями памяти и правилами создания интерфейсов мобильных приложений. Javascript является языком с динамической типизацией, это значит, что переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной. Таким образом, в различных участках программы одна и та же переменная может принимать значения разных типов. Для программиста это означает, что он должен сам следить, чтобы программа работала с данными в соответствии с их типами, поскольку в противном случае приложение будет падать.

Отсутствие InterfaceBuilder — замедляет процесс написания приложения — весь UI пишется в коде

Многие мобильные приложения содержат списки: твиттер, лента новостей, письма в почтовым ящике, адресная книга, напоминания, результаты поиска – это все списки. Обычно экран со списком реализуется на таблице. Каждый элемент — это ячейка, у которой описан шаблон. Для приложения на iOS программист создает шаблон (или различные шаблоны — для сложных таблиц) в Interface Builder

Image and video hosting by TinyPic

И пишет код для отображения данных в этом шаблоне:

- (void)updateViews
{
    if (!self.package)
        return;

    static NSDateFormatter* df = nil;
    if (!df) {
        df = [[NSDateFormatter alloc] init];
        df.dateStyle = NSDateFormatterShortStyle;
        df.timeStyle = NSDateFormatterNoStyle;
    }

    self.labelCity.text = self.package.city;
    self.labelPackageId.text = self.package.formattedPackageId;
    self.labelItemsCount.text = @(self.package.items.count).stringValue;
    self.prizeTypeImageView.image = [self prizeTypeImage];
    self.labelDispatchDate.text = [df stringFromDate:self.package.dispatchDate];
    self.labelPeriod.text = [NSString stringWithFormat:@"%@ – %@",
                             [df stringFromDate:self.package.periodBegin],
                             [df stringFromDate:self.package.periodEnd]];
}



На Титаниуме нужно написать все в коде, это выглядит так:

Скрытый текст
var rowTemplate = function(item, index, callback) {
  var row = Ti.UI.createTableViewRow({
    height: rowHeight,
    left: 0,
    right: 0,
    selectedBackgroundColor: '#11a2c5',
    backgroundSelectedColor: '#11a2c5',
    color: 'transparent',
    className: classNameStr
  });
  var view = Ti.UI.createView ({
    top: 0,
    left: leftOffset,
    right: rightOffset,
    height: rowHeight,
    color: 'transparent'
  });
  var backView = Ti.UI.createView ({
    top: 0,
    left: 0,
    right: voucherWidth,
    height: rowHeight,
    color: 'transparent'
  });
  view.add(backView);
  var imageView = Ti.UI.createImageView({
    top: top,
    left: 0,
    width: getControlSize(47),
    height: getControlSize(58),
    image: getPicturePath('/images/orders/icon_oteli_mini')
  });
  backView.add(imageView);

  var left = imageView.left + imageView.width + topOffset;
  var nameLabel = createCommonLabel({
    left: left,
    top: top,
    right: topOffset,
    height: imageView.height,
    font: {
      fontSize: fontSize
    },
    color: '#000000',
    text: itemName
  }, 2);
  backView.add(nameLabel);
  top = imageView.top + imageView.height;
  var placeLabel = createCommonLabel({
    left: 0,
    top: top,
    right: 0,
    height: height,
    font: {
      fontSize: fontSize
    },
    color: '#000000',
    text:  itemCity
  });
  backView.add(placeLabel);

  top = placeLabel.top + placeLabel.height;
  var dateLabel = createCommonLabel({
    left: 0,
    top: top,
    right: 0,
    height: height,
    font: {
      fontSize: fontSize
    },
    color: '#000000',
    text: datesText
  });
  backView.add(dateLabel);

  var voucherView = Ti.UI.createImageView({
    width: voucherWidth,
    top: topOffset,
    right: 0,
    height: getControlSize(48),
    backgroundImage: getPicturePath(voucherImagePath),
    visible: voucherActive
  });
  view.add(voucherView);

  top = dateLabel.top + dateLabel.height;
  var numberLabel = createCommonLabel({
    left: 0,
    top: top,
    right: 0,
    height: height,
    text: orderNumber,
    font: {
      fontSize: fontSize
    },
    color: '#000000'
  });
  backView.add(numberLabel);

  top = numberLabel.top + numberLabel.height;
  var statusLabel = createCommonLabel({
    left: 0,
    top: top,
    right: 0,
    height: height,
    font: {
      fontSize: fontSize
    },
    color: '#000000',
    text: statusText
  });
  backView.add(statusLabel);

  return row;
};



В Titanium 3.1.0 появилась новая возможность реализовать список – ListView. На Android она работает существенно быстрее таблицы, и реализация списков на ListView позволяет сделать приложение, которое не будет тормозить и подвисать при прокручивании списка. Для этого нужно переписать создание ячейки в виде шаблона:

Скрытый текст
var listItemTemplate = {
  properties: {
    height: rowHeight,
    backgroundSelectedColor: '#11a2c5'
  },
  childTemplates: [
    {
      type: 'Ti.UI.View',
      bindId: 'rootView',
      properties: {
        left: leftOffset,
        top: 0,
        right: rightOffset,
        backgroundSelectedColor: '#11a2c5',
        height: rowHeight,
        color: 'transparent'
      },
      childTemplates: [
        { // backView
          type: 'Ti.UI.View',
          bindId: 'backView',
          properties: {
            left: 0,
            top: 0,
            color: 'transparent',
            backgroundSelectedColor: '#11a2c5',
            right: voucherSize,
            height: rowHeight
          },
          childTemplates: [
            {
              type: 'Ti.UI.ImageView',
              bindId: 'icon',
              properties: {
                left: 0,
                top: top,
                width: getControlSize(47),
                height: height,
                image: getPicturePath('/images/icon_mini')
              }
            },
            {
              type: 'Ti.UI.Label',
              bindId: 'name',
              properties: {
                left: left,
                top: top,
                right: top,
                height: height,
                font: {
                  fontSize: fontSize,
                  fontFamily: fontName
                },
                color: '#000000'
              }
            },
            {
              type: 'Ti.UI.Label',
              bindId: 'country',
              properties: {
                left: 0,
                top: top + height,
                right: 0,
                height: subHeight,
                font: {
                  fontSize: fontSize,
                  fontFamily: fontName
                },
                color: '#000000'
              }
            },
            {
              type: 'Ti.UI.Label',
              bindId: 'date',
              properties: {
                left: 0,
                top: top + height + subHeight,
                right: 0,
                height: subHeight,
                font: {
                  fontSize: fontSize,
                  fontFamily: fontName
                },
                color: '#000000'
              }
            },
            {
              type: 'Ti.UI.Label',
              bindId: 'number',
              properties: {
                left: 0,
                top: top + height + subHeight + subHeight,
                right: 0,
                height: subHeight,
                font: {
                  fontSize: fontSize,
                  fontFamily: fontName
                },
                color: '#000000'
              }
            },
            {
              type: 'Ti.UI.Label',
              bindId: 'status',
              properties: {
                left: 0,
                top: top + height + subHeight + subHeight + subHeight,
                right: 0,
                height: subHeight,
                font: {
                  fontSize: fontSize,
                  fontFamily: fontName
                },
                color: '#000000'
              }
            }
          ]
        },
        {
          type: 'Ti.UI.ImageView',
          bindId: 'icon',
          properties: {
            right: 0,
            top: top,
            width: voucherSize,
            height: voucherSize,
            image: getPicturePath('/images/icon_act')
          }
        }
      ]
    }
  ]
};



Поскольку реализация на ListView полностью меняет код создания экрана приложения, то имеет смысл вынести создание этого экрана для iPhone и Android в различные файлы и добавить условия для подключения одного из них в соответствии с платформой, на которой запускается приложение.

Titanium SDK обновляется позже SDK операционных систем

Не так давно Apple выпустила iOS 7, где была существенно переработана графика, работа со статус баром (верхняя часть экрана, где отображается уровень заряда аккумулятора, время и оператор), и другое.

Как обычно происходит выпуск новой операционной системы на рынок? Сначала сторонним разработчикам, т.е. разработчикам мобильных приложений, предоставляется доступ к бета-версии нового SDK для создания приложений под новую операционную систему. Т.е. все желающие выпустить приложение, поддерживающее новейшие возможности системы, могут и должны подготовиться заранее. Потом появляется стабильная SDK и начинают приниматься для проверки в AppStore приложения с поддержкой новой версии iOS. Потом пользователям – владельцам iPhone’ов – становится доступно обновление операционной системы и успевших подготовиться приложений.

Что происходит с теми, кто разрабатывает приложение на Titanium? Первая бета-версия iOS SDK доступна разработчикам с 11 июня 2013, версия Titanium SDK с поддержкой беты iOS – c 15 августа (SDK 3.1.2). Приложения с поддержкой новой версии iOS принимались на проверку c 11 сентября 2013.

Операционная система была доступна пользователям с 18 сентября 2013 года: у разработчиков была неделя, чтобы пройти проверку Apple.

Titanium выпустил версию 3.1.3 RC 9 сентября, а стабильную версию 3.1.3 с поддержкой iOS 7 – только 18 сентября, т.е. когда момент быть первым на рынке был уже безвозвратно упущен.

В версии 3.1.2 не была реализована поддержка нормального поведения StatusBar’а. Во-первых, если приложение раньше работало в полноэкранном режиме, то теперь оно отступает от верхнего края экрана на размер статус бара, и содержимое экранов может оказаться расположено совершенно неожиданно, в зависимости от того, как задавались координаты элементов при создании приложения. Но даже если вы решили бы сделать приложение, работающее не в полноэкранном режиме, то статус бар все равно бы не работал как полагается – вместо статус бара отображается черная полоса без какой-либо информации: ни часов, ни заряда, ни оператора на нем не отображается. С выходом Titanium 3.1.3 ситуация заметно улучшилась, однако в режиме фотографирования появляется статус бар даже у полноэкранного приложения jira.appcelerator.org/browse/TIMOB-15203 — эту ошибку исправили только в версии Titanium 3.2.0, которая вышла 20 декабря.

Каждая версия SDK содержит исправление старых ошибок и привносит новые ошибки

На примере все той же версии 3.1.3: при переходе на нее с версии 3.1.2 значительно замедлилась анимации в приложении. Например, нам нужно, чтобы два элемента интерфейса одновременно плавно переместились слева направо. В нативных приложениях разработчик может описать все анимации, которые необходимо применить к элементам интерфейса и запустить анимации выполняться одновременно. В предыдущих версиях Titanium описание анимаций для отдельных элементов было последовательным, но запускалось почти одновременно, так что визуально воспринималось как одна анимация, действующая с несколькими объектами. В Titanium 3.1.3 расхождения по времени между последовательно запущенными анимациями стало очень заметным и может составлять 1-3 секунды. По завершению анимации обычно необходимо выполнить какие-то операции, изменить положение элементов (сама анимация их не меняет), удалить объекты, которые больше не видны, чтобы они не занимали память. В Titanium 3.1.3 вызов постобработки анимации случается не всегда. Хотите починить статус бар – обновите версию SDK. Да, у вас перестанут работать анимации. Ну и что. Обновитесь дальше. Можно просто никогда не сделать приложение, которое может пройти проверку Apple, ожидая, когда же починят баги Titanium. А можно его даже не собрать.

В нашей практике был случай, когда обновление Titanium Studio – среды разработки, которая используется для сборки приложений – привело к тому, что функция сборки приложения для публикации просто перестала работать: jira.appcelerator.org/browse/TC-1322 (SDK 2.1.3 RC2 will not build for iTunes store distirbution) или jira.appcelerator.org/browse/TC-2193 (Studio 3.1 Distribute for iTunes Store is producing ad-hoc builds) – при попытке собрать приложение для публикации в App Store собирается приложение для AdHoc распространения – в этом случае помогла переустановка студии.

В заключение хочется заметить, что кто-то может сказать, что все это ерунда и проблемы можно обойти. Вопрос только один — нужно ли это делать и нужно ли это делать за счет качества и удобства приложений, отставания от рынка, постоянно увеличивающихся расходов на сопровождение? Мы для себя решили, что оно того не стоит – специализированные задачи нужно решать созданными для этого инструментами.
Автор: @eastbanctech

Комментарии (55)

  • +2
    Почему вы ушли с phonegap? По опыту — работа на нем получается хуже, чем на Titanium?
    • +6
      Мы с него ушли довольно давно. Тогда на проекте мы собрали много глюков интерфейса, с большинством из которых сумели справиться. Проблемы были в основном от сырых библиотек, возможно, с ними сейчас получше. Главная причина, по которой ушли — это полностью нерабочее приложение на больших объемах данных. Т.е. пока тестировали — все было хорошо, как только попробовали загрузить реальные данные — приложение зависло.
      Недавно видели чужое приложение на Phonegap, которое нам предложили доделать. Первая проблема, которая бросилась в глаза сродни проблемы дабл-кликов: по нажатию на кнопку в приложении, открывается новый экран и на нем срабатыает то же самое событие, если там тоже кнопка на этом месте — то вызывается обработка нажатия на нее, и так далее, как будто клик проходит сквозь экраны.

      Главная проблема Phonegap, на мой взгляд, это снижение скорости реакции по сравнению с нативными приложениями. Если в нативном приложении вы хотите запустить камеру, или выполнить любую другую стандартную функцию операционной системы — вы просто делаете соответствующий вызов. И функция исполняется настолько быстро, насколько это позволяет устройство и операционная система. В phonegap вызов должен быть пропущен из веб-котента в Cordova, а оттуда попадет в систему, где и будет обработан нативными средствами.

      Вполне допускаю, что подобные технологии могут заинтересовать тех, кто делает свое первое мобильное приложение, и еще не знает к чему стремиться, и как круто можно сделать натив. Может заинтересовать веб-разработчиков, которые хотят сделать какое-то несложное приложение. Но проблемы останутся те же, что и на многих кросплатформенных приложениях: вы связаны проблемами самой системы, вы все время ждете обновлений библиотеки, когда новая платформа уже на рынке, и ваши пользователи все время ждут. И каждый раз, когда у вашего приложения проблемы — будь то зависания, глюки, неспособность работать с большими данными или устаревший дизайн — вы теряете пользователей, имидж и, в конечном счете, деньги.
      • +1
        проблема ghost-кликов довольно известна, и у этой проблемы есть масса решений. Одно из таких — использование нативных гестур. Ничего нету сложного в том, что бы пробрасывать все события, типа tap, double-tap, swipe, pinch и т.д. в webview и инициировать событие для js.
        • 0
          Об этом и речь: можно написать работающее приложение, если исправить проблемы, которые привносит кроссплатформенная разработка. У большинства из них есть решения, да, но какой смысл решать проблемы, когда можно провести время за более приятным занятием, например, реализуя удобную фичу для пользователей.
          Решение добавить еще один уровень обработки событий только сильнее отдаляет пользователя от желаемого действия. На флагманах, конечно, это будет не очень заметно, а на остальных устройствах — пользователи останутся недовольны.
          • +2
            Решение добавить еще один уровень обработки событий

            это не еще один уровень обработки событий, это вынос оного в нативный код. В этом случае в вашем приложении вы можете подписываться на события tap, swipe и т.д. и не нужно будет писать огромные обработчики touchstart/touchmove/touchend. Как результат, количество обработчиков событий снижается, количество вызовов этих обработчиков так же (что освободит для webview немного процессорного времени), генерация же ивента (найти элемент по координатам и инициировать событие) не создает такого уж большого оверхэда.
      • 0
        Вы в итоге остались на титаниуме, или еще куда-то перепрыгнули? Лично я, увидев такое количество реальных проблем, весьма недоумеваю, зачем было уходить с фонгапа. (комменты ниже читал). Сам разрабатываю на фонгапе, да, проблемы имеются, но их гораздо меньше, чем здесь описано у вас и все более-менее решаемые.

        Про падения фонгапа с большими данными интересны конкретные примеры с цифрами.
        • 0
          Вообще с phonegap все еще сильно зависит от выбранного подхода. Мне допустим нравится использование phoengap для реализации каких-то общих частей (вся бизнес логика, большая часть ui и т.д.), а сложные вещи, которые непрактично реализовывать в webview выношу в плагины и реализую нативно.
  • 0
    А пробовали xamarin? Мне просто интересно мнение людей которые пробовали писать на этих трех инструментах (phonegap, xamarin, titanium) кросс-платформенные приложения и к чему в результате они пришли.
    • 0
      xamarin все же не получится поставить в один ряд с phonegap и titanium.
      • +1
        Вы действительно правы: Xmarin — это порт платформы Mono на мобильные устройства, т.е. среда выполнения кода на С# и ее нельзя ставить в один ряд с Титаниум и Phonegap. У нас есть опыт работы с Ксамарин и мы обязательно поделимся, сейчас однозначно можем сказать, что есть довольно много сложнестей с тем, чтобы совместить MVC и MVVM Andriod и WinPhome 8 — т.е. по сути тот же if код получается.
        • 0
          По сути при создании xamarin авторы и не имели перед собой такой цели как унифицировать ui и избавить вас от необходимости писать if-код. На самом деле я считаю что такой подход оправдан, так как гайдлайны по ui и на одной и на другой платформе сильно различаются. Да, какие-то вещи дублируются, так что это довольно сложно реализовать ui одинаково для всех платформ.
        • +1
          >>чтобы совместить MVC и MVVM Andriod и WinPhome 8
          Пробовали MvvmCross? С ним получается перенести практически всё кроме разметки и некоторых вещей. Вообще мне даже нравится что на xamarin нет возможности писать разметку одну на всех — вас как бы физически заставляют писать нативный UI.
          • 0
            Писали приложение на MVVMCross получилось от 60% до 80% общего кода на iOS/Android/Wp7 60% получилось на iOS, потому что многое в интерфейсе пришлось задавать из кода.
            • 0
              Процент общего кода зависит сугубо от сложности бизнес-логики. Если она очень простая или её почти нет — то наверное xamarin использовать смысла нет.
              • +1
                Если вы не собираетесь длительное время поддерживать и развивать продукт, то кросс-платформенная разработка неоправданно дорога в принципе.
      • 0
        Titanium не получится поставить в один ряд с phonegap.
        Как раз в titanium и xamarin есть сходство. Там Node.ASC, а там Mono.
        А phonegap это просто браузер с несколькими дополнительными функциями.
    • +1
      Xamarin не стоит воспринимать как средство для создания переносимого кода (все что касается GUI не переносимо по умолчанию, а GUI это часто процентов так 50 кода). Скорее, это возможность писать под любую мобильную платформу на одном и том же C#, который знают очень многие. Со всеми нативными плюшками и быстродействием.
      • 0
        Если у вас GUI — 50% кода, то конечно, лучше делать нативно. В относительно среднем проекте, как я уже писал выше, мы добились того от 60 до 80% кода приложения на платформе общие с другими платформами. Детально разделение примерно следующее: 80% на wp7, так как весь GUI описывается в XAML и нет почти Behind-кода. 70% на Android, так как есть различные адаптеры, которые нужно писать из кода, но основная часть интерфейса все равно декларативная в AXML. И 60% у iOS, в основном из-за скудных возможностей InterfaceBuilder.

        При мало-мальски сложной бизнес-логике и необходимости дальнейшего её развития, кросс-платформенный подход себя оправдывает. Xamarin оправдывает себя потому, что пользователи хотят видеть качественный, нативный интерфейс в стилистике платформы, и Xamarin позволяет это сделать.
  • +6
    Не совсем понятно, что Вы имеете ввиду, говоря о «дабл-кликах». Нативные приложения не блокируют автоматически управляющие элементы. Потому что это нужно далеко не всегда. И именно разработчик должен это сделать, если такая необходимость есть.
    • 0
      Например, в iOS есть понятие MainThread, где происходит обработка событий от пользователя. Функция-обработчик события всегда вызывается в MainThread, и элемент, с которого получено событие, становится недоступным. Если в обработчике не написать переход в другой тред, то на время обработки интерфейс как бы подвисает, что правильно для коротких действий или например для открытия экрана. Если же требуется длительная обработка — загрузка данных и т.п., то кнопка блокируется вручную, и делается вызов обработки в другом треде.

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

      На титаниуме же, вы можете дважды нажать на кнопку и открыть два экрана. Стоит уточнить, что на тот момент, блокировка кнопки установкой enabled = false не работала, а на некоторых элементах интерфейса не работает до сих пор jira.appcelerator.org/browse/TIMOB-12668
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Мы старались писать так, чтобы статья была понятна не только программистам. Кратко и понятно описать потоки довольно сложно :)
  • 0
    И за этот титаниум надо платить деньги?!
    • 0
      нет.
  • 0
    Спасибо за вести с полей.
    Подозревал, что с кроссплатформенными фреймворками не очень хорошо все, но чтоб настолько печально…
    • 0
      И вообще хотелось бы узнать, а выгодно ли писать на кроссплатформенным фреймворке. Код заметно выпростает и усложняется из-за различий платформ. И писать его опять же сложней. Может статься, что время написания двух версий с нуля будет меньше чем единой универсальной. Плюс написание сразу двух версий под разные платформы отлично распараллеливается.

      P.S. По моему личному мнений динамически-типатизируемые языки сильно замедляют процесс обработки. Ибо просто не встречал сред разработки для javascript которые позволяют быстро и не отходя от кассы понять какого типа эта переменная и какие функции / поля у нее есть.
      • 0
        Все очень сильно зависит от задачи. Если речь идет о бизнес-приложениях, для которых все левые свистоперделки не особо нужны, то с тем же phonegap (к сожалению статистики по ксамарину и титаниуму у меня нету), если дизайн разрабатывался с учетом того что мы имеем дело с гибридом, оценка проекта всего на 10-20% выше чем у нативных проектов. Причем добавление новой платформы это все те же 10% времени, так что если нам (а точнее если это нужно клиенту) нужно реализовать проект на 3 платформы, то клиенту это обойдется раза в ~2.5 дешевле. Но это при наличии команды, у которой есть опыт разработки таких приложений, и если задача оптимизирована под гибриды, ну и было бы неплохо иметь нативщиков, на которых можно переложить задачи, которые проще и лучше реализовать нативно.

        Но опять же все зависит от проекта, задач и требований.
        • 0
          По поводу javascript, если вам нужна статическая типизация, вы можете вооружиться dart
        • 0
          Поверю специалистам что оценка времени на проект, всего лишь на 10-20% больше чем у нативного. А как с качеством и быстротой работы у гибридов? Судя по выше написанному существует определенные проблемы с большим количеством нажатий в секунду. Скажем скрол list view и тут же мгновенный выбор ячеек и открытие нового окна нормально работает? Или же с точки зрения пользователя интерфейс 0.5 сек лагает?
          • 0
            ну начнем с того что у вас нету ничего типа «list view» и окон. Лаг к 0,3 секунды при нажатии обходится обработкой тач-событий либо же обработкой нативных событий как я уже описывал выше. Лаги с отрисовкой — все зависит от верстки и количества элементов. Определенный лаг всеравно будет, но не настолько критичный. К слову я и в нативных проектах видел проблемы с подобным. Ну и еще все зависит от платформы, скажем на ios в силу некоторых особенностей ui рендрится намного быстрее (приоритет процесса с webview повыше), а на андроиде уже нехилые лаги заметны. Хотя на 4.4 этих налогов уже нету.

            Опять же все нужно оценивать. Если основной фичей вашего приложения должно быть отображение огромной таблицы, с переходом по клику на ячейку, и будут поддерживаться только две платформы, возможно будет эффективнее реализовать это дело нативно. Еще как вариант, если это только часть функционала, реализовать только саму таблицу нативно, и открывать скрин с webview (так делают, есть так же steroids.js но он уныл и проприетарный).
            • 0
              >Если основной фичей вашего приложения должно быть отображение огромной таблицы, с переходом по клику на ячейку, и будут поддерживаться только две платформы, возможно будет эффективнее реализовать это дело нативно

              Если это действительно огромная таблица, то практически только нативно, либо трансляцией в нативный код (как у Xamarin). Иначе будет падать по памяти на каждом шагу, а ничего поправить вы не сможете, ибо работа с памятью web view это вещь в себе.
              • 0
                А сколько это, огромная таблица? Я думаю что если в таблице больше 100К ячеек, то что-то пошло не так при проектировании интерфейса.
                • 0
                  Подозреваю что проект на javasript спокойно может загнуться и при просмотре списка товаров из 100 наименований с картинками
                  • 0
                    При грамотном подходе и при 10К не загнется.
                • –1
                  Любая галерея, сотня — другая картинок, например.
                  • 0
                    у вас падает браузер при просмотре google images с мобильного устройства?
                    • 0
                      У гугла хорошие инженеры. Если тупо загнать картинки в DOM — да, все упадет нафиг. Их надо асинхронно загружать и выгружать. И все это будет дергаться и тупить, прямо как у гугла или фликра, о 60fps речи не будет никакой.
                      • 0
                        я о том и говорю, смотря как сделать. И нативный вариант да, будет лучше, но опять же если говорить о том, что бы делать такую галерею с нуля, и для нативного ui придется делать подобные оптимизации.
                        • 0
                          Оптимизации делать придётся, но прослойка между программой и системой будет меньше.
                        • 0
                          >для нативного ui придется делать подобные оптимизации.
                          Да, еще как придется. Я возился с быстрым скроллом сотен картинок очень долго. Оперативки выделяемой приложению в том же айфоне хватает на несколько десятков полноразмерных картинок, поэтому обычно этого не замечают. Но если речь о сотнях то все, вешалка и накручивание всякого разного.
                          • 0
                            Советую посмотреть SSToolkit c его помощью довольно просто можно сделать бесконечный плавный скролл. При этом загрузка/выгрузка невидимых частей уже большей частью написана.
  • +1
    Как здесь уже правильно заметили, проблему дабл-кликов следует решать путем обработки touch-ивентов, а не кликов. И потом, возможно я чего-то не понимаю, но что вам мешает в коллбеке на клик или тап проставлять event.currentTarget'у атрибут disabled до момента полной обработки события?
    • 0
      Ну так это ж не phonegap.
  • +2
    Тут по поводу Xamarin вспомнили, вставлю и свои 5 копеек, так как имею честь с ним работать в последнее время. Хороший фреймворк, в целом багов особо не заметил(если несколько неудобных моментов, которые впринципе автоматизируются). Но для работы с ним нужно знать, как минимум азы разработки под конкретную платформу. Если пишешь под iOS, нужно изучить разработку под iOS, тоже самое с Android. Хотя и Xamarin делает библиотеку Monotouch.Dialog, Monodroid.Dialog для унификации работы с интерфейсами, в целом все же надо знать как работать с конкретной платформой.

    Но Xamarin дает возможность выносить работу с бизнес-логикой, с датой, с сервисами в отдельные Portable Library, остается только писать интерфейс. Ну и в ООП хватает стратегий и паттернов, что бы унифицировать ядро приложение, главное им уметь правильно следовать.

    Резюмируя вышесказнное могу сказать, что порог вхождения в Xamarin довольно высок, с виду он решает меньше проблем, чем тот же Titanium, да и шаблоны проектирования нужно подтягивать. Но с другой стороны, Xamarin надежней того же Titanium и имеет высокую производительность, а так же доступ к более менее богатой инфраструктуре Mono(.Net). Так например я спокойно использую в проекте библиотеки по работе с Rest (RestSharp), работа с json (Newton json.net) и другие. Применяю Linq, generics и другие прелести C#. К тому же вся бизнес логика вынесена в отдельную библиотеку, а работа с ресурами(картинки, звуки и т.п.) решаются с помощью build скрипта.
    • 0
      Так же замечу, что в Xamarin можно строить интерфейс родными стредствами (iOS Interface Builder), а для Дроида есть встроенный редактор в саму Xamarin Studio. Так же есть plugin для Visual Studio, в том числе и для iOS(но в случае с iOS build server должен быть все еще на маке).
      • 0
        Для дроида лучше интерфейс дизайнить в Android Studio, а потом копипастить в Xamarin.
    • 0
      мы тоже работаем с Xamarin. Фреймворк замечательный, но проблем у него хватает тоже, и надо очень хорошо понимать и устройство каждой платформы, и как работает сам Моно
  • 0
    Попробуйте Steroids.js. Это дополнение к Phonegap, которое позволяет поднять уровень производительности обычных Phonegap-приложений за счет использования сразу нескольких WebView'ов. Также у них в поставке есть интересные кроссплатформенные дополнения к Phonegap.

    Я сейчас консультирую одну команду, которая пишет проект на связке Angular.js + Steroids.js, в целом процесс идет хорошо. Есть некоторые проблемы, озвученные Вами выше, но нам инструменты нравятся.
    • 0
      у стероидов есть один минус, который на корню убил желание с ним работать. Сборка только через их cloud-сервис, слабая поддержка android (в плане нативных фич, причем законтрибьютить не выйдет ибо нативная часть является закрытой).

      Вообще идея интересная, и я даже пытался такое соорудить сам, но увы пришел к выводу что лучше заложить часов 8 на интеграцию нативного drawer (например), чем возиться с multiple webview. Хотя и это так же не проблема.
  • 0
    А то нибудь использовал Intel XDK или ionic framework?
  • 0
    Javascript — отсутствие типизации замедляет процесс написания кода и усложняет сопровождение.

    Дальше можно не читать…
    • 0
      А почему вы считаете что это не так? Процесс написания кода оно конечно не замедляет, но усложняет поиск багов что влияет на процесс сопровождения. Хотя опять же все относительно.
      • 0
        Поддерживаю отсутствие типизации довольно ощутимо сказывается на скорости разработки уже даже на средних проектах ( 10 000+ строчек кода). Просто не возможно удержать в голове все объекты и какими полями они обладают. Постоянно приходится лазить по коду, чтобы просто вспомнить с каким объектом ты работаешь.
        • 0
          ну обычно все же пытаются юзать какой jsdoc что бы не держать все это в голове, да и размеры функций стараются держать не такими уж большими…
          • 0
            Попытка разбиения на множество мелких функций в этом случае приводит к умножению сущностей. Приходится помнить что принимает эта функция на вход, что отдает. Комментарии же имеют свойство устаревать, и изначально могут быть некорректными.
            В итоге приходится тратить много времени на вещь на побочные вещи, а не собственно кодить.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка