В августе на 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 задачами.
Автор: Intel