Опыт использования 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 задачами.

ссылка на оригинал статьи http://habrahabr.ru/post/275305/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *