company_banner

Опыт использования 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 задачами.
    • +11
    • 9,2k
    • 6
    Intel 110,13
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 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
              Мы работаем над уменьшением размера финального приложения. Не могли бы вы поподробнее рассказать про «ломается». Можно в личку.

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

            Самое читаемое