2011/06/20

Flasherの為のRenderScript入門

■RenderScriptとは?
RenderScriptはAndroid OS3.0から導入された、ハイパフォーマンスな3Dグラフィックスのレンダリングや演算をC言語(C99の構文)で記述できるAPIです。

これまでも、ハイパフォーマンスなレンダリングを行う手段として、OpenGLをNDKで直接叩くという方法がありましたが、RenderScriptはLLVMで一旦中間コードにコンパイルされたものがアプリケーションに付随され、実行時にそれぞれの環境向けに最適化されたマシンコードに更にコンパイルして実行するという仕組みになっております。

その結果、使用しているCPUやGPUの種類によらず、同一のソースコードでそれぞれの環境に置いて効率の良いパフォーマンスを発揮しますが、当然のこととして、特定の環境向けにカリカリにチューニングされたOpenGLのパフォーマンスには及びません。

ただ、iPhoneと違って、様々なプラットフォーム上に展開しているAndroid OSのデバイスとしては、環境依存を吸収する手段として、なかなか面白い仕組みでは無いでしょうか?

■RenderScriptシステムのレイヤー構造
RenderScriptのシステムは、以下の3つのレイヤーから構成されます。

・Native RenderScript layer
このレイヤーはレンダリングや演算の為のRenderScriptそのものです。上に書いたように、C言語の構文で記述しますが、 RenderScriptは汎用的なCPUのみならず、GPU上でも実行する場合もあり得るので、標準的なCのライブラリーがすべて使えるわけではありません。

Native RenderScriptライブラリーで提供されている主な機能は下記。
  1.  膨大な数の演算関数
  2. ベクターや行列等の基本データ型に変換するルーチン
  3. ログ取得の為の関数
  4. グラフィックスのレンダリング関数
  5. メモリー領域確保の為の関数
  6. 2元、3元、4元ベクトル等の、RenderScriptシステムをサポートするデータ型や構造
・Reflected layer
Reflected layerは、AndroidVMからnative RenderScriptコードにアクセスする為の レイヤーで、Androidのビルドツールが自動生成します。その為、Reflected layerは、Android frameworkからRenderScript内の関数や変数にアクセスする為のエントリーポイントを提供します。

・Android framework layer
Android framework layerは、RenderScript APIや通常のAndroid framework APIから構成され、あなたのアプリケーションのActivityのライフサイクルやメモリーの管理などをします。また、このレイヤーで、Reflected layerを介して、ユーザーの操作をnative RenderScriptのコードに関連づけたりもします。

と、かなり前置きが長くなりましたが、詳しくはここのドキュメントを読んでみてください。Flasher的にはC/C++で記述し、同様にLLVMでActionScriptに変換するAlchemyを使うのと、ノリは似ているかも知れませんね。

今回、RenderScriptを使ってFlashでよく使われるParticleのデモを実装してみたいと思いますが、題材としてWonderflの重力マウス(さらに軽量化してみた)を使用したいと思います。

■重力マウスをRenderScriptに移植
重力マウスを移植するにあたって、以下の4つのファイルを記述します。
Gravity.javaGravityアプリのActivityを記述
GravityView.javaRenderScript用のSurfaceViewを記述
GravityRS.javaRenderScript用のエントリーポイントクラスを記述
gravity.rsRenderScriptのコードを記述

Gravity.java
package com.android.sample;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class Gravity extends Activity {
    private GravityView mView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        mView = new GravityView(this); // RSSurfaceViewの作成
        setContentView(mView);
    }
}
特に何も難しいことはしていません。Activityの中で、アプリケーションで使用するViewを設定していますが、今回はRSSurfaceViewを継承したGravityView(後述)を作成してます。

GravityView.java
package com.android.sample;

import android.renderscript.RSSurfaceView;
import android.renderscript.RenderScript;
import android.renderscript.RenderScriptGL;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.MotionEvent;

public class GravityView extends RSSurfaceView {

    public GravityView(Context context) {
        super(context);
    }

    private RenderScriptGL mRS; // グラフィックス出力の為にスクリプト等
                                // を付与するコンテクスト

