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/19

b-mobile Fairレポートその2

昨日、b-mobile Fairの簡単なレポートを報告しましたが、自分の普段のスマフォの使い方を詳しく書かなかったので、あまり参考にならなかったと思います。

今日、平日の典型的なパターン通りに使ってみて、データ通信量がどの程度だったか、確かめてみました。以下、普段の利用パターンです。

・まず、自宅では事情が無い限りは機内モードに設定し、Wi-Fiを有効にしてある
・家を出る時にWi-Fiと機内モードを無効に設定(つまり3Gを有効にする)
・通勤電車内で、twitterとwebサーフィンを少々
・帰宅したら、また機内モードに設定

これで、今日確かめてみたら3MBしか消費してませんでした(笑)。
結局のところ、自分は通勤電車内(往復約1時間)で少しネットをやるくらいなので、ほとんどデータの通信は発生しないみたいですね。もしかして、スマフォ要らん?

後は、稀に休日に出先で調べ物をしたりとか、今後、勉強会でノートPCのテザリングに使った時にどれだけ消費するか?何だけど、目安の250MB/月を大きく超えることはないような気がしてます。詳細は、その機会があった時にまた調べてみようと思います。

あと、スピードに関してですが、AndroidのSpeedTestがようやく東京のサーバーを捕捉してくれたので、比較してみました。

Download/Uploadスピード
NexusOne 2.78/0.38Mbps 
iPhone3G 1.21/0.22Mbps 

計測する場所や、その時のネットワークの状況にもかなり左右されるとは思いますが、自分の体感的にもNexusOneの方が明らかに速く感じるので、やはりドコモ回線はそれなりのスピードが出るようですね。

が、まぁご参考程度にお考えください。

2011/04/18

b-mobile Fair購入レポート

b-mobile Fairを購入して、手持ちのNexus Oneに挿して使ってみました。

実はau EVOのWiMAX&テザリングに魅力を感じてたので、XOOMとのセット販売がされれば購入しようかとも考えてたのですが、セット販売はWi-Fi WALKERのみというナゾ仕様なので、当面手持ちのNexus Oneを活用して、夏モデル以降に期待という戦略に切り替えました。

だって、EVOって海外だと1年前のモデルでっせ?それが日本では最新の携帯として売られるこの悲しさ・・・

それに、WiMAXより3Gの方が受信が安定して速度も出るケースが多そう?なので、自分の使い方とデータ通信量しだいでは、今後もb-mobile Fair + SIMフリー端末で行く可能性もあるかな。

ちなみに、今日、普段の使い方に近いイメージで使ってみて、大体6MBくらいを消費。これだと、250MB/月を下回るので、4ヶ月持つ計算に一応はなるね。まぁ日によってだいぶんと前後するんだろうけど、2500円/月で通信費を運用できれば、かなりお得だ。

で、気になる速度をSpeedTest使って計測してみたけど、Android版がなぜか何回やっても東京のサーバーを捕捉してくれないので、参考程度に海外のサーバーで比較すると、概ねソフトバンク回線の1.5倍ほどスピードが出ている。上りは3倍くらい出ることも珍しくはない感じ。

ここら辺はさすがはドコモ回線というところか?でも端末のCPUの速度も結構違うので、その影響も多少あるのやも知れん。

と言うことで、すっかり古くなってしまったiPhone 3Gは塩漬けにして、しばらくAndroidを常用してみようと考えているんだけど、Nexus Oneさんのバッテリー消費が結構激しいのねん・・・

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/04/02

てら子14 GoogleAPI

ちょっと遅くなりましたが、東京てら子14 GoogleAPIで発表した内容を公開します。

当日は時間が足りなくて、かなり駆け足でほとんど何も説明の無い状態でしたので、じっくり資料をご参照いただき、ご不明な点がございましたら、twitterにでも質問を投げてください。

・東京てら子14発表資料 Google URL短縮(リンク)

また、ソースコードはSpark Projectの方にアップしておきました(リンク)。
 
コミットしたの1年振りくらいかな(汗

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/20

Xib2JsでTitaniumアプリ作成入門

前回のエントリーで、Titaniumでアプリ開発をする際の面倒な点として、GUIのRADツールがまだ無いので、拙作のxib2jsというツールを紹介させていただきました。

今回は、もう一歩進んで、xib2jsで具体的にアプリを作成する手順をご紹介させていただこうかと思います。

1. Titaniumアプリの新規プロジェクトを作成
まず、Titanium Developerで新規プロジェクトを作成してください。


2. アプリケーションの画面を設計
次にInterfaceBuilderでアプリの画面を作成します。InterfaceBuilderを立ち上げてiPhoneアプリの作成を選択します(iPadアプリの場合はiPadを選んでください)。


次にTab Bar Controllerを画面構成ウィンドウにドラッグ&ドロップして作成します。


新規作成したTab Bar Controllerにインスペクターウィンドウから名称を設定します。名前は何でも構いませんが、スペース等を入れると後のJavaScriptのコンパイルでエラーが出ますので、ご注意ください。


 それぞれのTabにUIViewを配置しまして、以降は通常のやり方でUIComponentを画面上にドラッグ&ドロップで並べて行きます。



今回は、あまり見栄えはしませんが、Titanium Developerで作成されるデフォルトのアプリケーションと同じようなものにしてみましたが、適宜、いろんなUIComponentを使って画面を作成してみてください。ひと通りのもの使えるようにしてあるつもりです。



使用するUIComponentに名称が設定されてあると、後にxib2jsがJavaScriptのコードに変換する際に、その名称を使用しますので、適宜名称を設定しておきます。名称が未設定の場合はxib2jsの方で、コンポーネントの種類と通し番号で適当な名称に変換します。


[注意]
xib2jsはTab Bar Itemに設定されているラベルの名称でTitanium.UI.Tabを作成するコードを生成します。InterfaceBuilderはデフォルトで"Item 1"のようにスペースの入ったラベルを作成するので、このままでは後ほど生成されたJavaScriptをコンパイルする際に、エラーとなってしまいます。これを避ける為、ここではラベルを"Item1"のようにスペースを削除してます。

画面設計をひと通り終えたら、ファイルを保存しておきます。

3. JavaScriptコードへの変換

InterfaceBuilderで作成したファイルをJavaScriptへ変換するのにxib2jsを使用します。ここから(link)アプリをダウンロードしてください。zipで圧縮してあるので解凍してできた.airファイルをダブルクリックしてインストールします。

xib2jsアプリが起動したら、先程InterfaceBuilderで作成した.xibファイルをアプリにドラッグ&ドロップすると、JavaScriptに変換されます。



画面左上の[Save]ボタンを押して、変換されたコードを保管するディレクトリーを選択します。ここでは、最初に作成したTitianiumアプリのResourceフォルダー以下を指定します。

[注意]
xib2jsはファイルを保存する際に、同名のファイルが存在しても無断で上書きしますので、必要なファイルを誤って上書きしないようにご注意ください

また、xib2js上で作成されたコードは選択してコピー出来ますので、必要な部分のみを選択して、自分のコードにペーストして使用するという方法もあるかと思います。

4.コンパイル&実行

作成されたコードをTitanium Developer上のRun EmulatorでiOSシミュレータを起動します。JavaScriptのコンパイルに成功すると、シミュレータが立ち上がって先程作成した画面がアプリに反映されていることが確認できると思います。


ここで、Tabの順序がInterfaceBuilderとは逆になっているのは、ご愛嬌(笑)。JavaScriptを編集して、Tabの設定順序を変更してください。

このようにxib2jsは、まだInterfaceBuilderで作成した画面を100%完璧に再現するJavaScriptのコードに変換できるわけではありませんので、変換に失敗した場合は寛大な気持ちでコードを適宜修正して使用してくださいまし。

Code Strong,