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

в 6:51, , рубрики: Intel Multi-OS Engine, java, Блог компании Intel, Программирование, разработка, разработка мобильных приложений, метки:

Опыт использования Intel Multi-OS Engine для разработки iOS-приложения на Java - 1В августе на 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-версии и переключаться между ними, просто выбрав нужную:

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

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

Для реализации отображения «бегущих» графиков были использованы наработки по работе с 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js