Компания
184,00
рейтинг
18 января в 09:51

Разработка → Опыт использования Intel Multi-OS Engine для разработки iOS-приложения на Java

В августе на Intel Developer Forum в Сан-Франциско мы представили нативное мобильное приложение для iPаd для мониторинга пациентов, разработанное с помощью платформы Intel Multi-OS Engine. Приложение предоставляет данные о наиболее важных параметрах состояния пациента, подключаясь к прикроватным мониторам по WiFi-сети (более подробно о самом приложении и его функционале можно почитать на нашем сайте).
В данной статье мы поделимся опытом использования платформы Intel Multi-OS Engine, которая позволяет разрабатывать нативные приложения для iOS на Java.

В том случае, когда требуется разработать приложения как для Android, так и для iOS, возможность использовать Java для разработки приложений для iOS позволяет экономить время и ресурсы на разработку.

Основные преимущества работы с Intel Multi-OS Engine


UIElements


Основываясь на опыте, можно сказать, что основное положительное свойство платформы Multi OS-Engine — это возможность работать с нативными iOS UI элементами, практически так же, как в XCode. Все элементы, необходимые для разработки приложения, уже имелись в платформе, и, соответственно, не потребовалось добавлять что-то дополнительно. Каждый элемент полностью описан: присутствуют все характеристики и методы, что упрощает работу с платформой разработчикам, имеющим опыт работы с iOS.
Правильная организация приложения
В связи с тем, что данная платформа в дальнейшем позволит разрабатывать приложения сразу для Android и iOS, необходимо очень аккуратно подходить к разработке архитектуры приложения, правильно разделять общие и UI specific функциональности. Это позволяет строить очень точные и легкие приложения и организовывает разработчика.

Возможности Java


Преимуществом данной платформы является то, что она позволяет реализовать большинство возможностей Java. Например, рассмотрим работу с потоками и файлами:

Thread  thread = new Thread() {
    public void run() {
     
    }
};
thread.start();
private void openFileToRead() {
    String fileId = "hb";
    NSBundle mainBundle = NSBundle.mainBundle();
    String pathToFile = mainBundle.pathForResourceOfType(fileId, "dat");

    File file = new File(pathToFile);
    FileInputStream fis = null;

    try {
        fis = new FileInputStream(file);
        DataInputStream dis = new DataInputStream(fis);

        //что-то делаем с input stream

    } catch (IOException e) {
        e.printStackTrace();
    }
}


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

Удобно и быстро пишем iOS специфичную часть на Java


Дополнительно можно использовать ObjC-подход для запуска в бэкграунде. В нашем случае при старте приложения мы запускали процесс чтения файлов:

@Override
@Selector("application:didFinishLaunchingWithOptions:")
public boolean applicationDidFinishLaunchingWithOptions(UIApplication application, NSDictionary launchOptions) {

    performSelectorInBackgroundWithObject(new SEL("initQueueDispatcher"), null);

    return true;
}

@Selector("initQueueDispatcher")
@Generated
public void initQueueDispatcher() {
    QueueDispatcher.sharedQueueDispatcher().initQueue();
}

Запуск отдельных функций в главном потоке тоже не является проблемой:

public void heartRate(PatientRealData data) {
   performSelectorOnMainThreadWithObjectWaitUntilDone(new SEL("updatePatientData:"), 
}

@Selector("updatePatientData:")
@Generated
public void updatePatientData(PatientRealData data) {
    mHrLabel.setText(String.valueOf(data.getHeartRate()));
}

Доступ к ресурсам аналогичен с iOS API: чтобы получить изображение «alarm_on.png» из ресурсов и назначить его кнопке, достаточно выполнить следующее:

UIImage image = UIImage.imageNamed("alarm_on");
mAlarmButton.setImageForState(image, UIControlState.Normal);

Очень удобно, что синтаксис работы с iOS API на Java практически не отличается от оригинала на ObjC. К примеру, меню, основанное на UITableViewController, добавляли таким образом:

@com.intel.inde.moe.natj.general.ann.Runtime(ObjCRuntime.class)
@ObjCClassName("PatientsTableVC")
@RegisterOnStartup
public class PatientsTableVC extends UITableViewController {

    static {
        NatJ.register();
    }

    @Generated("NatJ")
    @Owned
    @Selector("alloc")
    public static native PatientsTableVC alloc();

    @Generated("NatJ")
    @Owned
    @Selector("init")
    public native PatientsTableVC init();

    @Generated("NatJ")
    protected PatientsTableVC(Pointer peer) {
        super(peer);
    }

    private ArrayList<PatientInfo> mPatients = new ArrayList<PatientInfo>();

    @Selector("prefersStatusBarHidden")
    @Override
    public boolean prefersStatusBarHidden() {
        return true;
    }

    @Selector("viewDidLoad")
    @Override
    public void viewDidLoad() {
        setTitle("Select patient:");
    }

    @Selector("numberOfSectionsInTableView:")
    @Override
    @NInt
    public long numberOfSectionsInTableView(UITableView tableView) {
        return 1;
    }

    @Selector("tableView:numberOfRowsInSection:")
    @Override
    @NInt
    public long tableViewNumberOfRowsInSection(UITableView tableView, long section) {
        return mPatients.size();
    }

    @Selector("tableView:cellForRowAtIndexPath:")
    @Override
    public UITableViewCell tableViewCellForRowAtIndexPath(UITableView tableView, NSIndexPath indexPath) {

        String reusableId = "patientCell";
        UITableViewCell cell = 
            (UITableViewCell) tableView.dequeueReusableCellWithIdentifierForIndexPath(reusableId, indexPath);
        PatientInfo patient = mPatients.get((int) indexPath.row());
        cell.textLabel().setText(patient.description());

        return cell;
    }

    @Selector("prepareForSegue:sender:")
    @Generated
    public void prepareForSegueSender(UIStoryboardSegue segue, NSObject sender) {
        NSIndexPath indexPath = tableView().indexPathForSelectedRow();
        PatientInfo patient = mPatients.get((int) indexPath.row());
        MainMonitorVC controller = (MainMonitorVC) segue.destinationViewController();
        controller.setPatient(patient);
    }
}

Multi-OS Engine Plugin для Android Studio


Серьезным преимуществом платформы от Intel является «глубина» интеграции плагина Multi-OS Engine в Android Studio. Можно практически всю разработку проводить в Android Studio. При этом можно с легкостью настроить проект на работу сразу с двумя платформами. Для этого достаточно настроить конфигурации для запуска Android- и iOS-версии и переключаться между ними, просто выбрав нужную:



Подключение графиков


Для реализации отображения «бегущих» графиков были использованы наработки по работе с OpenGL, написанные на C. Для их использования был написан UIWaveFormVC контроллер на ObjC, где уже был добавлен C-код. Данный контроллер по входным данным отрисовывает на OpenGL View соответствующие точки с заданной скоростью и цветом.

UIWaveFormVC.h
@interface UIWaveFormVC : GLKViewController
@property (nonatomic, strong) DPSampleQueue * inputQueue;

- (void)setDataQueue:(DPSampleQueue *) dataQueue;
- (void)setWaveColor:(UIColor *)waveColor;
- (void)setSampleFreq:(float)sampleFreq;

UIWaveFormVC.m
#import "UIWaveFormVC.h"
@interface UIWaveFormVC ()
@property (strong, nonatomic) EAGLContext * context;
@end
- (void)setDataQueue:(DPSampleQueue *) dataQueue {
    self.inputQueue = dataQueue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    if (!self.context)
        NSLog(@"Failed to create ES context");
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    // сложная логика рисования графиков
}


Далее для использования данного класса внутри Multi OS-Engine нам необходимо сгенерировать для него «обертку», то есть осуществить биндинг Java к ObjC:

UIWaveFormVC.java
@com.intel.inde.moe.natj.general.ann.Runtime(ObjCRuntime.class)
@ObjCClassName("UIWaveFormVC")
@RegisterOnStartup
public class UIWaveFormVC extends GLKViewController {

    @Generated("NatJ")
    protected UIWaveFormVC(Pointer peer) {
        super(peer);
    }

    @Selector("setDataQueue:")
    @Generated
    public native void setDataQueue(DPSampleQueue dataQueue);

    @Selector("setWaveColor:")
    @Generated
    public native void setWaveColor(UIColor waveColor);

    @Selector("setSampleFreq:")
    @Generated
    public native void setSampleFreq(float sampleFreq);

    static {
        NatJ.register();
    }
}

Далее мы просто добавляем UIWaveFormVC в логику экранов в MainUI.storyboard.

Затем вся работа проводится уже непосредственно из Java. Для передачи данных нашему UIWaveFormVC мы объявляем метод prepareForSequeSender(), который позволяет получить экземпляр класса контроллера до его отображения и передать ему данные.

@com.intel.inde.moe.natj.general.ann.Runtime(ObjCRuntime.class)
@ObjCClassName("MainMonitorVC")
@RegisterOnStartup
public class MainMonitorVC extends UIViewController {
    static {
        NatJ.register();
    }

    @Selector("alloc")
    public static native MainMonitorVC alloc();

    @Selector("init")
    public native MainMonitorVC init();

    @Generated("NatJ")
    protected MainMonitorVC(Pointer peer) {
        super(peer);
    }
    private QueueDispatcher mQueueDispatcher = null;

    @Selector("prepareForSegue:sender:")
    @Generated
    public void prepareForSegueSender(UIStoryboardSegue segue, NSObject sender) {
        if (segue.identifier() == null) return;

        UIWaveFormVC controller = (UIWaveFormVC) (segue.destinationViewController());
        controller.setDataQueue(sharedQueueDispatcher().queueWithID(segue.identifier()));
        controller.setSampleFreq(SAMPLE_FREQ);
        controller.setWaveColor(WAVE_GREEN);
   }
                    
    private QueueDispatcher sharedQueueDispatcher() {
        if (mQueueDispatcher == null) {
            mQueueDispatcher = QueueDispatcher.sharedQueueDispatcher();
	  mQueueDispatcher.startDataLoading();
        }
        return mQueueDispatcher;
    }
    
}

Рекомендации по улучшению Intel Multi-OS Engine


• В связи с отсутствием поддержки ODBC драйвера для базы данных проблематично сделать одну унифицированную базу сразу для Android и iOS версии.
• Для поддержки https на iOS требуется приложить некоторые усилия: необходимо вручную добавлять сертификаты от Android сборки.
• При использования сторонних библиотек иногда требуется вносить изменения в настройки proguard, но возможность сделать это стандартным способом через Gradle отсутствует. В результате приходится добавлять нужные флаги вручную.
Сделать это можно в файле proguard.cfg, который находится в /Applications/Intel/INDE/multi_os_engine/tools. Флаги следует просто добавить в конец файла. В нашем случае мы добавили следующие флаги, чтобы использовать Retrofit:

-keepattributes *Annotation*
-keep class retrofit.** { *; }
-keepclasseswithmembers class * {
@retrofit.http.* <methods>; }
-keepattributes Signature

• Отсутствует возможность редактировать storyboard непосредственно в Android Studio, приходилось верстать интерфейс в XCode.

Мы радостью продолжим использовать платформы Intel Multi-OS Engine в наших проектах по разработке мобильных решений, поскольку мы рассматриваем этот опыт как новую возможность приобрести уникальную экспертизу и продемонстрировать свое умение справляться со сложными R&D задачами.
Автор: @saul
Intel
рейтинг 184,00

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

  • 0
    А с RoboVM вы не работали? Хотелось бы услышать хотя бы субъективную разницу между ними.
    • +1
      Топик от лица конторы auriga, в целях совместного пиара;

      разница (отсюда)
      У RVM отладка и интеграция с Interface Builder являются платными, у Intel MOE — бесплатные (платных функций нет как таковых). И он тоже генерирует нативный код. Помимо нативного кода MOE унаследовал все преимущества ART-а, например более продвинутый Garbage Collector.
    • 0
      Есть разница в подходе к оборачиванию iOS API: в RoboVM API приводят к Java-style, а MOE старая сохранить максимальную похожесть на Objective-C
  • 0
    А можно поподробнее узнать о внутренностях MOE? Как компилируется Java код? AOT компиляция или JVM выполняет byte code? Каков размер приложений под iOS? В общем, вопросов куча. А то получается, предоставили, на первый взгляд, интересную вещь — писать на Java под iOS бесплатно, но никаких подробностей нет!
    P.S. Про RoboVM в курсе — у них такие конские цены, что даже рассматриват не хочется.
    P.P.S. Когда написал, хабр предложил мне топик habrahabr.ru/company/intel/blog/266653 так что вопрос снимается. Пойду читать.
  • 0
    Собрал из любопытства вот этот пример (2 экрана, простая лента)

    Android: 4Mb
    iOS: 74Mb и оно ломается при запуске на устройстве

    Надо иметь в виду, что
    Intel’s Multi-OS Engine is a preview technology
    • 0
      Мы работаем над уменьшением размера финального приложения. Не могли бы вы поподробнее рассказать про «ломается». Можно в личку.

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

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