2011/11/28

Galaxy Nexus購入

twitterでちょくちょくつぶやいているように、Galaxy Nexusを購入しました。所有しているNexus OneはICSが公式にはサポートされないと言われているし、INFOBARもあそこまで作り込んでいる関係上、簡単にはアップデート提供されないだろうから、まぁ開発用端末に買ってもいいかなと。

■First impression
で、第一印象はとにかくデカイなと・・・これ、とても持ち歩く気になれないですね。いや持ち歩きますけども(笑 Nexus Oneはいまとなっては小さい端末ですが、それにしてもって感じ。


解像度が高いこともあってのこのサイズなのでしょうが、個人的にはINFOBARくらいがベストかなぁ。まぁ、大きいことは大きいけど、薄いのと軽いこともあって持った感じはそんなに悪くないです。

画面解像度は高いけど、ペンタイルということで気にされる方も多いとは思いますが、私個人にはあまり気にならないレベルでした。有機ELは色の出方が強い気がするので、見栄えはしますね。タッチパネルの精度は問題ないと思われます。

少し気になったのは、自動調整では画面が少し暗いことかなぁ?バッテリーをケチる設定にでもなっているのかしら?

あと今回、UK版を輸入したのですが、b-mobile FairのSIMは使えました。でも、電波強度のアンテナがゼロなんですよね。なので何かおかしいのかも知れませんが、とりあえず通信は無事にできてます。

■Ice Cream Sandwich
ICSは、その前にXOOMでHoneycombを触っているので、大体想像の範囲。それもあってか特に違和感なく使えてますけど、やはりデフォルトでメニューキーが無くなったのはいただけない。

実際には、アプリがサポートしている場合はメニューキーは表示されますが、アプリによって画面の上に出たり、下に出たりで一貫性が無い上に、選択項目もリスト形式で表示されるようになったので、携帯のUI的には改悪だと思ってます。

まぁ、タブレットとアプリを共通にする為の措置だとは思いますが、もうちょっと何かうまい具合に解決できないものだろうか?正直マルチタスクボタンを優先する意味はあまり無いと思います。(まぁ、iOSのホームボタンダブルクリックもひどいUIだと思いますが・・・)

■Performance
それでは、気になるパフォーマンスはどの程度でしょうか?Galaxy NexusのMPUはOMAP4460で、これはCPUこそ1.2GHzのDual Coreですが、GPUはPowerVR SGX540とiPhone4相当。画面解像度が1280x720なので、これはちょいと不安が残ります。

まず始めに、Particle Breakを試してみました。ICSにはまだFlash Playerが来てないので、ブラウザでFlashは見れませんが、AIRのランタイムは問題無いようです。動かしてみると、速い速い!画面が大きいので、必然的に指の移動量も多くなることもあって、かなり難易度が上がってしまいました。

Particle Breakは画面をCPUモードでレンダリングしているので、CPUのパフォーマンスアップがそのまま利いた感じですね。

では、3Dの表示はどうかと、以前にRenderScriptの勉強で作ったニャンコカルーセルを実行してみました。以下に動画を貼り付けますが、XOOMと比較しても、そんなに悪く無いような気がします。3Dバリバリのゲームでもしない限りはあまり問題無いのかも?


ちなみに、Honeycomb用のコードがそのまま無編集でICSでも動きました。今回、Galaxy Nexusを購入した目的のひとつにRenderScriptを駆使したUIの実験をしたいことがあったので、この結果にはちょっと勇気づけられました。

■まとめ
このサイズの大きさから常用端末には薦めづらいですが、開発用の端末としていち早くICS上でアプリを作りたい人なら買っても良いかと。でも、もしICSのアップデートまで待てるなら、Galaxy S2 LTEが現世代機では最強端末だと個人的には思ってます。

まぁ、でも来年2月のMobile World Congressでは、Tegra3や最新のSnapdragon S4を搭載したICS機が続々と発表されるでしょうから、そんなに慌てて飛びつくほどのことは無いかも知れません。ってか、Androidって陳腐化が激しいですよね。バージョンアップがもっとタイムリーになされるのなら良いのですが、メーカーの自助努力に頼ったいまの調子では買い時が非常に難しい・・・

あれ?そう言えば、XOOMの購入レポート書いてなかったな、まぁ、ええか?(笑

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

Processing + ADK

この間、Basculeさん主催のMAKEイベントに参加したのですが、ProcessingやArduinoを用いたADK(The Android Open Accessory Development Kit)の話しとか、スペースバルーンプロジェクトの話しとか聞けて、かなり面白かったんですよ。

で、前々からADKは何かやってみたいなぁとは思ってたんだけど、基板とか弄るのはちょっと個人的にハードルが高いので、前々から見つけてたここのblogを参考にNexus OneとMacで自分もちょっと遊んでみました。

と言っても、やってることはほとんどそのまんま(笑。でも、まるっきり同じだとつまらないし、せっかくProcessingに興味が沸いたところなので、Nexus Oneの傾きセンサーの値をProcessingの描画パラメータに活用する(しょぼい)サンプルを作りました。

で、前述のblogに詳しいのですが、ADKって結局アクセサリーの認証方法だけ標準化してあって、アクセサリーとAndroid側のアプリの間に流すデータはBulk転送を使って独自のおれおれフォーマットで良いんですね?

まぁ、アクセサリーによって起動するアプリが選択されるから、そういう仕様の方が自由度が大きくて、開発者にとっては作り易いですよね。

では、動作時の動画を以下に貼りつけておきます。左手前のNexus Oneを傾けると立方体の群れが同じように傾いているのがわかりますでしょうか?


オリジナルのままだと傾きセンサーのデータを更新する頻度がちょっと少なすぎて、Processing上での描画がスムーズに見えなかったので、更新頻度を5倍にしてあります。

まぁ、こんな感じでMacとADKを使えば、簡単にAndroid機器と連携した何かを作ることはできそうです(これくらいだったらソケット通信でやればええやんってのは言いっこ無し)。

今回は、どっちかと言うとNexus Oneの方がアクセサリーっぽい役割になってますけど(ADK的にはMacの方がアクセサリー)、逆に例えばMacにKinectをつないで、そこで解析したデータをAndroid側に渡すなどしたら、何か面白いアプリが作れるかも知れませんね。

と、何だか伏線っぽい締め方になってますが、気が向いたら本当にやってみるかも知れませんけど(笑。

あ、今回はコード等は前述のblogからダウンロードできるからいいかな?と思ってましたが、せっかくですのでProcessingのsketchだけ、晒しておきますか。
int angle;
BufferedReader reader;
String line;

void setup() {
  size(440, 330, P3D);
  noStroke();
  fill(255);
  angle = 0;
}

void draw() {
  lights();  
  background(0);
  
  // カメラの設定
  float camZ = (height/2.0) / tan(PI*60.0 / 360.0);
  camera(width/2.0, height/2.0 - 100, camZ, width/2.0, height/2.0, 0, 0, 1, 0);
  
  translate(width/2, height/2, -20);
  int dim = 27;
  
  // 角度の書きこまれたファイルの中身を参照
  try {
    reader = createReader("angle.txt");
    line = reader.readLine();
  } catch (IOException e) {
    e.printStackTrace();
    line = null;
  }
  
  // 角度の文字列データを数値に変換
  if (line != null) {
    angle = (line.charAt(1) - '0') * 100;
    angle += (line.charAt(2) - '0') * 10;
    angle += (line.charAt(3) - '0');
    if (line.charAt(0) == '-') {
      angle = 360 - angle;
    }
  }
  
  // 演算した角度で回転
  rotateZ(radians(angle));

  // 長方形を並べる
  for (int i = -height/2; i < height/2; i += dim*1.4) {
    for (int j = -height/2; j < height/2; j += dim*1.4) {
       pushMatrix();
       translate(i, j, 0);
       box(dim, dim, dim);
       popMatrix();
    }
  }
}

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!