Androidアプリのビルドからアップロードまでを全自動化する方法

March 2nd, 2012

久しぶりのエントリー。ゆーすけべーさんのおっぱいスクリプトに触発されて、自分も少し生活を楽にするスクリプトを書いてみました。

KinoPadという娘のために一時間くらい作った即席アプリが題材です。公開以来ぼちぼち他の言語にも対応してほしいとのご要望を頂いてきました。ローカライズ自体は言語につき数分で出来ることがわかり、ゴミアプリを量産する後ろめたさを感じながらも18言語分作ってしまいました。しかし、これを手作業でやっていてはメンテナンスがとても大変です。そこで極限まで手間を減らせるよう色々と工夫しましたので地味ですがTipsとして公開します。ゆうすけべーさんのような高尚な動機ではなくてすみません。

プロジェクトをテンプレート化する

まず、18個にもなると、一つずつEclipseのプロジェクトを作って、編集して、ビルドしてという作業は想像しただけでも絶望感を味わえます。そこで、一つ雛形となるプロジェクトファイルを用意して、スクリプトで動的にプロジェクトを生成することにしました。テンプレートとして差し替えられるようにするのは次のアイテムです。

  • パッケージ名
  • アプリケーションID
  • アイコン

パッケージ名とアプリケーションIDを書き換える

プロジェクトのテンプレートが出来たら後はそれをコピーして派生バージョン用に書き換えます。パッケージ名とアプリケーションIDはAndroidManifest.xmlとjavaファイルに書かれていますので、コピーと置換はbashだと下記のような感じになります。

cp -r ${TEMPLATE_DIR} ${PROJECT_DIR}
find ${PROJECT_DIR} -type f -name "*.xml" -or -name "*.java" | xargs sed -i "s/org\.exilis\.kinopad/org.exilis.kinopad${LANG}/g"

プログラムの挙動自体はこのパッケージ名を判別して変えるようにするとよいでしょう。パッケージ名はgetPackageName() で取得できます。

getApplication().getPackageName()

アイコンの生成はImageMagick

アイコンも18個を手作業で作るとなると大仕事です。クオリティにこだわらなければ簡単な合成のみでそれらしいものは作成可能です。KinoPadの場合は、ベースとなる画像を一枚用意して、後はそれに国旗の画像を重ねただけのものを使用しています。合成にはImageMagickを使います。

convert assets/icon512.png assets/flags/${LANG}.png -gravity southeast -composite assets/icon512_${LANG}.png

ついでに解像度に合わせた縮小画像を生成するためのコマンドは下記のとおりです。

convert -geometry 36x assets/icon512_${LANG}.png ${PROJECT_DIR}/res/drawable-ldpi/ic_launcher.png
convert -geometry 48x assets/icon512_${LANG}.png ${PROJECT_DIR}/res/drawable-mdpi/ic_launcher.png
convert -geometry 72x assets/icon512_${LANG}.png ${PROJECT_DIR}/res/drawable-hdpi/ic_launcher.png
convert -geometry 96x assets/icon512_${LANG}.png ${PROJECT_DIR}/res/drawable-xhdpi/ic_launcher.png

プロジェクトのビルドとパッケージ作成

これもコマンド一発です。いちいちEclipseを起動する必要もありません。

ant -f ${PROJECT_DIR}/build.xml release

ついでに接続されている実機にもインストールしたければ、

${ANDROID_SDK_ROOT}/platform-tools/adb install -r

でOKです。

Androidマーケットへの自動アップロード

これがキモです。普通は下記の作業をWebブラウザでやることになります。

  1. http://market.android.com/publish にアクセス&ログイン (入力&1クリック)
  2. 該当アプリのエントリを開く (1~2クリック)
  3. 「APK ファイル」のタブを開く (1クリック)
  4. ファイルをアップロードする (3~クリック)
  5. アクティベート、保存 (3~クリック)

少なく見積もっても一つアップロードする度に10クリック+テキスト入力が必要になり、アップロードやページロードの待ち時間を考慮すると1分でやるにはかなりの反射神経が要求されます。18アプリともなると単純計算で約20分、180クリックが必要となりやってられません。自動化しましょう。

アプローチとしては二通りあります。一つはおっぱいスクリプトのように生のHTTPリクエストでやりとりする方法、もう一つはWebブラウザの操作を自動化するツールを活用する方法です。

前者は、APIとしてHTTP経由のインターフェースが定義されている場合や、込み入った認証が必要ない場合、あるいはページがJavaScriptやCanvasにあまり依存しておらず比較的簡単にパースできる場合に高速で有用です。

一方後者はそれ以外のケースでも無難に動くという点で適用範囲の広い方法です。今回は、後者の方法をWatirというRubyのモジュールを使用して実装することにしました。目標は

update_apk.rb <google_id> <google_pass> <full_path_to_apk>

というコマンドを実装することです。

APKファイルのバージョンを取得する

パラメーターとして渡されるのはAPKファイルのパスだけですので、アップロードする際に必要になるアプリケーションIDやバージョン文字列はAPKから取得します。これらは、自動化する際のURLを生成するためにあると便利です。APKファイルといってもタダのZIPなので展開してAndroidManifest.xmlを読めば簡単に抽出出来ると思ったのですが、パッケージングするときにバイナリーにエンコードされてしまっていることが分かりました。この件についてはKlabの方の記事が詳しいです。でググったらRuby用のパーサーがありましたのでこれを使わせていただくことにしました。パーサー部を関数化するだけの単純な変更を加えました。

https://github.com/exilis/apk-updater

Watirでアップロードする

これは、FirebugなりでDOMを見ながら実際にワンステップずつwatirのコマンドに書き換えていくことになります。以下のような感じになりました。

上記のパーサーと合わせてgithubに載せてあります。

require 'rubygems'
require 'watir-webdriver'
require 'watir-webdriver/wait'
require File.dirname(__FILE__) + "/axml2xml"
require "rexml/document"
 
unless ARGV.length == 3
  puts "Usage: ruby update_apk.rb <google_id> <google_pass> <full_path_to_apk>\n"
  exit
end
 
userid = ARGV[0]
passwd = ARGV[1]
apk_fullpath = ARGV[2]
 
# extract package name and version code out of the APK
begin
  xmldoc = REXML::Document.new(load_android_manifest_xml(apk_fullpath))
  version_code = xmldoc.elements['manifest'].attributes['versioncode'].to_i(16)
  package_name = xmldoc.elements['manifest'].attributes['package']
rescue
  puts "Error: failed to load APK file\n"
  exit
end
 
# login
b = Watir::Browser.new
b.goto 'https://market.android.com/publish/'
b.text_field(:id => 'Email').set userid
b.text_field(:id => 'Passwd').set passwd
b.button(:id => 'signIn').click
 
# upload & save
Watir::Wait.until { b.text.include? "All Android Market listings" }
b.goto 'https://market.android.com/publish/Home#AppEditorPlace:p=' + package_name
b.div(:id => 'gwt-debug-multiple_apk-apk_list_tab').when_present.click
b.button(:id => 'gwt-debug-apk_list-upload_button').when_present.click
b.file_field(:name, "Filedata").when_present.set(apk_fullpath)
b.button(:id => 'gwt-debug-app_editor-apk-upload-upload_button').when_present.click
b.button(:id => 'gwt-debug-bundle_upload-save_button').when_present.click
b.execute_script("window.confirm = function() {return true}")
b.a(:id, "gwt-debug-apk_list-activate_link-#{version_code}").when_present.click
b.button(:id => 'gwt-debug-multiple_apk-save_button').when_present.click

まとめ

以上の処理をバリエーションごとにループで回すシェルスクリプトなりを書けば、元のテンプレートとなるプロジェクトを一つ修正するだけで、ビルドからAndroid Marketへのアップロードまでをコマンド一発で済ませることができます。GNU Parallel等で並列化するともっと素敵です。KinoPadの場合は実質一つのバージョンをメンテナンスするだけの労力で18のバージョンを管理できるようになりました。

開発に関係の無い日常の業務でも自動化はとても有効です。時間の節約だけでなく、ヒューマンエラーのリスクを減らすことができます。私は他の業種のことは知りませんが、コンピューターを使って仕事をしている現場のほとんどでExcelのマクロ以上の自動化は出来ていないのではないかと思います。Salesforceなどワークフロー全体を管理するシステムにしっかり投資していれば話は別ですが、私が経験してきたIT企業の現場ですらエンジニアがいるにもかかわらず自動化可能な「作業」を手作業でこなしている場面が多々見られました。何でも二回以上する可能性のあることは自動化すると生産性は確実に上がります。

今後の展望

ところで、蛇足ですがBingの画像検索APIにあるAdultフラグは全然使い物になりません。KinoPadもおっぱいスクレイパーと同じく内部でBingの画像検索APIを使用していますが、子供向けということでこのフラグを有効にしても想像力豊で過激な画像がたくさん降ってきます。仕方なく不適切な検索結果が表示された時はワンタッチで通報できる機能を実装していますが、現状キーワードベースなのでキリがないというか根本的な解決にはつながりません。ちゃんとやるなら画像単位で通報できるようにして、サーバー側で画像解析&クラスタリング、いけない画像コーパスを構築して検索結果を表示する前に逐次評価ってことになると思いますが、KinoPadにそんなに手間をかけるつもりはないので今のところ放置です。

ただ考えようによっては「いけない画像だけをはじける=いけない画像だけを集められる」ということなので、同等の技術はおっぱいスクリプトを新たな次元に押し上げる足がかりになるに違いありません。つまりおっぱいスクリプトが収集したビッグおっぱいデータから有益なおっぱいだけを抽出するということです。おっぱい星人&ビッグデータ屋さんのみなさん、いかがでしょう?

NaClでローカルディスクからファイルを読み込む方法

December 3rd, 2011

しばらく放置していたMilkyTrackerのNaClポートを少し更新してローカルディスクからファイルを読み込めるようにしてみました。

機能としては単純なものなのですが、これがなかなか一筋縄には行かず、ハマりにハマりまくったのでポイントを整理して晒しておきます。(JavaScriptは簡単のためにjQueryベースで書きます)

PostMessageは64kbが上限

まずアプローチとしてNaClは基本的にJavaScriptに許される範囲内のインターフェースしか持たないため、ローカルのファイルにアクセスするためにはHTML5で定義されているFileReaderを使うことが考えられます。JavaScriptで読み込んだらそれをBase64形式でNaClにPostMessageしてやればデータの受け渡しができます。コードはこんな感じです。

$("#file").change(function(ev) {
  var reader = new FileReader();
  reader.onloadend = function(e) {
    e.target.result.match(/.*base64,(.*)$/);
    module.postMessage("import:" + ev.target.files[0].name + "," + RegExp.$1);
  };
  reader.readAsDataURL(ev.target.files[0]);
};

しかし、ドキュメントに書かれていなかったので実装してから分かったのですが、PostMessageに渡すことの出来るデータサイズには64kbの制限が有り、これを越えるデータを渡すとエラーが発生します。MilkyTrackerの場合は読み込むデータソースが64kbに収まらない事のほうが多いと思いますので、その場合はデータを64kb単位で細切れにして送らなければいけません。いけてません。

createObjectURLで取得したアドレスをNaClから直接ダウンロード

FileReaderによる64kb分割送信大作戦は、そもそもバイナリーデータをbase64エンコードしている時点でデータサイズが膨らんでいる上に、分割してオーバーヘッドが発生しまくるという点であまり格好良くありません。そこでもっとスマートにできる方法を見つけました。

window.URL.createObjectURL を使います。このAPIにFileオブジェクトやBlobオブジェクトを渡すと、”blob:(URIエンコードされた一時的なローカルファイルを指すURI)”というようなURIが取得できます。このURIは、例えば画像ファイルのURIを取得してJavaScriptでimgタグのsrcにセットするだけで画像が表示されたりするもので、ブラウザ内ではサーバー上のファイルを指すURIと同じように使うことが出来ます。このURIをNaClのpp::URLLoaderで(NaCl内に)ダウンロードします。コードはこんな感じです。(Chromeではwindow.webkitURL.createObjectURLになる)

$("#file").change(function(ev) {
  var url = window.webkitURL.createObjectURL(ev.target.files[0]);
  module.postMessage("import_url:" + ev.target.files[0].name + "," + url);
};

NaCl側のコードは長いので割愛させて頂きますが、NaCl-Quakeのソースが参考になります。これはサーバー上からゲームアセットをダウンロードするためにURLLoaderを使用していますが、同じ手法でcreateObjectURLによって生成されたURIを使用してローカルファイルをダウンロード出来ます。

これでバイナリデータを直接NaCl内に取り込むことが出きるようになりました。PostMessageはコマンドとURIを受け渡すためだけに使われます。恐らく現時点ではこれが一番よい方法かとおもいます。

JavaScriptからファイル選択ボックスを表示したい

あと一点だけ触れておきたいのが「ファイルを開く」ダイアログボックスをどうやって表示するかです。実はこれに一番ハマりました。結論を先に言っておくと、NaClには現状そのようなインターフェースはありませんし、NaClからJavaScriptのメッセージングを駆使してJavaScriptに表示させることも出来ませんでした。

HTML/JavaScriptのレイヤではファイル選択ボックスはHTMLタグで<input type=”file”/>で定義されたボタンをユーザーがクリックした時に表示されます。これはかなり厳しい制約で、例えばJavaScriptから$(“#file”).click();と呼び出してユーザーのクリックを偽装しようとしてもダメです。このファイル選択ボックスはJavaScriptがローカルディスクのファイルにアクセスする唯一の方法ですので、自由にスクリプトからユーザーに表示出来るとセキュリティ的によろしくないということでしょう。

しかし、このブラウザ標準のボタンは装飾が地味で、必ずしもページのデザインに馴染まないということで、いろいろな裏技が開発されています。

  1. invisibleなinput要素を見せたいデザインのdiv要素に重ねる
  2. $(“#file”).show().focus().click().hide();

この二つ目に注目です。先ほどJavaScriptからinputオブジェクトのclickイベントを偽装することは出来ないと書きましたが、オブジェクトが表示状態に有り、かつフォーカスを持っている状態で、かつ「JavaScriptで捕捉した他のオブジェクトのclickイベント内」であれば可能みたいです。(自分で試しただけなので正確ではないかもしれません)

これを使えるのではないかと期待しましたが残念ながら無理でした。「他のオブジェクトのclickイベント内」というのがポイントで、あくまでこのトリックはボタンの外見をカスタマイズするためにわざわざブラウザに実装されたもののようで、例えばsetTimeoutやmousemoveやonload等他のハンドラで呼び出してもファイル選択ボックスは表示されません。

念のため、NaClオブジェクトのmessageハンドラから呼び出してみましたが、やっぱりダメでした。

セキュリティのためとはいえ、描画を完全にNaCl内で完結するMilkyTrackerのようなアプリの場合、NaClコンテナ内でのイベント(例えば[LOAD]ボタンをクリック)をトリガーとしてファイル選択ボックスを表示することが出来ないと言うのは悲しすぎます。ファイルを選択するだけであればブラウザに対するドラッグ&ドロップは一つの打開策ではありますが、やはりファイルを選ばせるというUIは捨てがたいです。

NaCl内でもマウスがクリックされた時の特別なハンドラが提供され、そこからであればJavaScriptと同じようにファイル選択ボックスを表示できる、というロジックであれば現状のセキュリティレベルを落とすことなくNaClアプリをもっとええ感じに出きそう。

1の方法をNaClの埋め込みオブジェクト上でやるというのはまだ試してないけどひょっとしたらうまく行くのかも。どなたか他によい方法があればご教示下さいませ。

一才児からのWEB検索【KinoPad】を公開しました

November 18th, 2011

2歳になってしばらくたった娘はiPadやiPhoneにも飽きてきて最近はPC、特にキーボードに興味を持ち始めました。最近覚えたアルファベットや数字が書いてあるボタンがたくさんついていて、しかも押したらリアルフィードバック&画面になんか出るというレトロ感がタッチUI世代のtoddlersにはたまらないようです。

色々なものに興味を持ってくれるのは大いに結構なのですが、これまでは膝の上に載せてプログラミングしていてもおとなしくしていたところがキーボードを触られ始めるとこちらもかなわんということで、古いノートPCを与えて遊ばせていました。しかし、GUIなしのNetBSDでVim縛りというのは少々ストイック過ぎたようで、すぐにお父さんの作業を妨害しにくるようになりました。

これは困った、ということで即席でkinopadなるものを作りました。

Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player

出来ることは、

  • キーを押すと文字が画面に出る
  • 入力した単語でイメージ検索した結果を背景に表示してくれる

昨日早速娘に遊ばせて見たところ大喜びでしたので公開することにしました。

kinopadを使えば一才児でも多分楽しくABCや単語で遊びながらついでにインターネットで検索も出来ます。

子供が使うことを考慮に入れて検索エンジンのセーフサーチはStrictに、入力される単語に対してもbadwordsのチェックを行っています。が、時々変な画像が出てきてしまいます。こればかりは申し訳ないのですがご一報頂ければbadwordのリストに登録させていただきます。その他ご意見やご要望もどうぞお寄せ下さい。

stand aloneアプリをブラウザに移植するということ

October 21st, 2011

ここ数日ちょっと暇を見つけてNaClにMilkyTrackerを移植して遊んでいるのですが、クロスプラットフォーム向けにデザインされている事もあってコアの移植自体はもう殆ど出来てしまっています。しかし、サンドボックスに閉じ込められた環境でファイルIOをどう実装するかについて色々迷っています。

と言うのも、オリジナルのMilkyTrackerは当然ローカルのディスクに対してデータの読み書きを行いますが、ブラウザに移植した時点でデータはクラウドに持っていくのが筋なのではないかと思うからです。

もちろんNaClBoxのようにブラウザの中でスタンドアロンに動くだけでも、十分おもしろいのですが、アプリをインストールしないでも動くというのと(これは結構大きいか)、ChromeOSで動くというくらいしか有り難味がなく、ポテンシャルが活かしきれていません。

 

ローカルのファイルを読み書きする

NaClのセキュリティモデルでは、ブラウザ内で許されていることしかできないので。OSのAPIも呼べなければローカルのファイルシステムへの自由なアクセスも出来ません。

  • HTML5のアプリ固有の仮想ファイルシステムをLocalDatabase or WebDatabaseで実装する
  • HTML5のローカルファイルアクセス機能(FileReader and FileWriter)を使う

前者は作成したファイルに他から一切アクセスできないのでいまいちだと思いますし、後者もファイルの一覧などを取得することは出来ず、あくまでブラウザにドロップされたりinputで選択されたりしたファイルを読み込むものです。また書き込みについてもブラウザの対応状況がいまいちだったりします。

単純にスタンドアロンアプリを移植する、という観点からあえて選ぶと後者のFileReader / FileWriterを使った実装がよいと思います。

クラウド上でファイルを読み書きする

折角ブラウザ上で動いていて常にサーバーと通信できる状態にあるのに、無理してローカルのストレージにこだわるのも微妙です。クラウドをストレージ代わりに使うような実装をすれば、ログイン処理こそ必要なものの、ブラウザさえあればどのマシンからでも自分の曲データにアクセスできますし、バージョン管理やバックアップの心配もいりません。

また、サーバー側でそのまま楽曲を共有させたりと、ソーシャルな要素を付け加えようと考えたりするともう夢が膨らんでしまいます。特にMilkyTrackerの場合はwww.modarchive.comのコミュニティーと密接に繋がっていますのでおもしろい事がいろいろ出来そうです。

問題点は、大したことではないですが、セキュリティモデル上ドメインの制限があるのと、オリジナルのUIをできるだけ保ったままうまく実装する方法を見つけるということぐらいでしょうか。

鏡が円柱状の万華鏡はどう見える?

October 21st, 2011

万華鏡シミュレーターカレイド卿をちょこっと更新しました(Android版のみ)。これまでのバージョンは鏡を三角に向かい合わせた万華鏡をシミュレートしたものだったのですが、他の形状にするとどうなるのだろうという個人的な好奇心から色々試してみました。

 Available in Android Market Lord Mange - exilis

メニューで形状が選べる!

上のキャプチャ画像は三角柱上の万華鏡のスクリーンショットなのですが、これを四角柱、五角柱、六角柱にしたらこんな感じになりました。

三角柱
四角柱
五角柱
六角柱

では皆さん、これが円柱だったらどのように見えるでしょう

答えは是非アプリでお試しください!w Available in Android Market

※ヒント:以下三角形から18角形までのuvマップです

Ubuntu11.10のタスクスイッチャーがいけてない件

October 18th, 2011

自分用のメモ。下記のステップで元のスイッチャーに戻せる。

情報元:http://ubuntuforums.org/showthread.php?t=1860267

  1.  “CompizConfig Settings Manager” をインストールする
    sudo apt-get install compizconfig-settings-manager
  2.  “CompizConfig Settings Manager” を起動し [Desktop] – [Ubuntu Unity Plugin] – [Switcher]を開く
  3.  “Key to start the switcher” と “Key to start the switcher in reverse” を無効にする
  4.  [Window Management] を開き”Static Application Switcher”をチェックする

MilkyTrackerをNaCLに移植してみた(まだ途中)

October 17th, 2011

週末にNaClを遂に触ってみた。三年前に論文が出たときからコードは読んでいたんだけど、ハマりどころが多そうで実際にNaCl上で動くコードを書く気にはなかなかなれなかった。今ではChromeに標準搭載されていたりと、大分落ち着いてきたっぽいので手遅れにならないうちにと思ってやってみた。

やるからにはビジュアル的にいけてて音も出る派手な奴がいいと思ったけど、QuakeDosboxという先行者たちがいたのでゲームはやめてMilkyTrackerにした。DemoScene好きなら誰もが知っているModトラッカーFastTrackerのクローンだ。MilkyTrackerはマルチプラットフォーム対応のオープンソースなので移植しやすいように綺麗にコードも整理されている。

やってみたのがこれ → SaltyTracker

ファイル回りは当然そのままでは動かないのでオンメモリの簡単なファイルシステムをやっつけで実装して、[Load]ボタンを押したら決め打ちでデモの曲が読み込まれるようになっている。Enterキーで再生が始まる。

Linuxで開発したが、この件でLinux版のChromeはNaClが標準で無効になっている。ここの手順で有効にしないといけない。UbuntuとChromeOSとMacOSXでは動いた。動かせなかった人のために一応スクリーンキャストも。

Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player

コードを綺麗にしたら作者にパッチを送る予定。( patch will be released as soon as it’s polished )

NaClはあらゆる面で素晴らしい。以前は仕事でよくActiveXのオブジェクトを作ったりもしたけど、同じブラウザ上で動くプログラムといってもこれは全く別物だわ。あえて言うと既存のアプリを移植するという観点ではネットワーク回りが無いのが厳しいかな。パケットをカプセル化してWebSocket経由でプロキシさせるsocketレイヤでも書いたらみんな使ってくれるだろうか。サーバーを立てないといけないのとセキュリティが課題だな。

スマホアプリ開発環境雑感

October 17th, 2011

MagoPicのiPhone版を作るにあたって、下記いくつかの開発環境をリサーチしてみた。

結論から言うと、自分がやりたいことの主なジャンルからすると、そこまでクロスプラットフォームの開発環境に対するメリットは感じられなかった。またカメラやOpenGLなどを酷使しようとするとTitaniumやPhoneGapでは独自の拡張モジュールを書かないと対応出来なくて、そうなると結局ネイティブで書いた方がデバッグしやすかったりとやりにくさを感じた。しかし、TitaniumやPhoneGapは既存のWebサービスのフロントエンドを作るというような用途においてはかなりお手軽である事が分かった。

要はケースバイケースということなので、ケーススタディを交えて紹介する。

 

言語の壁は低い

C/C++がある程度分かればプラットフォームごとのラーニングコストはJavaとObjectiveCに関してはかなり低かった。またeclipseもXCodeも非常に洗練されており、普段screenとvimでの開発がメインの自分にとってはカルチャーショックだった。(結局ソースはvimで書いたけど)

JavaScriptに関してはノウハウがあった方がかなりよいがやっつけも可。

C/C++が分かるのであれば、新しい言語を学ぶことがいやだという理由でクロスプラットフォームの開発環境を選ぶというのはあまり得策では無いかもしれない。そういう動機で凝ったことをしようとしたりするとすぐに制限にぶつかる。

 

基本TitaniumでOK 【字画命名】

 名付けマシーン - exilis

このアプリはTitaniumで3分で作ったもの。Android版とiPhone版がコードを共有している。とはいっても、実際のロジックとUIはクライアントには実装されておらず、 数年前に子供の名前を考えるときに書いたPythonスクリプトをGoogle App Engineにのっけて適当にインターフェースをくっつけた。クライアントはWebViewを表示しているだけ。

ちなみに同等のことはPhoneGapでも出来るが、「PhoneGapはWebViewの拡張インターフェース」的な色合いが強くプログラムのエントリーポイントやプロジェクトの管理自体は開発者に委ねられている。一方でTitaniumはプロジェクトファイル自体も一つのxmlファイルから動的に生成され、単一のIDEでプロジェクトの作成からビルド、パッケージングまで行うことが出来る。現時点ではiPhoneとAndroid以外のプラットフォームを視野に入れないのであればPhoneGapを使うメリットは感じられなかった。

  • Titaniumによるクライアントのコード実装:3分
  • サーバーコードの実装:数時間
  • パッケージング&ストア登録:一日
    • アイコンの作成(&inkscapeの使い方勉強):数時間
    • スクリーンショットや各種マーケティングアセットの作成:数時間
    • Androidパッケージングハマり:数時間
    • iOSパッケージングはまり:数時間
この程度の規模であれば、プログラム自体はサーバークライアント合わせて半日ぐらいで全部出来るのだが、それを公開するまでの作業が非常に多い。
  • Titaniumで日本語のアプリ名をつけるとビルドエラーが発生
    • Android版:build.pyを書き換え、utf8デコードと、メインActivityのクラス命名ルーチンにパッチ
    • iOS版:ネットを探したらローカライゼーション用のplistを設置するだけでOK
  • アイコン、スプラッシュスクリーンがたくさん
    • アイコン4種、スプラッシュスクリーン15種、解像度やオリエンテーションに応じて必要
    • アイコンは1024×1024、スプラッシュスクリーンは1024×768,768×1024で作っておいてimagemagickのスクリプトを書いて一発
      • -geometryオプションで’!’をつけるとアスペクト比を無視してリサイズしてくれる
  • ストアに登録する時に必要な画像がたくさん
    • スクリーンショットが面倒くさい

OpenGLを使うならネイティブ 【カレイド卿】

 Available in Android MarketLord Mange - exilis

このアプリは、カメラからの入力をOpenGLのテクスチャに流し込んでエフェクトを加えて描画するというもの。おまけでキャプチャした画像をTwitterに投稿する機能も実装した。OpenGLが出てきた時点でTitaniumとPhoneGapは選択肢から消える。UnityやCoronaなど商用のエンジンは検討の価値あり。しかし、OpenGL自体がそもそもプラットフォームを越えたインターフェースを定義するものであるので、OpenGLを使ってかかれたプログラムをJava<->ObjectiveC(C/C++)間で移植する手間は殆どキーワード置換のみの単純作業だ。

このアプリはAndroid版とiPhone版それぞれネイティブで別々に作成した。

リアルタイムでのカメラストリームの処理は、転送データ量的にもCPU負荷にも重いものなので、ネイティブでの実装は必須だ。Androidの場合はJavaだと十分にパフォーマンスが出ないというのであればNDKの使用も検討すべきだ。このアプリの場合は、カメラからのYUVストリームをOpenGLのテクスチャバッファに流し込み、ちゃんとRGBに変換して表示するというのが重い処理だった。ネットを見た限りではJavaでのYUV->RGBコンバージョンとNDKによる実装しか見られなかったが、いずれもCPU負荷が高い&バッファのコピーが一度余分に発生するので、YUVのストリームをそのままテクスチャバッファに流し込んでYUV->RGBコンバージョンはGPU上で行うことにした。

Android版の開発においては、カメラプレビューが流れてくるスレッド、OpenGLのレンダリングスレッド、UIスレッドをちゃんと意識した設計にしないと非常に不安定になるという点で大分はまった。iOS版はAndroid版が出来てから移植を行ったが、問題なく数時間で完了した。

Androidのカメラは鬼門 【マゴピク】

これはAndroidネイティブで作成した。当初はカメラアプリということもあって、リッチなカメラUIを実装しようと思いTitaniumではなくネイティブを選んだが、ふたを開けてみるとAndroidのカメラ回りのコードは機種依存、バージョン依存が激しく、きちんと実装しようと思うとかなりのノウハウが必要である事が分かった。初期公開バージョンは独自のカメラプレビュー機能を実装していたが、すぐに取りやめ、カメラインテントを呼び出す方式に変更した。おかげでお気に入りのカメラアプリを使ってマゴピクの写真を撮ることなども出きるようになったので、Androidのアプリのデザインとしてはよい方向で落ち着いたとは思う。

しかし、こうなるとネイティブで実装する必要が殆ど無い。Titaniumで充分だ。強いて言えば、マゴピクのAndroid版はユーザー登録等の煩雑な作業を一切取っ払ってシームレスにサービスを使用出きるようにするというこだわりがあったので、それを実現するためのAndroid本体で管理されているGoogleAccountの取得、認証APIを叩けるというのが唯一の言い訳。しかしその程度であればカスタムモジュールを書けば済んだし、それ以外の通信やUIに関してはiPhone版と共通にすることが出来たのになぁ。はっきり言って失敗だった。Titaniumで作るべきだった。

まとまりが無いが、まとまらないので一旦ここで切る

MagoPicまでの長い道のり その二

September 20th, 2011

やはりないもんは自分で作るしかありません。Google+が全部解決してくれそうと思いつつも、とりあえず親孝行と思って作ることにしました。

最低限以下の点をカバーしようと思いました。

  • 簡単手間なし
    • 自動でアップロード
    • 自動でダウンロード
    • 自動で更新通知
    • ユーザー登録(ID/Password)完全不要
  • プライバシー
    • データはクラウドに置くがPGPのような方式でEnd-to-Endの暗号化

End-To-Endの暗号化やユーザー登録なしでのアカウント管理は従来のブラウザをインターフェースとしたクラウドサービスでは出来ません。厳密に言うとJavaAppletやActiveXやJavaScript(非現実的)を使えばEnd-to-Endでのデータの暗号化/復号化はできますが、パフォーマンスや可搬性やトレンドから考えて良策ではないと判断しました。ただ、Native Clientはありです。それを除いてはやはりスマートフォンのアプリという選択肢が有力という結論に至りました。ちなみに、ユーザー登録なしでのアカウント管理というのは多くの方にはあまり馴染みがないかもしれませんが、実はNintendo DSはNintendo Wi-Fi Connectionにおいて2005年の時点で既に実現していたりします。

調べたところ、AndroidのJava(CipherInputStream)、iOSのSDK(CCCrypt)いずれも標準でRSA(RSA1024のDER base64bits)とAES32を使えることが分かりました。サーバー側は基本的にkey serverとしての役割だけでよくて、クライアントで暗号化されたデータのリレーできればいいと思っていたのですが、実装しているうちにサーバーからクライアントに対して公開鍵で署名暗号化したメッセージを送れたらもっとセキュアなネットワークになるなぁと思い、サーバー側でも暗号化をサポートしました。これもGAEに標準で入っているpycryptで一発でした。

次は、ユーザーIDというかキーペアの生成に使うシードをどうするかです。iOSはUDIDやMACを組み合わせればデバイス固有のIDはほぼ確実に取得出来そうな感触でしたが、Androidではノーブランドの機種などもたくさんあり、物によっては再起動するごとにMACアドレスが変わるだとか、どれも同じデバイスIDを持っていたりだとかかなりのカオスっぷりだったので、諦めていろんなセンサーの情報等を寄せ集めたシードから乱数を生成するようにしました。

キーペアの生成と暗号複合、及びクラウドを経由した鍵交換のメカニズムが実装出来ました。さぁ、次はどうやってデバイス間で共有関係を結ぶかです。DSでいうフレンドコードの交換です。いくつか考えて、実際に試しました。

  • 実際にユーザー同士がFace-to-Faceで会える場合
    • Bumpでスマホ同士をぶつけるだけ (試した。基本うまく動く。3Gの載っていない端末では微妙)
    • QRコードなどをカメラでキャプチャ (試した。動くけどダサい)
    • NFC (試していない。対応機種がまだ少ない)
  • 遠隔地のユーザーと共有関係を結びたい場合
    • サーバー経由でメールで公開鍵のURLを送る (問題無し。とてもうまく動く。)
    • フレンドコードのような物を生成し、電話などで口頭で伝える(基本的に技術的にはメールと同じ)

これで、完全に暗号化されたネットワークで自由に静的なデータをやりとりすることが出来るようになりました。ただ、実際のユースケースを考えるとまだ足りないところがあり、下記の機能を実装しました。

  • 秘密鍵が漏洩した場合に、キーを更新して再度暗号化する仕組み (すべてのデータを再度クラウドにアップロードする必要があり。これは非現実的でありシステムの致命的な欠陥)
  • 一つの端末上に複数のアカウント(キーペア)を保有できる仕組み
  • アカウント(キーペア)をデバイス間で引越しさせる仕組み

ここまで実装して気づきました、「俺は家族と写真やビデオを共有したかったんだ」、と。

この仕組みは、めちゃめちゃセキュアで企業が秘密文書のやりとりに使っても問題ないほどの代物なのですが、大量のメディアデータを同時閲覧可能な形でやりとりするには様々な問題を含んでいます。

  • データ転送量、ストレージ消費が半端ない
    • サーバー側でのデータ操作が出来ないため、写真で言うと最大サイズのデータをやりとりして受信した側で行う必要がある(クライアントに大きなストレージが必要)
    • アップロードする際に、サムネイル、閲覧用、オリジナル、とあらかじめ想定した用途の画像を生成し、暗号化した上でアップロードする(データ転送量がかさむ)
    • ビデオのストリーミング再生が出来ない!!サーバーでのトランスコードも出来ない!!(デコーダーに手を加える必要がある。多分SDKの上ではむり。)
  • CPU負荷が高すぎる
    • ストレージを節約するためにOn Demandでデータをストリームするようにすると、その都度復号にかかるレイテンシとCPU消費量がバカにならない
  • Webブラウザ上で見れない
    • なんだかんだいってでかい画面で見たい。暗号化されたデータはブラウザで表示することが出来ないので、馴染みのあるWebインターフェースは作れん。(Native Clientが普及したら作るで)

CPU負荷、Webブラウザ上で見れないというのは時期尚早なだけで時間が解決してくれると思いましたが、データ転送量、ストレージ消費量に関しての最適解は(特にこの様なプライベートなネットワークの場合には)やはりクラウドを使わずにピュアP2Pでやることに帰結すると考えると、現実的な安定性とパフォーマンスを得るためには試行錯誤に相等の時間がかかりそうだと思いました。また、技術的な点ではありませんが、ここまで閉じたネットワークではバイラル効果がほとんど期待できず、サービスとしての継続は困難であると判断しました。

よって、ここまでのコードはすべてブランチを切ってさようならです。これらのコードはもう少し時代が追いついてきたら帰ってくることでしょう。

家族で簡単に安心して使えるスマホ世代の写真共有サービスMagoPic水が低き方へ流れるかのごとくユーザーの今の状況をより意識してよりフレンドリーなサービスを目指して方向転換していくのです。

つづく

 

C/C++のクイズ

September 17th, 2011

こんなコードが同僚から送られてきた。

#include <iostream>
 
int a[] = {1, 12, 13,2};
int b[] = {2, 12, 23,2};
 
int main()
{
  int c = 3[b][a];
  std::cout << c;
  return 0;
}

結果が気になる人はこちらcode pad

以下ダンプ

.globl a
        .data
        .align 4
        .type   a, @object
        .size   a, 16
a:
        .long   1
        .long   12
        .long   13
        .long   2
.globl b
        .align 4
        .type   b, @object
        .size   b, 16
b:
        .long   2
        .long   12
        .long   23
        .long   2
        .text
.globl main
        .type   main, @function
main:
.LFB963:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        movl    %esp, %ebp
        .cfi_offset 5, -8
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $32, %esp
        movl    b+8, %eax
        movl    a(,%eax,4), %eax
        movl    %eax, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZNSolsEi
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc