T

【Titanium Advent Calendar 2011:18日目】Titanium MobileでAndroid

この記事は、@astronaughtsさん企画の「Titanium Advent Calendar 2011」向けに書いています。
12月1日~25日まで毎日誰かがTitanium Mobileについての記事を書いていくというイベントです。


最近、よくいろんな方から「Androidの案件を受注してしまったけど、Titaniumで大丈夫だろうか」と相談を受けるので、そんな人たちを少しでも慰めるためにそれをテーマに書いてみたいと思います。

さて。AndroidでTitanium Mobileを使う場合の鉄則がいくつかあります。面倒くさがりな方のために先にまとめておきます。

【鉄則1】iOSを基準に仕様を作ってはいけません
【鉄則2】試験用にIS03とタブレットは必ず用意しましょう
【鉄則3】anyDensityでごまかせると思ったら大間違いです

それから、開発する上で外してはいけないポイントがいくつかあります。

【ポイント1】シングルコンテクストにしないとまずいことが起きる
【ポイント2】Javaは友達、怖くないよ

では、ざっとみていきましょう。

【鉄則1】iOSを基準に仕様を作ってはいけません

クロスプラットフォームの開発を楽にするのがウリのTitanium Mobileですが、iOSでは動作してもAndroidでは実装されていない機能がたくさんあるので、iOSのKitchenSinkを見ただけで仕様を決めると後で痛い目に遭います。考えてみれば当たり前の話で、これだけ大きく異なるiOSとAndroidの違いを抽象化しようとしても、どうしても漏れるものがあるのは火を見るより明らかです。漏れのある抽象化そのものですから。

最初に作ったAndroid用アプリ「AKB48大島優子 ボクの彼女」に取りかかったときには、リモートのムービーファイルを再生する機能がAndroidには実装されていないことを知りませんでした。Intentを起動して端末にインストールされた動画プレーヤーを起動して解決する方が断然楽なのでやり方はないか探しましたが、結局いったんChromeを起動させて、そこから動画プレーヤーを起動させる方法しかありません。でも、それだとムービーの再生が終わってバックボタンを押すと真っ白なブラウザの画面に戻ることになってしまいます。そんなのを作ってお金をもらってたら、詐欺ですよね。

最初の一歩

そこで、リリースの際はTitanium Mobileのソースコードに手を入れて、直接動画プレーヤが起動するように変更して対応していたのですが、その後モジュールが書けることがわかったので、今ではMIMEタイプを渡すと適当なIntentが起動するこちらのモジュールを使って回避しています。とっても簡単なものなので、PDFビューワなどにも応用できると思います。

iOSは楽だったなあ

いずれにせよ、Android案件ではこんな苦労はしょっちゅうです。また、特に困るのがUI設計の問題です。例えばiOSでは上の画像みたいにナビゲーションバーが表示されて、その左側には何もしなくても戻るボタンがあり、また必要に応じて画面下部にタブを表示する、みたいな画面設計になるのが一般的なのですが、Androidだとナビゲーションバーというものは存在せず、タブも画面上部に並んでしまうので、どう転んでもiOSと同じものにはなりません。Android向けのアプリは、バックボタンやメニューボタンで遷移することを考慮する必要があります。なんといってもiOSと比較するとタッチセンサーの感度は微妙だし、小さな画面で正確にボタンをタッチするのは面倒くさいので、バックボタンを押しまくるユーザも多いのです。しかし、世の中というのは不思議なもので、何をどう勘違いしたのか、それでもUIを全てiOSと同じになるようにしろと要求する人が後を絶ちません。しかもその数は開発者の予想を遥かに越えてたくさんいることが最近の調査で判明しています。その手のプロジェクトは、iOSが用意してくれるボタンなどの機能をAndroid用にわざわざ画像でパーツを全部作って対応しようとするなど、無駄なコストがたくさんかかってしまっているので遠くからでもすぐにわかります。テストも倍以上になり、プログラムは複雑になり、それでいてiOSの偽物のような外観の、全く使用感の異なるパチモノが出来るだけなのですから、どうやっても結局誰も得をしません。WindowsとMacでもそうですが、UIを複数のOSで共通化しようというのはほとんどが無駄な努力だと思います。たいていのユーザはどちらかでしかそのプログラムを使いません。Microsoft Officeならいざ知らず、あなたのアプリをiOSとAndroidの両方で使うというユーザなんて、あなたが雇ったテスター以外は誰もいないでしょう。だから、もしあなたがiOSとAndroidで共通のUIのアプリを作ろうと頑張っているのなら、まあ多少はいいですが、そろそろいい加減に目を覚ましてください。

【鉄則2】試験用にIS03とタブレットは必ず用意しましょう

Androidマーケットにアクセスした端末に表示されるアプリは、その端末の画面サイズに適したものだけです。Titanium Mobileではデフォルトでたしかこんな感じで指定されていたかと思います。

  <supports-screens 
    android:smallScreens="false"
    android:normalScreens="true"
    android:largeScreens="true"
    android:anyDensity="false"
  />
  

smallScreensに非対応にするとIDEOSみたいな端末ではそのアプリは利用できないことになります。シェアからすると別に構わないと思いますが、特殊な用途がある場合などは注意する必要があります。また、一般的な端末にも要注意なものがあります。携帯サイトの開発を経験された方なら誰もが開発者泣かせの癖のある端末をいくつか苦々しい思い出と共に心の中にしまってあるかと思いますが、スマートフォンでもその手のやつは根絶されたわけでは決してありません。代表的なのはauが只同然で配っていたためユーザ数の多いIS03です。こいつはなかなか癖のある端末で、例えばソフトキーの高さを除いた画面サイズを返してくれないのでレイアウトの調整に苦労します。またSnapdragon第一世代らしいハードウェアスペックの低さは想像以上です。

余分三兄弟

⇨ココ大事!

それから、Ti.UI.createAlertDialog()を使うとき、どうしてもダイアログが出ないことがあります。そんなときは、ダイアログの前にTi.UI.createWindow()してそのウィンドウにダイアログをaddしてみましょう。IS03のときだけに起きる現象です。内緒にしてぼくだけの競争優位にしようと思っていたのですが、口が軽いので白状します。

また、タブレット機の存在も非常に厄介です。画面を縦または横固定で使うアプリを作成すると、作る側のコストはとても低く抑えることができるのですが、普通のスマートフォンならそれだけで使い勝手に悪影響が生じることは滅多にありません。縦固定で使いにくいアプリなら大抵は横でも使いにくいからです。しかし、タブレットは重量があるので寝転がって使ったりする際に手で支えるのも大変なため、縦でも横でも好きなように変更できるのが望ましいです。ご存知の通り、これは開発者にとって地獄への道まっしぐらなので、せめて試験用端末だけでも用意しておきましょう。

【鉄則3】anyDensityでごまかせると思ったら大間違いです

iOSとAndroidの一番大きな違い(二番目は飲み屋でそこら辺の人に自慢できるかどうかの違いです)は複数の画面サイズに対応しなければいけないことです。以前はAndroid SDKのサイトに対応する画面サイズの一覧があったのですが、デバイスが増え続ける昨今、いつの間にかそんな資料は見かけなくなってしまいました。

一応、Android側でもanyDensityという仕組みを使ってこれに対応しようとしています。

「anyDensity = false というのは、そいういう対処をしていないので、コード側で設定しているピクセル値を解像度に応じて調整してください」ということです。 この場合、コード側で setWidth(10) とした場合と XMLで android:layout_width=”10dip”とした場合の大きさは端末の解像度によらず同じになります。

Y.A.M の 雑記帳

Titanium Mobileの場合、anyDensityはデフォルトでfalseです。つまりdipで指定したのと同じことになります。ところが、そもそも端末の縦横サイズがまちまちなので、複数のディスプレイで同じ画面構成にしようとすると大変なことになります。

この辺、資料を読んでもいまいちよくわからないんですよね。たぶん、書いてる人が悪いんだと思います。

赤い部分をタッチできるようにしたい

例えば上の画像みたいなアプリで、赤く塗ったところをタップするとアクションが実行される、という機能を実現したい場合、その部分に透明なViewを置いてタッチイベントを検知する、みたいな作り方でなんとかなります。しかし、その範囲を指定するのに、端末の縦横サイズがバラバラではうまく指定することができません。

そこで、例えばこんな対応方法を考えてやってみたところ、結構うまくいきました。まず、背景となるviewにはbackgroundImageとして横幅が端末の横サイズに拡大/縮小されることを前提に、上下に余分な部分を残しておきます。次に、各パーツを以下の手順に従って配置していきます:

・画面中央の位置を取得する
・画面の横幅を10または100分割して1つの単位とする(例・1 andro_scale)
・各パーツのY座標は中央から何andro_scale上/下かで決める
・各パーツのX座標は左/右端から何andro_scale右/左かで決める
・各パーツのwidthやheightも何andro_scaleかで指定する

pxとかdipとかもういいから自分で単位を作ってしまおう

このやり方だと、どんなサイズの端末でも同じ距離の指定方法で動作しました。もちろん画面最上部や最下部に張りついたパーツなどは素直にtop = 0やbottom = 0とすればいいでしょう。

1 andro_scaleをどれくらいにするかで勝負が決まるところでもあります。あんまり大きいとタブレットで画面が大きく(解像度が低く)なった途端にズレが目立ったりします。

そういう意味では鉄則2の試験端末の用意は必須ですね。

【ポイント1】シングルコンテクストにしないとまずいことが起きる

最近ではAppcelerator社から出ているアプリのソースでも使われていることが話題になっていた、シングルコンテクストで記述する方法ですが、Androidだとおそらくこれをやらないととてもじゃありませんがうまくいかないと思います。例えば

・HTTPClientからPOSTリクエストが送信されない
・コールバック関数がたまにしか実行されない

といった症状にお悩みの方は、シングルコンテクストで記述しないと改善されません。その際、記述が冗長にならないようにいろいろと気をつける必要があるのですが、個人的にはsyrupという名前で公開されたこの手法がとても参考になりました。そして全部のアプリをCoffeeScriptで書き直しました。残念なことに、syrupという名前のCoffeeScript的Lispというのもgithubにあるので、名前については再考の余地がありますが、とても分かりやすいのでオススメです。これに、例えばデータベースの扱いやIO関連のユーティリティを用意しておけば便利です。いつも用意しているライブラリでは、例えばデータベースの扱いは下のようなモデルを用意するだけでテーブルがなければ作成、初期データの投入(あれば何もしない)まで勝手にやってくれます。

syrup.namespase 'MYAPP.Model', (exports)->
  Model = syrup.use 'MYAPP.Model.Model'
  class User extends Model
    init:()->
      this.property "id", "integer"
      this.property "name", "text"
      this.property "email", "text"
      
      this.init_data.push {id:1, name:'ヒューバート・セルビー・ジュニア', email:'last_exit@to.brooklyn.org'}
      this.init_data.push {id:2, name:'ダグラス・アダムズ', email:'adams_d@42.org'}
      
      super
    exports.User = new User('users')

簡単でしょ?これでusersテーブルが(まだ未作成の場合は)作成され、2行のデータがINSERTされます。

CoffeeScriptで作るノウハウがもっと溜まってきたらもっと公開して還元していこうと思います。

【ポイント2】Javaは友達、怖くないよ

ここまで頑張ったら普通はどんな残酷な悪魔でも許してくれそうなものですが、Androidの開発ではそうは問屋がビジネスしてくれません。実はこれまでJavaで何かを作ったことは一度も無い(一応、教養として一通りいじったことはある)のですが、できればこのまま一生避けて通りたいと思っていました。でも、Ti.UI.createToolbar()を指定しただけでこんなものを見せつけられるようでは余裕こいてばかりもいられません。

梶芽衣子もカンカンですよ

Androidでは実装されていません、だと?

パンがないならお菓子を食べるのが真の王妃というものです。じゃなかった、ないならないで作ってしまうのが開発者というものです。そんなとき、ヒントになるのが、Titanium MobileのAndroid用モジュールのソースコードです。実はAndroidのカメラなどもモジュールとして作られているので、そこで使われているテクニックは自作モジュールでも利用できます。Intentを起動してコールバックを指定するとか、Intentを起動するだけとか、ごく普通に関数を実行するとかのサンプルはこちらをどうぞ。

そろそろ疲れてきたので仕事に戻りますが、他にもAndroidという二番手ならではのbkはたくさんあります。みなさんも、見つけた悪しき知恵は出来るだけウェブで公開して、同じ苦労を分かち合いましょう。そうすれば、プラットフォームとしてのTitanium Mobileの発展に貢献することができます。このプラットフォームが成長して機能が改善され、また良い人材が引き込まれてくるようになれば、この先もっといいことが待っているはずです。たぶん、きっと。

Posted by on 12月 18, 2011 in Android, Titanium

コメントを残す