Поиск по этому блогу

понедельник, 5 ноября 2012 г.

Hello ANE! Или как создать нативное расширение для iOS

В этой статье я расскажу как создать простое нативное расширение для платформы iOS. А так же разберем как созданное расширение применяется в ActionScript проекте. Нам понадобятся компиляторы Adobe Flash Builder и Xcode. Подразумевается что вы знаете как создавать swc библиотеки и actionscript-приложения  в Adobe Flash Builder. Если вы не знаете как это делается, советую почитать соответствующую литературу.  Знаний по ObjectiveC/Xcode не требуется.

Разработка нативного расширения делится на три этапа:
  1. создание библиотеки SWC
  2. создание библиотеки Xcode
  3. сборка пакета ANE
Разберем каждый этап подробнее.


Создание библиотеки SWC.
Ниже приведен листинг класса HelloAne.as
package com.anedevelop.helloane
{
 import flash.events.EventDispatcher;
 import flash.external.ExtensionContext;

 public class HelloAne extends EventDispatcher
 {
  private var context:ExtensionContext;
  public function HelloAne()
  {
   try {
    context = ExtensionContext.createExtensionContext('com.anedevelop.helloane', '');
   } catch (error:Error) {
    // если context не создается - проверьте правильность указания
    // ID расширения: com.anedevelop.helloane
    // это же значение необходимо указать в файле extension.xml
   }
  }
  
  public function run():String
  {
   if (context != null) {
    return context.call('runHelloAne') as String;
   }
   return null;
  }
 }
}
Разберем его подробнее. В конструкторе класса мы создаем экземпляр класса ExtensionContext используя идентификатор расширения:com.anedevelop.helloane. Вы можете использовать любой идентификатор. Второй параметр в методе createExtensionContext может понадобится в том случае если вы хотите использовать в одном расширении разную логику. Мы не будем рассматривать эту возможность в рамках этой статьи.
В методе run класса HelloAne мы вызываем медом runHelloAne у созданного экземпляры ContextInterface. Далее FlashPlayr вызовет метод с именем runHelloAne в проекте Xcode. Собираем из полученного класса библиотеку helloaneLib.swc. Когда создаете библиотеку SWC необходимо в настройках проекта Flash Builder поставить галочку Include Adobe AIR libraries.



Создание библиотеки Xcode.
Создайте проект библиотеки Xcode выбрав пункт меню: File->New->Project. В появившемся окне в левой колонке кликаем на пункт Framework & Library раздела iOS, в правой части панели выбираем проект с именем Cocoa Touch Static Library и нажимаем кнопку Next:
На предыдущем скрине вы наверняка обратили внимание на пункт AIR Native Extension в списке шаблонов проекта. Это шаблон который позволяет быстро создавать проект для компиляции статической библиотеки и сборки пакета ANE. Об этом шаблоне я подробно расскажу в последующих статьях. А пока попробуем разобраться со стандартными средствами :)

На следующем шаге указываем имя проекта helloane, и другую не очень важную на данный момент информацию. Нажимаем кнопку Next:

На последнем этапе выбираем каталог для проекта и сохраняем его:

Создав проект необходимо положить в каталог проекта файл FlashRuntimeExtensions.h и добавить его с помощью пункта меню File->Add files to "helloane". Файлик должен появится в проекте как указано на скриншоте.

По умолчанию Xcode компилирует результат во временный системный каталог. В своих проектах я настраиваю Xcode так что бы он компилировал результат в каталог build, рядом с каталогом приложения. Для этого кликните на название проекта(helloane) в левой части Xcode. А затем в центральной части Xcode кликаем на targets->helloane. В правой части Xcode откроется окно с настройками, кликаем на вкладку Build Settings и находим поле с Pre-configuration Build Products Path и пишем туда: $(PROJECT_DIR)/build/.


Разработку Xcode приложения для вашего нативного расширения можно условно разделить на 3 шага:
  1. Глобальная инициализация
  2. Локальная инициализация
  3. API компонента для вашего приложения

