ラベル AIR の投稿を表示しています。 すべての投稿を表示
ラベル AIR の投稿を表示しています。 すべての投稿を表示

2012/05/05

xib2js & TiMockをリリースしました

frogonmobileのサイトにxib2jsの新版とTiMockをリリースしましたので、ご案内すると同時に簡単なQuick startガイドをここに記したいと思います。

基本的に、frogonmobileのサイトで公開している英語版のQuick Start Guideの和訳+αみたいな感じにしたいと思います。

ここのサイトに来ていただいている方ならばxib2jsに関してはもうご存知だと思われますが、前回のエントリーで述べた通り今回はこれのバージョンアップと共にTiMockというTitanium Mobileアプリを併用することで、モックアップを簡単に作成し実機で確認できる環境を構築することができるようになったのが大きな売りとなっております。

ワークフロー的には、下図のようにXcodeでUIのコンポーネントを並べて.xibファイルを作成し、それをxib2jsでJavaScriptに変換した後にTiMockを使って実機やシミュレータ上で確認しながらコンポーネントの位置や大きななどをコードを改変して微調整するという感じになります。


1. XcodeでUIを作成
XcodeでFileメニューからNew Fileを選択し、iOSのUser Interfaceのテンプレートを選択します(ここではEmptyを選択してます)。


後は、コンポーネントを並べてUIを構築していけば良いのですが、ここでの注意点としましては、ファイルの形式を"Interface Builder3.1"を選択しておくことです。それ以外のバージョンではxib2jsが正常にコードを生成できないことを確認してます。


2. xib2jsで.xibファイルをJavaScriptに変換
UIが出来たら、毎度お馴染み.xibファイルをxib2jsにドラッグ&ドロップすると、いつもの通りにJavaScriptのコードが生成されます。


今回のバージョンから生成されるJavaScriptがCommonJSのスタイルに則っていることが確認できると思います。

3. TiMockと連携してUIのカット&トライを行う
さて、ここからが今回の売りであります、TiMockを併用したUIのカット&トライです。まず、TiMockを実機かシミュレータで起動できるようにまずはビルドから始めます。

Githubからコードをダウンロードしたら、Titanium Studioで適当な新規モバイルプロジェクトを作って(Titanium Mobile SDKは2.0を選択する)コードや画像ファイル等をコピーし、ビルドします。

特に難しい部分は無いと思いますが、実機用にビルドする時にはui/ApplicationWindow.jsファイルの31行目のコードのコメントを外してください。
//require("api/Includes");
シミュレータで実行する場合は、そのままビルドして問題ありません。

ビルドが完了しましたら、TiMockを起動してください。TiMockは起動するとBonjourで_timock._tcpサービスをサーチします。xib2jsが起動していれば、xib2jsがこのサービスを提供しますので、TiMockアプリ上に"TiMock Service"というボタンが見えていると思います。


このボタンをタップすると、xib2jsとTiMockの通信が確立されます。xib2js上のSyncボタンが有効になるのと、TiMock上のボタンもConnectedとなったことが確認できると思います。


試しに、この状態でSyncボタンをクリックすると、変換されたJavaScriptのコードがTiMock側に転送されて、UIを実機上で確認できます。


後はxib2js上で微妙なレイアウトの崩れなどを、コード上でコンポーネントの位置やサイズのプロパティ値を調整しながらカット&トライしてUIのモックを仕上げていきます。コードを編集したら、都度Syncボタンをクリックすることで実機上で修正内容が反映され確認できます。

尚、コードの編集に関しては、TiMock上で正常に動作させるには、かなり制約されたものになります。

まず、関数とexportsの定義は変更できません。また、self変数に何らかのUIコンポーネントのインスタンスを格納し、それを返り値として返す形を取る必要があります。
function ApplicationWindow() {             // この行は編集してはダメ
  var self = Ti.UI.createWindow();           // self変数にインスタンス化した
                                                              // UIを格納する
  // ここの中はわりと自由に書ける

  return self;                                          // 必ずselfを返すようにする
}                                                           // この行は編集してはダメ
module.exports = ApplicationWindow; // この行は編集してはダメ
このルールを守れば、逆に関数の中身に関してはわりと自由に書けますが、あくまでも目的はUIのモック作成なので、あまり本格的なアプリケーションロジックをここでコーディングすることはお薦めできません。

とは言え、どんなコードなら動くのかはある程度試行錯誤が必要だと思いますので、いろいろ試してみて自分なりの答えをみつけてください。

また、xib2jsは拡張子が.xib以外のファイルを受け取った場合は、そのファイルをTiMockに転送します。TiMock側は受け取ったファイルをTi.Filesystem.applicationDataDirectoryに保存しますので、コード上でパスを適切に設定することで、画像をUIにはめ込むことが可能です。


ここで指定するパスは、実際のアプリを作成する際にリソースが置かれる位置とは違うものと思われますが、画像がどのような位置・サイズで表示されるのかを確認するには便利に使えるのでは無いかと思います。

また、xib2jsにはファイルをまとめて複数ドラッグ&ドロップしても、同時には1つのファイルしか処理できませんので、お手数でも1つずつドラッグ&ドロップしてください。

4. コードの保存
さて、最後にここまで調整してきたモックのコードを保存します。Saveボタンをクリックするとファイルの保存先を選択するダイアログが表示されますので、Titianium Studio上の適切なプロジェクトのResourceフォルダーを選択してください。


尚、いつもの注意点ですが、保存の際にxib2jsはファイルの存在の有無を確認しませんので誤ってファイルを上書きしないようにお気をつけください。これ、いい加減にちゃんと対応しないといけませんね。。

ここまで来たら、後はTitanium Studioに引き継いでアプリケーションの開発を継続しましょう。

Code Strong,

2012/04/23

xib2js 2.0 preview

久々の更新ですね(^_^;

Titanium Mobile 2.0 ローンチ記念イベント in Tokyo!!が開催されましたので、参加してきました。イベントの詳細レポートはTitanium Newsをどうぞご参照ください。

イベントの中でLTの枠がありましたので、長らく放置してましたxib2jsの新しいバージョンを発表させていただきました。ちなみに、デモがメインだったのでスライドの公開予定は特にありません。

xib2jsで生成するJavascriptは当時のKitchenSinkを参考にしましたので、現在は推奨されていない所謂マルチコンテキストと呼ばれていたスタイルのコードを生成していましたが、今回のバージョンアップで、CommonJSスタイルのコードを生成するように変更しました。

まぁそれだけでは面白くないので、TiShadowを真似て変換したJavascriptを実機やシミュレーターに転送して実行できる機能を組み入れました。

TiShadowでは、HTTPのサーバーと接続するのにIPアドレスをいちいち打ち込む必要がありますが、xib2jsはMac + iOSでの利用を前提にしているので(なぜならXcodeで作成した画面レイアウトをJavascriptに変換するツールなので)、簡単に接続できるようにBonjourを使うようにしました。

後は、画面のレイアウトを作った後に画像データを貼り込むケースを想定して、.xibファイル以外をドロップした時は、端末のアプリケーションデータディレクトリーにファイルを転送して保存する機能も追加しました。

実際のアプリを作成する時は、違うディレクトリーにアセットやリソースを置かれるとは思いますが、実機上で画像の位置やサイズとかを簡単に確認・調整するには便利に使えるかなと思ってます。

正式に公開するまでには、まだじゃっかんコード整理以外にもやることがありますので、もうしばらくご辛抱くださいまし。

Code Strong,

2011/11/27

macosxでANE

AIR Native Extensionを使ってAIRが標準でサポートしない機能を使ったモバイルアプリを作ることができるようになりました。

では、デスクトップアプリはどうでしょうか?デスクトップアプリの場合は、標準でサポートされていない機能を使いたいというよりは、既にあるコードを流用したり、処理の重いルーチンをネイティブコードに置き換えて高速化したいという要望の方が強そうです。

■サンプルのビルド
まぁ、何はともあれビルドにはまた手を焼くだろうから、簡単なサンプルから始めてみるしかありません。ということで、Adobeが公開しているサンプルを試してみました。

このサンプルがなかなかよく出来てて、順を追って各フォルダーにあるgo.shを使ってビルドして行けば、サンプルのAIRアプリが出来るようになっているのですが、恐らくネイティブコードをビルドするところで、躓くことになると思います。

02 - create platform extension/mac/TestNativeExtension以下にxcodeのプロジェクトファイルがありますので、ダブルクリックしてxcodeを起動してみましょう。

多くの人は以下のようにAdobe AIR.frameworkのところが赤くなっていることでしょう。これはこのframeworkに設定されているパスが正しくないからです。


なので、コンテクストメニューから"情報を見る"を選択して、正しいパスを設定しましょう。


次に"プロジェクト"メニューから"アクティブアーキテクチャーを設定"で、i386が選択されていない場合は、これに設定してください。ANEはx86_64だとうまく動かないようです。


後は、オリジナルのソースだと"Adobe AIR/Adobe AIR.h"という謎のインクルードファイルを指定しているのですが、これを"FlashRuntimeExtensions.h"に置き換えて、ソースと同じ場所にコピーしておきます。

[TestNativeExtension.cppファイル内]
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//#include <Adobe AIR/Adobe AIR.h>
#include "FlashRuntimeExtensions.h"
これで、xcode上でビルドしても問題が出なければ、次からはコマンドライン上でgo.shを使ってビルドしてもきっとうまく行くと思います。

さて、ようやく本題に入れます。

■サンプルを実行
とりあえず、サンプルを実行してみましょう。画面上部のラジオボタンで、ASのコードを使うかネイティブコードを使うか選択できるようになっているのですが、注目してもらいたいのは、画面下部にあるソートの機能です。

試しに、1万件くらいの配列を指定してASとネイティブコードの実行時間を比較して見ると、あれれ、ASの方が速いですね?


10万件にしても、100万件にしても同じです。やはりASの方が速いですね。以下は私の環境で実行した時の値です。
件数 ASコード Nativeコード
10000 0.003sec 0.005sec
100000 0.029sec 0.045sec
1000000 0.352sec 0.448sec

何ででしょうかね?と悩むより前に、ソースコード上のコメントにも「ネイティブコードはCの配列を作るオーバーヘッドがあるのでソートはASより遅いよ」と書いてあります(笑

ってことで、TestNativeExtension.cppのFREObject _Advanced_timeLongOp関数内で、該当のデータをやり取りしている部分を見てみると、
// Copy input array to sort internally
for ( uint32_t i = 0; i < arrayLength; i++ ) {
    result = FREGetArrayElementAt( argv[0], i, &obj );
    if ( result != FRE_OK ) {
        return (FREObject)NULL;
    }

    result = FREGetObjectAsInt32( obj, &largeArray[ i ]);
    if ( result != FRE_OK ) {
        return (FREObject)NULL;
    }
}
Array配列の要素ひとつひとつに対して、Int32型のオブジェクトを生成して確保したメモリー領域に格納しています。 

以下は、ソートした後のデータを元のArray配列に戻すコードですが、基本的にさっきと逆のことをしてます。
// Copy sorted result back
for ( uint32_t i = 0; i < arrayLength; i++ ) {
    result = FRENewObjectFromInt32( largeArray[ i ], &obj );
    if ( result != FRE_OK ) {
        return (FREObject)NULL;
    }

    result = FRESetArrayElementAt( argv[0], i, obj );
    if ( result != FRE_OK ) {
        return (FREObject)NULL;
    }   
}
なるほど、確かにこれはなかなかオーバーヘッドが大きそうなコードです。

このように、異なるプログラミングモデルを用いる場合に(Alchemyなんかもそうなのですが)、レイヤー間を跨ぐデータのやり取りのオーバーヘッドが大きくて、実際は速くならないというようなことがしばしば起こるわけです。

せっかく速くする為にネイティブコードを使いたいのに、返って遅くなるのでは、ANEって全然使えねーじゃん!となるところですが、今回の場合はデータ構造を少し工夫すればオーバーヘッドを軽減できそうです。

具体的には、Arrayでちまちまデータを渡すのでは無くて、ByteArrayにソートするデータをつっこんで、データ列をネイティブコード側で直接ソートできるようにデータを準備します。

まず、AS側のコードですが、
// データを格納するByteArrayを宣言
var byteData:ByteArray = new ByteArray();

// ソートするデータを格納するコード
for ( var i:int = 0 ; i < arraySz ; i++ ) {
    byteData.writeByte(i & 0xff);
    byteData.writeByte((i >> 8) & 0xff);
    byteData.writeByte((i >> 16) & 0xff);
    byteData.writeByte((i >> 24) & 0xff);
}
byteData.position = 0;

// ソート後のデータを取得するコード
var data:int = ((byteData.readByte() & 0xff)) | ((byteData.readByte() & 0xff) << 8) | ((byteData.readByte() & 0xff) << 16) | ((byteData.readByte() & 0xff) << 24);
ネイティブコード側の手間を省く為に、データをリトルエンディアンに並べておくのがコツです。ま、本来はエンディアンに依存しないコードにすべきでしょうけど。

AVM2もリトルエンディアンだと思ったのですが、ByteArray.write/readInt()ではうまくソート出来ませんでした。何か勘違いしてるかな?

では次にネイティブコード側です。
FREObject _Advanced_timeLongOp(FREContext ctx, void* functionData, uint32_t argc, FREObject argv[]) {
    // Byte列を格納する変数
    FREByteArray bytedata; 

    // ByteArrayクラスオブジェクトのバイトを取得
    FREResult result = FREAcquireByteArray(argv[0], &bytedata);
    if ( result != FRE_OK) {
        return (FREObject)NULL;
    }

    qsort(&bytedata.bytes[0], bytedata.length/4, sizeof(int32_t), cmpFcn);

    // ByteArrayクラスオブジェクトを解放する
    FREReleaseByteArray(argv[0]);

    return (FREObject)NULL;
}
え、こんだけ?えぇ、こんだけです。

qsortで直接ソートできる形式でデータ列を格納してあるので、余計なメモリー確保もデータコピーも不要になりました。これらのオーバーヘッドを軽減したことにより、ソートサンプルは以下のようにネイテイブコード側の方がASに比べてかなり速く処理できるようになりました。
件数 ASコード Nativeコード
最適化前
Nativeコード
最適化後
10000 0.003sec 0.005sec 0.001sec
100000 0.029sec 0.045sec 0.009sec
1000000 0.352sec 0.448sec 0.079sec

■あとがき
まぁ、今回のように極端に最適化できる例はあまり無いかも知れませんが、基本的にデータのやり取りのオーバーヘッドは大きいので、データ量が多い時は、ByteArrayにデータをつっこんで受け渡しするってのが、常套手段になるような気がします。

[Updated]
ソースコードのライセンスがよくわからないので、patchファイルだけane-labにアップして置きました。

2011/11/18

コマンドラインでANE

随分と久しぶりの更新になってしまいました。タイの洪水の影響とかいろいろあって本業の方が忙しく、まとまった時間を取ることが出来なかったのでした。ようやく落ち着いたので、いろいろ気になってたことをまたぼちぼち試して行きたいと思います。

さて、今回はANEことAIR Native Extensionを取り上げたいと思います。と言っても既に世の中には沢山の事例が出てますので、何を今更という感じでしょうか?

ただ、世の中にあるほとんどのサンプルがFlash Builder4.5.1を持っていることを前提にしてますよね?いやいや、ちょっと待て、俺は持ってねーぞ、という方も中には居らっしゃることでしょう。

そこで、Flex SDKとAIR3.0 SDKとAndroid SDK(Eclipse)でAIR for Android向けにANEを使ったアプリをコマンドラインでビルドする方法を今回は解説します。

その前にANEって何?って人はAKABANAのAIR 3 Native Extension Seriesをひと通り事前に読んで内容を理解しておいてください。

今回は、ExtensionContextやAIR for Androidを拡張するJavaクラスの書き方等は特に解説しません。あくまでも、それのビルド方法にフォーカスして説明します。

ってことで、具体的なビルドの手順は以下のような感じで進めて行きます。

  • ExtensionContextのビルド
  • JARファイルの作成
  • ANEファイルの作成
  • AIRアプリのビルド
  • Android apkファイルの作成

0. AneLab-HelloWorldの入手
説明を進めるのに、題材としてane-labにあるhelloworldを使いたいと思いますので、入手してください。
$ svn checkout http://ane-lab.googlecode.com/svn/trunk/mobile/android/java/helloworld helloworld
以降、コードをcheckoutしたフォルダーを[HELLOWORLD_HOME]と記載します。

1. ExtensionContextのビルド
ExtensionContextというのは、AIRからネイティブライブラリーの中にある関数を呼び出すためのクラスですね。こいつはcompcを使ってSWCにします。ソースは、[HELLOWORLD_HOME]/AneLab-HelloWord-ane/src以下にあります。
$ cd [HELLOWORLD_HOME]/AneLab-HelloWord-ane
$ compc -source-path ./src -include-classes com.example.ane.android.helloworld.HelloWorldExtension -external-library-path [AIR3SDK_HOME]/frameworks/libs/air/airglobal.swc -output ./bin/AneLab-HelloWorld-ane.swc

2. JARファイルの作成
AIR for Androidを拡張するJavaクラスをJARファイルにまとめます。ここだけはコマンドラインでやるのでは無くて、Eclipseの機能を使います。

プロジェクトファイルは[HELLOWORLD_HOME]AneLab-HelloWorld-Java以下にあるので、これをEclipseに既存のプロジェクトとして取り込んでください。

JARファイル化はとても簡単で、まずJARファイルにするプロジェクトを選択したら、コンテクストメニューからExport...を選択します。


すると表示されるダイアログでJAVAフォルダ内のJAR fileを選択し、Next>ボタンを選択して次に進みます。


そしたら、書き出し先を選択(ここでは、[HELLOWORLD_HOME]/AneLab-HelloWorld-ane/platform/android)して、Finishボタンを選択して終了です。


3. ANEファイルの作成
ここまで進んだら、いよいよaneファイルの作成と進みますが、その前に、先程作ったSWCファイルをunzipしたら得られるlibrary.swfを[HELLOWORLD_HOME]/AneLab-HelloWorld-ane/platform/androidに置いておきます。
$ cd [HELLOWORLD_HOME]/AneLab-HelloWorld-ane/bin
$ unzip AneLab-HelloWorld-ane.swc
$ mv library.swc ../platform/android
そしたら、AIR 3 SDKのadtコマンドを使ってANEファイルにパッケージします。
$ cd [HELLOWORLD_HOME]/AneLab-HelloWorld-ane
$ adt -package -storetype pkcs12 -keystore test.p12 -target ane AneLab-HelloWorld-ane.ane extension.xml -swc ./bin/AneLab-HelloWorld-ane.swc -platform Android-ARM -C ./platform/android .
今回のサンプルで使用する認証ファイル(test.p12)に設定されているパスワードに関しては、[HELLOWORLD_HOME]/AneLab-HelloWorld-ane/readme.txtの中身をご参照ください。

4. AIRアプリのビルド
AIRアプリのビルドは通常通りamxmlcコマンドを使えば良いのですが、通常と違うのは外部ライブラリーとして、先程作成したANEファイルを指定することです。AIRアプリは[HELLOWORLD_HOME]/AneLab-HelloWorld-app以下にありますが、コンパイル前に先程作成したaneファイルをlibsフォルダーにコピーしておきます。
$ cd [HELLOWORLD_HOME]/AneLab-HelloWorld-app
$ cp ../AneLab-HelloWorld-ane/AneLab-HelloWorld-ane.ane ./libs
$ cd src
$ amxmlc +configname=airmobile -compiler.external-library-path+=../libs/AneLab-HelloWorld-ane.ane AneLabHelloWorld.mxml
+configname=airmobileを指定するのを結構忘れがちなので、ご注意ください。

5. Android apkファイルの作成
ここまで来れば、最後にadtコマンドを使ってAndroidのapkファイルにパッケージするだけです。事前に作成したアプリのSWFファイルとAIRのアプリケーション記述ファイル(AneLabHelloWorld-app.xml)を[HELLOWORLD_HOME]AneLab-HelloWorld-appにコピーして置きます。

また、コマンドラインでコンパイルする関係上、AneLabHelloWorld-app.xmlの49行目の<content>タグにアプリのswfファイルを指定しておかないとエラーになりますので、編集しておいてください。
$ cd [HELLOWORLD_HOME]/AneLab-HelloWorld-app
$ cp src/AneLabHelloWorld-app.xml src/AneLabHelloWorld.swf .
$ adt -package -target apk -storetype pkcs12 -keystore test.p12 AneLabHelloWorld.apk AneLabHelloWorld-app.xml AneLabHelloWorld.swf -extdir ./libs
普段と違うのは、-extdirにaneファイルを置いてあるフォルダーを指定するくらいですね。

これで無事にapkファイルが作成されたと思いますので、インストールしてちゃんと外部拡張が機能するか確かめてみてください。

■あとがき
今回は既存のファイルを使ったので特にトラブル無く進んだと思いますが、自分で新規に作成する場合は、AIR extension descriptor file内に記述するクラス名を間違えないように注意してください。

ここのクラス名を間違ったばかりに、私は数週間悩みました。ここのクラス名が間違ってても実行時には何もエラーが出ません。なので、何が原因でネイティブコードが呼ばれないのか、非常に悩むことになります。

SWCの作り方が悪いのか?JARの作り方が悪いのか?あるいは、ANEなのか、と。ここに解説してある手順でapkを作成したのに実行時に動作しない場合は、ここを一度疑ってみてください。

あと、いちいち上記手順を手で打ち込んで実行するのは手間なので、itozさんのblogを参考に自動化するスクリプトをantか何かで作った方がいいでしょう。

では、良いaneライフを、Good luck!

2011/05/14

AIRとTitanium MobileがBonjour

モバイル端末上のアプリケーションデータを、PC上の別アプリケーションに送りたいような場合に、HTTPサーバーを立ててやる方法もあるにはあるんですが、事前にIPアドレスを知っていないといけないとか、もひとつエレガントさに欠けると思うんですよ。

で、PC側がMacintoshでモバイル側がiPhoneならば、Bonjour(リンク)を使ったらネットワークの面倒な設定をしなくても簡単にやり取りを出来るんじゃないかと思い、試してみました。

iPhone側はTitanium MobileがBonjourをサポートしているので、これを利用すれば良さそうです。今回は、Kitchen SinkにBonjourを使ったサンプルがあるので、せっかくだから、これをそのまま使用します。

Mac側はAIRでアプリを作ることに決めているんですが、残念ながらAIRではBonjourをサポートしておりません。仕方ないので、NativeProcessで外部アプリを利用しましょう。

1. dns-sdコマンドラインツール
 Macにはdns-sdというコマンドラインツールがあって、これでBonjourサービスの検索や登録ができます。このツールを使って、接続したい機器のアドレスとポートの情報を取得するまでの手順を確認しましょう。

まず事前に、Kitchen SinkのPlatformタブにBonjourってのがあるので、これを選択しておきます。これは、Bonjourのサービスを起動すると共に、そのサービスとのやり取りを兼ねたサンプルとなっております。

ソースコード(リンク)を見てみると、サービスタイプが _utest._tcp になっているので、以下のコマンドを実行すると、このサービスタイプを提供している機器のインスタンス名を取得できます。
$ dns-sd -B _utest._tcp
インスタンス名が取得できれば、以下のコマンドでアドレスとポートを知ることができます。
$ dns-sd -L <インスタンス名> _utest._tcp
ここまでわかれば、後はAIRで、このアドレスとポートに接続するSocketを作成してあげれば、それを通じてやり取りが出来るようになります。

実際に上記コマンドを実行して、出力されるログを確認してみてください。

2. AIRアプリの作成
AIRで外部のプログラムを起動するには、NativeProcessを使用します。以下のようにして、まずインスタンス名を取得するコマンドを実行します。
var process:NativeProcess = new NativeProcess();

var args:Vector.<string> = new Vector.<string>();
args.push("-B");
args.push("_utest._tcp"); // 今回のサンプルのサービスタイプ

var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
info.executable = new File("/usr/bin/dns-sd");
info.arguments = args;

process.start(info);
process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,
    onBrowseService);
コマンドの実行結果は標準出力に出ますので、ハンドラー関数の中で以下のようにして、結果を取得し、インスタンス名に当たる部分を抜き出します。
private function onBrowseService(event:ProgressEvent):void{
  var stdo:IDataInput = process.standardOutput;
  var result:Array = stdo.readUTFBytes(stdo.bytesAvailable).split('\n');
  var column:String = String(result[result.length - 3]);
  var index:int = column.search("Instance Name");
  if (index != -1) {
    var instanceName = result[result.length - 2];
    instanceName = instanceName.slice(index); // インスタンス名
  }
}
ここでは、直前の行の"Instance Name"文字列の位置を手がかりにインスタンス名を抜き出してます。また、同名のサービスタイプを提供する機器が複数存在する場合を考慮してません。

インスタンス名がわかれば、以下でアドレスとポートの情報を取得します。
var process = new NativeProcess();

var args:Vector.<string> = new Vector.<string>();
args.push("-L");
args.push(インスタンス名);
args.push("_utest._tcp");
    
var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
info.executable = new File("/usr/bin/dns-sd");
info.arguments = args;
process.start(info);
process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,
    onResolveService);
同様にコマンドの実行結果は標準出力に出ますので、ハンドラー関数の中で以下のようにして、結果を取得し、アドレスとポートに当たる部分を抜き出します。
private function onResolveService(event:ProgressEvent):void {
  var stdo:IDataInput = process.standardOutput;
  var result:Array = stdo.readUTFBytes(stdo.bytesAvailable)
                                      .split("can be reached at ");
  if (result.length == 2) {
    process.removeEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,
        onResolveService); 
    result = result[1].split(" (");
    var iNamePort:Array = String(result[0]).split(":");
    var address:String = iNamePort[0];     // アドレス
    var port:int = parseInt(iNamePort[1]); // ポート
  }
}
アドレスとポートがわかれば、それとやり取りする為のSocketを作成してあげます。
var socket = new Socket();
socket.connect(address, port);
socket.addEventListener(Event.CONNECT, onConnect);
無事に接続すると、ハンドラー関数が呼ばれます。Kitchen Sinkのサンプルでは、ターゲットに"req"という文字列を送ると、機器情報を返信してきますので、これを受け取れるように受信ハンドラーを設定しておきます。
private function onConnect(e:Event):void {
  socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
  socket.writeUTFBytes("req"); // ターゲットにデータ送信
}

// ターゲットからデータを受信
private function onSocketData(e:ProgressEvent):void {
  var reply:String = socket.readUTFBytes(socket.bytesAvailable);
}
ご参考までに、以下に実際に動作するAIRアプリのソースコードを置いておきます。
http://homepage.mac.com/daoki2/lab/Bonjour/BonjourTest.tar.gz (4KB)

以上のようにすれば、Bonjourを介して、MacとiPhoneで簡単にデータのやり取りを行うアプリを作ることが出来そうです。ただ、Mac以外やAndroidでは、どうすれば良いのでしょうか?

実は、ここ(リンク)にBonjourのJava実装のソースコードがあるので、これを使えばもしかしたらその他のプラットフォームでも同じようなことが出来るかも知れません。

もしうまく動かせたら、是非教えてください (^_^;

2011/04/03

AIR for iOSやってみた

AIR2.6 SDKからiOS用のパッケージを作成できるようになりました。ただ、所有しているiPhoneが3Gの為にiOS3.xからアップデートを行っておらず(AIR2.6 SDKで作成できるのはiOS4.x以降のパッケージ)、いままで試す機会がございませんでした。

今回、iPod 4thを手に入れましたので、早速以前にAndroid用に作成したアプリをiOS向けに書きだして、どんなもんか確かめてみました。尚、AIR2.6 SDKの詳細に関しては上条さんのblogに詳しいので、そちらをご参照いただければと思います(リンク)。

1. 開発証明書ファイルとプロビジョニングファイル
iOS用AIRアプリケーションの作成手順に関しては、ここのドキュメント(リンク)が参考になりますので、目を通しておいてください。

iOS用にipaインストーラーファイルを作成するには、Android同様にadtコマンドを使用しますが、その際に、P12ファイル形式のApple開発用証明書ファイルとApple開発用プロビジョニングファイルが必要になります。

既に、iOS用にアプリ開発する環境がセットアップされていれば、開発証明書ファイルはキーチェーンアクセスから書きだすことが出来ます。また、プロビジョニングファイルは、iOS Provisioning Portalからダウンロードできます。

具体的なadtコマンドは以下のようになります。
adt -package -target ipa-debug 
    -keystore iosPrivateKey.p12 -storetype pkcs12 -storepass qwerty12 
    -provisioning-profile ios.mobileprovision 
    HelloWorld.ipa 
    HelloWorld-app.xml 
    HelloWorld.swf icons Default.png

2. アプリケーション記述ファイル
iOS用のアプリケーション記述ファイルで指定できる内容に関しては、ここのドキュメントをご参照ください(リンク)。Androidとは指定する内容や、記述方法がだいぶん違うことにはじめは戸惑いますよね。

まぁ、何はともあれ、これでipaファイルを書きだせば、後はそれをダブルクリックして、iTunes経由で実機に転送というPackager for iPhoneと同様の流れになります。ちなみに、ipaファイルの書きだし時間はPackager for iPhoneと比べて、だいぶん短縮されてます。

で、動かしてみたのですが・・・「お、遅い」。えぇ、Android(NexusOne)と比べて明らかに遅いのです。まぁ、全く同じハードでは無いので、厳密な比較はできないのですが、iPhone4相当のiPodとNexusOneで、そんなにハードウェアの処理速度に違いがあるとは思えません。

比較したアプリが、Bitmapの描画とフィルターを酷使するもの(Particle Break)だったので、恐らく高解像度なRetinaディスプレイが不利に働いているものと思われます。

また、iOSではNativeApplication.nativeApplication.exit()が効かないらしく、アプリ側からアプリを終了させることが出来ません。ホームボタンを押して、アプリがバックグラウンドにまわった時には、一応実行は止まります(アプリを選択することで、動作が再開します)が、ちょいと不便ですわね。

まだ触りはじめたばかりなので、何か勘違いとかしているかも知れませんが、個人的にはややがっかり・・・でも、まだ出たばかりだし、Androidもはじめは遅かったから、今後ランタイムがチューニングされて行くことに期待しましょう。

2011/03/21

StageWebViewでGoogle OAuth2.0認証

Google APIを使用してユーザー固有のデータにアクセスする際に、ユーザー認証が必要となります。StageWebViewを使用すると、簡単にGoogleのOAuth2.0認証を行えますので、その方法を紹介します。

前準備として、Google APIを使用するアプリケーションの登録と、認証時のリダイレクト先を用意しておく必要があります。

1. Google APIを使用するアプリの登録
ここの手順(リンク)に従って、Google APIを使用するアプリを登録します。

2. リダイレクト先の用意
OAuth2.0認証を行う際に必要となる、リダイレクト先を用意します。実際にアクセスできるURIならどこでも構わないと思います。

3. OAuth2.0認証を行うAIRアプリの作成
では、OAuth2.0認証を行うStageWebViewを用いたAIRアプリを作成します。

まず、OAuth2.0認証で使用する定数の定義。
private const OAUTH_URL:String =
  "https://accounts.google.com/o/oauth2/auth?response_type=token";
private const CLIENT_ID:String = "xxxx.apps.googleusercontent.com"; 
private const REDIRECT_URI:String = "http://your.redirect.uri";
private const SCOPE:String = "https://www.google.com/m8/feeds/";
OAUTH_URLOAuth2.0認証を行うURL
CLIENT_ID1.で設定したGoogle APIを使用するアプリのID
REDIRECT_URI1.で設定したリダイレクト先のURI
SCOPEOAuth2.0認証を使ってアクセスするGoogleのサービス

次にStageWebViewを作成してOAuth2.0認証を行なうコードです。
var web:StageWebView = new StageWebView();
web.stage = this.stage;
web.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
web.loadURL(OAUTH_URL + "&scope=" + SCOPE
  + "&redirect_uri=" + REDIRECT_URI
  + "&client_id=" + CLIENT_ID);
web.addEventListener(LocationChangeEvent.LOCATION_CHANGE,
  web_LocationChangeHandler);
StageWebViewはDisplayObjectよりも前面のレイヤーに表示されますが、今回はアプリケーションの全面に表示されるように設定しています。

web.loadURL()で、OAuth2.0認証を行うURLを指定しています。

最後にweb.addEventListener()でLocationChange.LOCATION_CHANGEイベントのイベントハンドラーを設定していまので、GoogleのOAuth2.0認証を行った後に、指定のリダイレクト先に飛ぶと、このイベントハンドラーが呼ばれることになります。

では、イベントハンドラーのコードを見てみましょう。
private function web_LocationChangeHandler(e:LocationChangeEvent):void {
  var url:Array = web.location.split("#");

  // もしリダイレクト先が自分の指定したURLだったら
  // OAUTHトークンをデコードする
  if (url[0] == REDIRECT_URI) {
    var params:Array = String(url[1]).split("&");
    for each (var param:String in params) {
      var vars:Array = param.split("=");
      switch(vars[0]) {
      case "access_token":
        OAUTH_TOKEN = vars[1];
        break;
      default:
        // do nothing
        break;
      }
    }
    web.removeEventListener(LocationChangeEvent.LOCATION_CHANGE,
      web_LocationChangeHandler);
    web.stage = null;
    web.dispose();
  }
}
web.locationで飛んだ先のURLを取得します。ちゃんとOAuth2.0認証に成功して飛んで来た場合には、

http://your.redirect.uri#access_token=$$$$&expires_in=$$$$

という文字列が取得できるはずですので、飛び先のURLが自分が指定したURLの場合は、以降のパラメータをデコードして、OAuthのトークンを取得します。

最後に、OAuthトークンを取得する為に使用したStageWebViewは不要となりましたので、 後始末を行います。

以降、Google APIでユーザー認証が必要となるデータにアクセスする際には、ここで指定したOAuthトークンを使用します。

2011/02/05

De MonsterDebuggerを使ったデバッグ

第1回AIR for Android勉強会で、自分のお薦めの開発スタイルとして、なるべくデスクトップ上で開発するという話をさせていただきました。

AIRの利点はマルチプラットフォーム環境ということです。マルチプラットフォームには2つの意味があります。1つは、MacやLinux、WinなどのOSを超えるという意味でのマルチプラットフォーム。

もうひとつは、デスクトップやモバイル、TVなどの機器を超えるという意味でのマルチプラットフォーム。要するに、AIR for Androidだろうが、所詮AIRアプリなので、それなら開発効率を最優先して、画面の小さなデスクトップアプリとしてあらかた開発してしまい、デバイス固有な部分やパフォーマンスなどの合わせこみは、大体アプリが動き始めてから行えば良いではないか?という発想です。

そういう開発スタイルをとった場合に、デバッグ用のツールとして重宝したのがDe MonsterDebuggerです。

1. インストール/実行方法
De MonsterDebuggerのホームページからAIRファイルをダウンロードして、インストールします。

De MonsterDebuggerを起動すると、Fileメニューに"Export Client Class"というアイテムがあるので、これを選択し、De MonterDebuggerと接続する為のクライアントクラスを自分のプロジェクトディレクトリーを指定して作成します。


最後に、自分のアプリケーション内でMonsterDebuggerのインスタンスを作成しておけば、アプリケーションを起動した際にDe MonsterDebugerと接続します。
import nl.demonsters.debugger.MonsterDebugger;
var debugger:MonsterDebugger = new MonsterDebugger(this);


2. 便利な使い方
De MonsterDebuggerを使う利点は、簡単にログをトレースしたりメモリーリークのチェックができたりすることもあるのですが、個人的にはLive editing機能によってDe MonsterDebugger側から実行時にオブジェクトのプロパティを編集できるのが非常に便利だと感じました。


表示オブジェクトのちょっとした座標やサイズの調整に、コンパイル⇒実行というサイクルを繰り返さないでも済むのは、ありがたいです。

3. 注意点
De MonsterDebuggerをAIR for Androidアプリのデバッグに使う際に、1つだけ注意が必要です。

それは、アプリケーション記述ファイルのsupportedProfilesタグ(下記)を無効、もしくは"desktop"等にしないといけません。
<supportedProfiles>mobileDevice</supportedProfiles>
これは、AIRアプリとDe MonsterDebuggerはLocalConnectionで接続されるのですが、この機能はmobileDeviceプロファイルではサポートされていないからです。

4. ハードウェアキーの割り当て
同様に、adlはmobileDeviceプロファイルが指定されていると、メニューからAndroidのBack/Menu/Searchキーをエミュレーションできるのですが、これが使えなくなりますので、別途Back/Menu/Searchの機能を矢印キー等に割り当てておくこともお薦めします。
private function keyHandler(e:KeyboardEvent):void {
    switch(e.keyCode) {
    case Keyboard.MENU:
    case Keyboard.DOWN:
        //Menu選択時の処理
        break;
    case Keyboard.LEFT:
    case Keyboard.BACK:
        //Back選択時の処理
        break;
    }
}

AIR for Android勉強会の発表資料

第1回AIR for Android勉強会で「Flashコンテンツの移植ノウハウ」というお題で発表させていただきましたので、その資料をここに晒しておきます。

AIR for Android勉強会 Flashコンテンツの移植ノウハウ

De MonsterDebuggerをAIRアプリケーションのデバッグに使う為の改造方法も近日中に公開しますので、お楽しみに。


[追記]改造しなくてもちゃんと動きました(汗 何か勘違いしてたようです。資料の方もアップデートしました。