    private GravityRS mRender;  // エントリーポイントクラス

    // Surfaceの構成が変わると呼ばれるハンドラー
    public void surfaceChanged(SurfaceHolder holder, int format,
                               int w, int h) {
        super.surfaceChanged(holder, format, w, h);
        if (mRS == null) {
            // グラフィックスのピクセルフォーマットを指定する為のクラス
            RenderScriptGL.SurfaceConfig sc
                = new RenderScriptGL.SurfaceConfig();
            mRS = createRenderScriptGL(sc);
            mRS.setSurface(holder, w, h); //osのsurfaceをバインド

            // エントリーポイントクラスを作成
            mRender = new GravityRS();
            mRender.init(mRS, getResources(), w, h); // 初期化
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        // 後始末
        if (mRS != null) {
            mRS = null;
            destroyRenderScriptGL();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        // エントリーポイントクラスにタッチした座標、指圧と
        // IDの情報を渡す
        // IDはマルチタッチがサポートされている場合に、
        // タッチした指を識別する為のID
        mRender.newTouchPosition(ev.getX(0), ev.getY(0),
            ev.getPressure(0), ev.getPointerId(0));        
        return true;
    }
}
RenderScriptでレンダリングした内容を表示する為のSurfaceViewクラスです。

surfaceChanged()メソッド内でRenderScriptのコンテキストを生成し、Surfaceをバインドしています。スクリプトのバインディングは、後述するエントリーポイントクラス(GravityRs)の方で行なっています。

また、タッチイベントが発生した際も、エントリーポイントクラスに座標等のタッチに関連する情報を渡します。このように、ユーザーの操作によって発生したイベントは基本的にエントリーポイントクラスに情報を渡して、エントリーポイントクラス側で処理をしてもらいます。

GravityRS.java
package com.android.sample;

import android.content.res.Resources;
import android.renderscript.*;

public class GravityRS {
    public static final int PART_COUNT = 50000; // 総パーティクル数

    public GravityRS() {
    }

    private Resources mRes;
    private RenderScriptGL mRS;      // レンダリングのコンテクスト
    private ScriptC_gravity mScript; // スクリプトファイルへの
                                     // エントリークラス

    // 初期化関数
    public void init(RenderScriptGL rs, Resources res,
                     int width, int height) {
        mRS = rs;
        mRes = res;

        // GLSLコードを用いないシンプルなフラグメントシェーダーを作成
        ProgramFragmentFixedFunction.Builder pfb
            = new ProgramFragmentFixedFunction.Builder(rs);

    // バーテックスプログラムから指定された色を使用する
        pfb.setVaryingColor(true);

        // フラグメントシェーダーをレンダリングコンテクストにバインド
        rs.bindProgramFragment(pfb.create());
        
        // スクリプト内のPoint構造体×パーティクル数の分だけ
        // メモリー領域を確保する
        ScriptField_Point points = new ScriptField_Point(mRS,
                                                         PART_COUNT);
        
        // RenderScriptで表示する幾何学データのコンテナを作成する
        // オブジェクト
        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);

        // pointsを頂点データとしてオブジェクトに追加
        smb.addVertexAllocation(points.getAllocation());

        // データの種類を点座標に設定
        smb.addIndexSetType(Mesh.Primitive.POINT);

        // コンテナを作成
        Mesh sm = smb.create();

        // スクリプトファイルのエントリークラスを生成
        mScript = new ScriptC_gravity(mRS, mRes, R.raw.gravity);

        // スクリプト内のpartMeshにMeshをセット
        mScript.set_partMesh(sm);

        // pointsとスクリプト内のpointをバインド
        mScript.bind_point(points);

        // レンダリングコンテクストにスクリプトをバインド
        mRS.bindRootScript(mScript); 
    }

    // タッチ情報のアップデート
    public void newTouchPosition(float x, float y, float pressure,
                                 int id) {
        mScript.set_gTouchX(x); // スクリプト内のgTouchXにX座標をセット
        mScript.set_gTouchY(y); // スクリプト内のgTouchYにY座標をセット
    }
}
一見、複雑なことをやっているようですが、まずフラグメントシェーダーの設定し、それから座標データを格納するメモリー領域の確保をしてます。その後、ビルドツールで自動生成されるエントリーポイントのクラスをインスタンス化して、スクリプト内の変数やポインターと確保したメモリー領域の関連付けを行ってます。

そして最後に、レンダリングコンテクストにスクリプトをバインドして実行、という感じでしょうか?

gravity.rs
#pragma version(1)
#pragma rs java_package_name(com.android.sample)
#pragma stateFragment(parent)

#include "rs_graphics.rsh"

// staticな変数はエントリーポイントが生成されない
static int initialized = 0;

rs_mesh partMesh; // 描画するデータのコンテナ

// タッチ座標
float gTouchX = 0.f; // X座標
float gTouchY = 0.f; // Y座標

// パーティクルの構造体
typedef struct __attribute__((packed, aligned(4))) Point {
    float2 delta;
    float2 position;
    uchar4 color;
} Point_t;
Point_t *point;

// パーティクルの初期化
void initParticles()
{
    // データサイズを取得
    int size = rsAllocationGetDimX(rsGetAllocation(point));

    float width = rsgGetWidth();   // サーフェイスの幅
    float height = rsgGetHeight(); // サーフェイスの高さ

    uchar4 c = rsPackColorTo8888(0.9f, 0.0f, 0.9f); // 色

    Point_t *p = point;
    for (int i = 0; i < size; i++) {
        p->position.x = rsRand(width);  // 乱数でX座標をセット
        p->position.y = rsRand(height); // 乱数でY座標をセット
        p->delta.x = 0;
        p->delta.y = 0;
        p->color = c;
        p++;
    }
    initialized = 1; // 初期化完了
}

/*
 * root()は描画する毎に呼ばれる
 */
int root() {
    // 初回実行時に初期化
    if (!initialized) {
        initParticles();
    }

    float width = rsgGetWidth(); 
    float height = rsgGetHeight();

    // 指定色でレンダリングサーフェイスをクリア
    rsgClearColor(0.f, 0.f, 0.f, 1.f);
    
    int size = rsAllocationGetDimX(rsGetAllocation(point));

    Point_t *p = point;
    for (int i = 0; i < size; i++) {
        float diff_x = gTouchX - p->position.x;
        float diff_y = gTouchY - p->position.y;
        float acc = 50.f/(diff_x * diff_x + diff_y * diff_y);
        float acc_x = acc * diff_x;
        float acc_y = acc * diff_y;
        p->delta.x += acc_x;
        p->delta.y += acc_y;
        p->position.x += p->delta.x;
        p->position.y += p->delta.y;
        p->delta.x *= 0.96;
        p->delta.y *= 0.96;
        if (p->position.x > width) {
            p->position.x = 0;
        } else if (p->position.x < 0) {
            p->position.x = width;
        }
        if (p->position.y > height) {
            p->position.y = 0;
        } else if (p->position.y < 0) {
            p->position.y = height;
        }
        p++;
    }    
 
    rsgDrawMesh(partMesh); // 座標データ列の描画
    
    return 1; // おおよそ1ms後に再描画
}
最後にいよいよRenderScriptの本体です。

まず、Point構造体がパーティクルのデータ構造を構成しており、初回実行時だけ、initParticles()関数内で、パーティクルの座標や色のデータの初期化を行います。

そして、root()関数が描画毎に呼ばれる関数で、Flasher的にはenterFrameのイベントハンドラーのようなものと思えば、理解がしやすいでしょうか?この中のfor文で各パーティクルの座標データをアップデートしております。ここのコードは重力マウスのコードをほぼ、そのまま丸パ◯リ(笑)

ActionScriptでは、動作スピードを上げる為に、リンクリストを形成しておりましたが、ここではリンクリストは使用しないで、単純に構造体のポインターをfor文で要素の数だけ回すという処理になってます。

パーティクルの座標データの更新が終わったら、rsgDrawMesh()という関数で、コンテナ内のデータを元に描画を行います。

最後に、このファイル中に出てきたRenderScript関連の関数をまとめておきます。
rsGetAllocation()VM側で確保したメモリー領域を返す
rsAllocationGetDimX()メモリー領域のX方向の大きさを返す。今回、データは1次元の配列なので、要素数を返すことと同じか?
rsgGetWidth()サーフェースの幅を返す
rsgGetHeight()サーフェースの高さを返す
rsPackColorTo8888()R, G, B値から色コード(8888形式)を生成
rsRand()乱数値を生成
rsgDrawMesh()コンテナのデータを元に描画

■RenderScriptのパフォーマンス
RenderScriptを使うことで、どれくらいのパフォーマンスが出たのでしょうか?PC上では重力マウスは、楽に60fps出ていますが、手持ちのXOOMのブラウザ経由では、20fpsほどしか出ません。

今回、RenderScriptにはfpsを計測する機能を実装していないので、厳密にはどれくらいのフレームレートが出ているのかはわかりませんが、 フルスクリーン(解像度で4倍以上)で、尚且つパーティクルの数を50000に増やしても、目視で30fpsはゆうに出ているように感じました。

なので、少なく見積もっても5倍以上の性能差はありそうです。モバイル版のFlash Playerに早くStage3Dが搭載されることが待たれますね。

実際のアプリケーションの動作の動画を以下に貼りつけておきます。


■まとめ
難しいことは全て理解しなくとも(私も半分くらいしか理解できてない)、上記ファイルをテンプレートに、RenderScriptファイル中のfor文の中身(パーティクルの動作をつけている部分)を適当に書き換えれば、簡単に遊べると思いますので、腕に覚えのあるFlasherの方に是非お試しいただければと思います。

まぁ、遊ぶにはAndroid 3.xの実機が必要にはなりますがね・・・(^_^;

2011/06/12

書評:Titanium Mobileで開発するiPhone/Androidアプリ


6/10に世界初のTitanium Mobile本こと「Titanium Mobileで開発するiPhone/Androidアプリ」が発売されました。

レビューをほんの少しお手伝いさせていただいた関係で、ご献本いただきましたので、軽く中身をご紹介させていただきます。

第1章はTitanium Mobileを開発したAppcelerator社の紹介やツールの将来展望、そして気になる価格体系の情報など。そしてそして、最後に例のSection3.3問題に関して。昨年のいま頃は、もうこれからどうなるの?って感じで、個人的にTitaniumに対するモチベーションもかなり削がれた時期だったのですが、まさか1年経って開発環境としてこんなに注目を集め、入門書まで刊行されることになるとは、感慨もひとしおですね。

第2章は開発環境のインストールから、Hello worldまで。Titanium Mobileは開発環境もターゲットもマルチプラットフォームに対応しているので、すべての環境を網羅するのは大変だったと思うのですが、非常に丁寧に解説されてます。

第3〜5章は、具体的なアプリの開発を通じて、より詳細にTitanium Mobileの使い方を学べます。Twitterクライアントを題材にしているので、多くの方に非常に参考になる内容では無いでしょうか?また3-4章でスクリプトファイルの分割に関しての解説があるのですが、個人的にはTitanium Mobileでアプリを作る際にわりと肝になる部分ではないかと思いますよ。

第6章は、Titanium Mobile APIリファレンスとなってます。本家Appceleratorのサイトよりも詳細で、豊富なコードサンプルも掲載されており、本書の実に1/3ほどのボリュームをこのAPIリファレンスに費やしています。簡易リファレンスと書いてあるのは単なる著者のご謙遜でしょうね(笑)。このAPIリファレンスの為だけに本書を購入しても良いくらいです。

そして、充実のAppendixへと本書は続きますが、これより先は実際に手に取ってみて皆さんの方で是非ご確認してみてください。拙作のXib2Jsも紹介されてて、うれしい限りです(何でも、Appcelerator社の研修テキストブックにも紹介されてるとか?)。

ってことで、これから始める方にも、既にTitanium Mobileを開発に使っている方にもお薦めの一冊だと思います。

Code strong,

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年振りくらいかな(汗