- Глобальная инициализация это два метода которые  вызывает Flash Player в момент создания/уничтожения вашего нативного расширения. В нашем примере эти методы называются helloaneExtInitializer и helloaneExtFinalizer. Назовем их методами Глобальной инициализации.
- Локальная инициализация подразумевает создание функций которые будут вызваны из методов глобальной инициализации для создания/уничтожения вашего компонента. Возникает вопрос зачем два метода инициализации? Честно говоря, на момент написания это статьи я не знал ответа, если кто то сможет объяснить зачем это нужно - милости просим к обсуждению.
- API компонента это те методы, которые вы будете вызывать из flash-приложения. В нашем случае один метод runHelloAne.

Возможно вы уже заметили в проекте два основных файла: helloane.h и helloane.m. Файл с расширением .h содержит прототипы всех функций которые мы будем использовать в файле с расширением .m. Рассмотрим листинг файла helloane.h:
/*
 Здесь мы создаем прототип нашего приложения.
 */

#import <Foundation/Foundation.h>
#import "FlashRuntimeExtensions.h"

/////// Шаг первый. Глобальная инициализация кмопонента **************
// Создание компонента
void helloaneExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet);
// Уничтожение компонента
void helloaneExtFinalizer(void* extData);

/////// Шаг второй. Локальная инициализация **************
// Создание компонента
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet);
// Уничтожение компонента
void ContextFinalizer(FREContext ctx);

/////// Шаг третий. API компонента для вашего приложения **************
// Метод который будет доступен из flash-приложения
FREObject runHelloAne(FREContext ctx, void *data, uint32_t argc, FREObject argv[]);



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

helloaneExtInitializer
  - void** extDataToSet : указатель на указатель (смешные они, ObjectiveC-разработчики :) ) данных для кастомной инициализации нативного расширения. Как пользоваться этим, пока не разобрался возможно расскажу в следующих статьях.
  - FREContextInitializer* ctxInitializerToSet : указатель на метод локальной инициализации
  - FREContextFinalizer* ctxFinalizerToSet : указатель на метод локальной финализации
ContextInitializer
  - void* extData : судя по всему это такая же шляпа что и в методе  helloaneExtInitializer.
  - const uint8_t* ctxType : тип нативного расширения, здесь передается значение которые вы передаете во втором аргументе в методе ExtensionContext.createExtensionContext в библиотеке SWC, в нашем случае используется пустая строка.
  -  FREContext ctx : объект обеспечивающий связь между xcode и flash приложениями. Например через него можно отправить вызов из xcode приложения во flash.
  - uint32_t* numFunctionsToTest : указатель на количество наших api-методов, которые мы будем вызывать из flash проекта.
  - const FRENamedFunction** functionsToSet : массив api-методов
runHelloAne
  - FREContext ctx : ссылка на объект обеспечивающий связь между xcode и flash 
  - void *data : эта шляпа как то связана с данными которые передаются в  extDataToSet и extData. Когда нибудь я разберусь как это работает :)
  - uint32_t argc : Количество аргументов переданных из flash приложения
  - FREObject argv[] : массив аргументов переданных из flash приложения.

Разобравшись с прототипами функций, давайте посмотрим на их реализацию в файле helloane.m
//  helloane.m
//  helloane
//
//  Created by Serious Sam on 01.11.12.
//  Copyright (c) 2012 anedevelop.com. All rights reserved.
//

#import "helloane.h"

/////// Шаг первый. Глобальная инициализация кмопонента **************
// Глобальная инициализация
void helloaneExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
    *extDataToSet = NULL;
    
    // передаем ссылки на наши методы инициализации и освобождения памяти
    *ctxInitializerToSet = &ContextInitializer;
    *ctxFinalizerToSet = &ContextFinalizer;
}
// Уничтожение компонента
void helloaneExtFinalizer(void* extData)
{
}

/////// Шаг второй. Локальная инициализация **************
// Создание компонента
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
    static FRENamedFunction func[] =
    {
        // здесь можно через запятую передать сколько угодно функций
        { (const uint8_t*)"runHelloAne", NULL, runHelloAne }
    };
    *numFunctionsToTest = sizeof(func) / sizeof(FRENamedFunction);
    *functionsToSet = func;
}
// Уничтожение компонента
void ContextFinalizer(FREContext ctx)
{
    return;
}

/////// Шаг третий. API компонента для вашего приложения **************
BOOL resultValue = NO;
// Наш основной метод, который будет вызываться из flash приложения
FREObject runHelloAne(FREContext ctx, void *data, uint32_t argc, FREObject argv[])
{
    // поочередно будет возвращать значение true и false
    resultValue = !resultValue;
    // создаем объект который необходимо вернуть после выполнения метода
    FREObject freResult; 
    // присваиваем объекту result значение resultValue
    FRENewObjectFromBool(resultValue, &freResult); 
    // возвращаем результат freResult
    return freResult;
}
Наше xcode-приложение готово.   Нажимаем Cmd+B (или Product->Build), в каталоге build в каталоге вашего приложение должен появится файлик статической библиотеки: libhelloane.a


Сборка пакета ANE
Итак мы добрались до последнего этапа создания нативного расширения. В предыдущих двух этапах мы создали две библиотеки helloaneLib.swc и libhelloane.a. Теперь необходимо из этих двух библиотек создать пакет расширения ane. Делается это с помощью компилятора от Adobe adt, он работает в командной строке. Ниже приведен листинг shell-скрипта который собирает наш пакет ane:
#!/bin/sh

AIR_SDK_PATH="~/sdks/AdobeAIRSDK_3.3/"
ANE_NAME="hello.ane"
SWC_FILE_NAME="helloaneLib.swc"
STATIC_LIB_NAME="libhelloane.a"
BUILD_DIR="./build/"

mkdir $BUILD_DIR

cp -f "extension.xml" $BUILD_DIR"extension.xml"
cp -f "platformoptions.xml" $BUILD_DIR"platformoptions.xml"
cp -f "../xcode/build/"$STATIC_LIB_NAME $BUILD_DIR
cp -f "../asLib/bin/"$SWC_FILE_NAME $BUILD_DIR

/usr/bin/unzip -o "$BUILD_DIR$SWC_FILE_NAME" -d "$BUILD_DIR"
rm $BUILD_DIR"catalog.xml"

pushd "$BUILD_DIR"
"$AIR_SDK_PATH"/bin/adt -package -target ane "$ANE_NAME" extension.xml -swc "$SWC_FILE_NAME" -platform default library.swf -platform iPhone-ARM -platformoptions platformoptions.xml "$STATIC_LIB_NAME" library.swf
popd

rm $BUILD_DIR$SWC_FILE_NAME
rm $BUILD_DIR$STATIC_LIB_NAME
rm $BUILD_DIR"extension.xml"
rm $BUILD_DIR"platformoptions.xml"
rm $BUILD_DIR"library.swf"

Для работоспособности скрипта необходимо проверить следующее:
  1. AIR_SDK_PATH - путь к каталогу, где лежит AIR SDK
  2. Убедитесь что рядом с этим скриптом лежат файлы extension.xml и platformoptions.xml. Описание этих файлов будет ниже.
  3. Убедитесь что путь до файла статической библиотеки задан верно:
    "../xcode/build/"$STATIC_LIB_NAME
  4. Убедитесь что путь до библиотеки swc задан верно:
    "../asLib/bin/"$SWC_FILE_NAME  
extension.xml
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
 <id>com.anedevelop.helloane</id>
 <versionNumber>1</versionNumber>
 <platforms>
  <platform name="iPhone-ARM">
   <applicationDeployment>
    <nativeLibrary>libhelloane.a</nativeLibrary>
    <initializer>helloaneExtInitializer</initializer>
    <finalizer>helloaneExtFinalizer</finalizer>
   </applicationDeployment>
  </platform>
  <platform name="default"> 
   <applicationDeployment/> 
  </platform>
 </platforms>
</extension>
 Описание параметров:
  1. id - идентификатор расширения, значение должно совпадать со значением первого аргумента в методе ExtensionContext.createExtensionContext в библиотеке SWC. 
  2. nativeLibrary - название статической библиотеки
  3. initializer - название метода глобальной инициализации, значение должно совпадать с методом глобальной инициализации, которое мы использовали в проекте Xcode.
  4. finalizer - название метода глобальной финализации в проекте Xcode
platformoptions.xml
<platform xmlns="http://ns.adobe.com/air/extension/3.1">
    <sdkVersion>6.0</sdkVersion>
    <description >Hello Ane extension</description>
    <copyright>Serious Sam (c) 10.2012</copyright>
    <linkerOptions>
    </linkerOptions> 
</platform>
Здесь все понятно, кроме linkerOptions. Если в проекте Xcode вы используете дополнительные фреймворки (frameworks) то в разделе linkerOptions необходимо указать названия этих фреймворков, что бы они были принудительно внедрены в ваш пакет нативного расширения.  Использование этого параметра мы рассмотрим когда будем разбирать пример нативного расширения для получения данных контактов из адресной книги устройства.


Использование нативного расширения в проекте flash
А теперь самое интересное - создадим проект flash с использованием нативного расширения: File->New->ActionScript Mobile Project. Вводим имя и переходим к следующему шагу. На этапе Mobile Settings отключаем галочки BlackBerry Tables OS и Google Android, т.к а данной статье мы разбираем разработку расширения только для платформы iOS.



Откройте свойства проекта, далее в левом списке кликаем на раздел ActionScript Build Path. В правой части окна выбираем вкладку Native Extensions. Нажимаем кнопку Add ANE и выбираем созданный ранее пакет hello.ane и нажимаем ОК что бы изменения вступили в силу.



Открываем свойства еще раз, раскрываем список ActionScript Build Packiging и выбираем пункт Apple iOS. В правой части экрана видим 4 вкладки:
  1. Digital signature
  2. Package Contents
  3. Entitlements
  4. Native Extensions

  - Digital signature Здесь мы указываем путь к Apple-сертификату и Provisioning файлу. Получить их можно на сайте https://developer.apple.com/ios/manage/certificates/team/index.action. Получение сертификата Apple задача не тривиальная, по крайней мере так было для меня :). Если кому интересно расскажу подробнее об этом в отдельной статье.
  - Package Contents Здесь мы указываем список файлов которые мы хотим загрузить в наше приложение. Имейте ввиду что загружать SWF файлы с кодом ActionScript нельзя, допускается только загрузка swf файлов с анимацией на Timeline. Я глубоко опечалился узнав об этом ограничении. Но ничего не поделаешь - это политика безопасности.
  - Entitlements Пустая бесполезная страница.
  - Native Extensions Здесь мы видим список используемых нативных расширений. Поставьте галочку Package, если она не установлена. Здесь же, в поле Apple iOS SDK не забудьте указать путь до каталога где лежит используемый в проекте Xcode SDK:


Обратите внимание что если вы подключили нативное расширение, то подключать SWC библиотеку не обязательно(!). Т.к. весь код из этой библиотеки лежит в пакете нативного расширения.

С настройками покончено, теперь приступим к написанию кода:
package
{
 import com.anedevelop.helloane.HelloAne;
 
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 import flash.text.TextField;
 import flash.text.TextFormat;
 
 public class testAne extends Sprite
 {
  private var textField:TextField;
  private var ane:HelloAne;
  public function testAne()
  {
   super();
   //
   textField = new TextField();
   textField.width = 200;
   textField.height = 40;
   textField.border = true;
   textField.background = true;
   textField.defaultTextFormat = new TextFormat(null, 30, 0xff0000, true);
   this.addChild(textField);
   //
   this.stage.addEventListener(MouseEvent.CLICK, stageClickHandler);
  }
  
  private function stageClickHandler(event:MouseEvent):void
  {
   if (ane == null) {
    ane = new HelloAne();
   }
   textField.text = "result: "+ane.run();
  }
 }
}
Здесь все максимально просто. Создаем объект класса HelloAne из созданной ранее библиотеки SWC, и вызываем метод run(). В текстовое поле поочередно должны выводиться значение true и false.


Ну вот и все 
Надеюсь статья была полезна. Скачать исходники можно по ссылке. Если у вас что то не получилось - буду рад помочь, пишите в коментах

4 комментария:

  1. Можно ли сделать подобную hello.ane под MacOs?

    ОтветитьУдалить
  2. Для реализации подобного расширения под MacOS, если я не ошибаюсь, необходимо:
    1) добавить платформу в файле extension.xml
    2) в командной строке "$AIR_SDK_PATH"/bin/adt -package добавить новую платформу -platform ...

    Постараюсь в ближайшее время собрать рабочий проект.

    ОтветитьУдалить
  3. Http://anedevelop.blogspot.ru/2012/12/hello-ane-for-mac-macos.html : о том, как создавать нативное расширение для платформы MacOS. Надеюсь статья окажется полезной.

    ОтветитьУдалить
  4. Круто! Спасибо.
    Долго искал где разжуют все.

    ОтветитьУдалить