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

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

Загрузка приложения(ipa) на устройство(iPhone) с помощью командной строки(Terminal)

Суть проблемы:
После компиляции приложения во FlashBuilder 4.6, приходится загружать ipa файл через iTunes или через органайзер Xcode-а. Это очень не удобно. Цель статьи: научить загружать  скомпилированное приложение на iУстройство автоматически сразу после компиляции во Flash Builder 4.6

Немного о Flash Builder 4.7
На момент написания статьи компанией Adobe была выпущена демо версия Flash Builder 4.7, которая как известно по умолчанию умеет загружать скомпилированное приложение по USB на iУстройство. После 2х недель работы с FlashBuilder 4.7, понял что демо версия еще очень сырая. Очень часто вылетают баги, приложение не компилится, часто приходится перегружать FB. Очень надеюсь что с выходом релизной версии все встанет на свои места.

Ближе к делу...
Погуглив немного нашел замечательную статью, о том как можно загрузить приложение на iУстройство из командной строки: http://gamua.com/blog/2012/03/how-to-deploy-ios-apps-to-the-iphone-via-the-command-line/ . Респект и уважением людям которые это сделали :) Статья правда на английском. Если вкратце, то необходимо скачать архивчик, разархивировать и вызвать в терминале команду:
transporter_chief.rb my-app.ipa

Скрипт устанавливает приложение my-app.ipa на первое найденное устройство. Если к компьютеру подключено более одного устройства, есть возможность указать на какое именно устройство необходимо установить приложение:
transporter_chief.rb my-app.ipa -d <идентификатор устройства>

Для того что бы скрипт запускался автоматически каждый раз когда Flash Builder компилит приложение, я написал sh-скрипт который раз в секунду проверят время последней модификации файла.ipa, и если время изменилось запускает скрипт установки приложения на устройство.
ctime="";
if [ -f mobile.ipa ]; then
 eval $(stat -s mobile.ipa)
 ctime=$st_mtime
fi

while sleep 1; do
if [ -f mobile.ipa ]; then
 eval $(stat -s mobile.ipa)
 if [ "$ctime" != "$st_mtime" ]; then
  ctime=$st_mtime
  echo "run install app"
  transporter_chief.rb -v mobile.ipa
 fi
fi
done

Ну вот и все, запускаем sh-скрипт который работает в фоновом режиме, и не обращая на него внимания работаем во Flash Builder.

PS Очень хотелось бы после установки ipa файла, научиться запускать приложение на iPhone из терминала, что бы не делать лишних телодвижений для запуска приложения во время отладки во FlashBuilder. Если кто знает как это сделать - поделитесь тайными знаниями :)

понедельник, 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.


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