As Sloth As Possible

可能な限りナマケモノでありたい

タグ:Android

カメラを買って以来すっかり写真を撮るのに夢中になってて、その上最近はリスなんか飼っちゃったりしてて、んまーめっきり週末プログラマーしてなかったもんで、しまったこっちのブログ全然書いてないやーと思って久々に見たら最後の記事が去年の8月26日とかになってた。これはひどい。少しは書かないと。

久々の更新なのに大分周回遅れ感のある話題だけど、Androidアプリ内でFlashを表示するときに面倒な感じだったのでメモ。Flash PlayerがPlay Storeから消えてから大分経ち、時代はAndroid 4系だと言うのに今更Flashかよーって自分でも思ってるしなんならこのメモ二度と見返すことが無ければいいなーと心から願いながら書く。あれなんです。世の中には大人の事情が一杯あるんです。2系とか爆発しろって思ってるけど俺の端末はGalaxy SII LTE 2.3.6です。俺も爆発しろ。

Androidアプリ内でFlashを表示するには単純にWebViewを開いてswfなりswfをロードするタグが書いてあるhtmlなりをloadUrlすればいい。ただしデフォルトだとプラグインが無効になっているので、WebSettingsを弄っておいてやる。

package jp.faultier.sample;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class FlashActivity extends Activity {
    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.flash);

        String url ="file:///android_asset/hoge.html"; // Flashを表示するタグが書いてあるHTML

        webView = (WebView) findViewById(R.id.webView);
        webView.getSettings().setPluginsEnabled(true);
        webView.loadUrl(url);
    }
}

とりあえずこれで望み通りassetsに放り込んだhoge.htmlが表示され、その中で指定されたswfが読み込まれる。ところがここで罠にハマる。このままだとFlashは止まらなくなる。

例えばあなたがFlash黄金時代のFlashを蒐集し、密かに通勤中に眺めるのが趣味だったとしよう。もしそれが「さくらんぼキッス 〜爆発だも〜ん〜」をBGMに1さんと8頭身が舞い踊るやつだったらこっそり俺に送って欲しい。たぶん違うと思うけど。

で、にやにやしながら心の中で「あれれ?おかしいなこのドーキードキーはー 君の腕の中であふーれだすー」と歌っていたあなたは、たまたま電車を降りたところで上司にばったり出くわすかもしれない。慣れた手付きで電源ボタンを押してポケットにAndroidをしまうだろう。そして上司にあいさつをするだろう。でもその最中もイヤホンからは「すきすきすき すーきすきすき すきすきすき すーきすきすき(キュンキュン!)」とか流れっぱなしなのだ。ついでにいい加減暑くなってきてるというのに、ポケットの中には今にも発火せん勢いで発熱しているホッカイロが出来上がっている。なんなんだその罰ゲームは。

これを避けるには、onPauseのタイミングでWebViewの動作も止めておいてやらないといけない。そんくらいなんでやっといてくんないのと思うんだけど。

public class FlashActivity extends Activity {
    private WebView webView;

    ...

    protected void onResume() {
        super.onResume();
        try {
            WebView.class.getMethod("onResume").invoke(webView);
        } catch (...) {
            ...
        }
    }

    @Override
    protected void onPause() {
        try {
            WebView.class.getMethod("onPause").invoke(webView);
        } catch (...) {
            ...
        }
        super.onPause();
    }
}

やっと止まった。これなら上司とばったり出会っても、上司が昔Fla板に常駐してたかどうかを気にしないで済む。ちなみにリフレクションで呼んでるのはこのAPIが公式に実装されてるのはAPI Level11、つまり3.0以降なので、2.2や2.3では必ずしも実行できないから。とは言えGalaxy SIIでは一応これで止まった。めでたしめでたしだ。

さて安心して上司と雑談してるあなただが、途中で困ったことに「さっきスマホで何見てたの?」という話になる。また面倒な、と思いながらも、光速の異名を持ち重力を自在に操る高貴なるAndroid騎士のあなたはロック解除からコンマ数秒でアプリを終了させれば良いだろうとポケットから出し、電源ボタンを押す。するとどうだろう、ロック画面が表示されているというのに、端末からは「(ほんとはね…ずっと好きだったの…ナイショだよ…)」とか囁きが聞こえてくる。おかしいなー、ビルの中は空調効いてるのになんかすっごい汗出てきたなー。うふふそうかー、これ冷や汗って言うんだー。

はい。どうしてそんなふざけた実装になってるのかは知らないけども、Androidはロック画面が表示された時点で既に最前面のActivityのonResumeが呼ばれてしまっているので、上記のWebViewへのonResumeも走り、Flashは再び走り出してしまっている。落ち着いて聞いて欲しいのだけど、この挙動はAPI Level16、4.1になるまで直ってない。Androidさん的にはつい最近まで「スリープ状態からのアプリの再開=ロック画面の表示」だった。うん、本当に意味が分からない。

これを避けるには、onResumeじゃなくてonWindowFocusChangesを使うように変える。ロック画面表示した時点ではActivityはresumeしているが、Activityのwindowは表示されていないので、フォーカスしていない。ついでに、逆にフォーカスが外れたら画面が見えてないってことだよね、と考えれば、このメソッドだけで完結しそうだ、ということでonPauseの分もこっちに持ってってみよう。

public class FlashActivity extends Activity {
    private WebView webView;

    ... // さっき追加したonResumeとonPauseの処理は削除

    @Override
    public void onWindowFocusChanges(boolean hasFocus) {
        if (hasFocus) {
            try {
                WebView.class.getMethod("onResume").invoke(webView);
            } catch (...) {
                ...
            }
        } else {
            try {
                WebView.class.getMethod("onPause").invoke(webView);
            } catch (...) {
                ...
            }
        }
        super.onWindowFocusChanges(hasFocus);
    }
}

どうやら今度こそうまく行ったようだ。電源ボタンを押せば音声は止まるし、ロック画面表示中も止まっているようだけど、ロック解除すればちゃんとまた動き出す。完璧だ。

そう思いながらの帰り道、懲りずに蒐集したFlashたちを眺めながら乗る電車は満員の埼京線、屠畜場に行く肉牛の方がもう少し人道的な扱いを受けるんじゃないかなー?うん?人道じゃなくて牛道?とか考えてる最中、さっきまで狂ったように連呼されていた「みこみこナース!みこみこナース!」の声が聞こえないことに気付く。見ると、画面ではフルスクリーン表示されているFlash。そこまではいい。多分、車内で圧縮されてるときにうっかり画面を長押ししてしまったのだろう。AndroidではFlashを長押しすることでフルスクリーンモードに移行することができる。のだけど、明らかに再生は止まっているし、押してもつまんでも舐めても全く反応してくれない。一体何が起きたんだ。

これは一個前の変更で、onWindowFocusChangesでwebViewのonPauseを呼ぶようにしてしまったせいだ。残念なことに「通常表示からフルスクリーンモードへの移行」も「WebViewを表示しているActivityのWindowのフォーカスが外れた」ことになるので、onPauseが走り、一切の動きが停止する。ちなみにバックボタンで戻るとまたフォーカスするのでonResumeが走り、何事も無かったかのように再生が再開する。

折角フルスクリーンにできるというのにこれは大変がっかりな事態である。結局、一部を元に戻して、最終的には次のようにする。

  • onPauseでwebViewのonPauseを呼ぶ
  • onWindowFocusChangesで、hasFocusがtrueの場合のみ、webViewのonResumeを呼ぶ

これで一応、電源ボタン押したりホームボタン押したりすると再生が停まるし、ロック画面を表示しても勝手に走り出さず、ロックを解除すると再開し、フルスクリーンモードもちゃんと動く、という動きになる。なんかえらく回り道した。途中の懐しFlashのくだりとか全く要らんかった。懐し過ぎてなつみSTEPとかnum1000とか観てしまった。全然関係ない話である。

とりあえず、Androidの2系速やかにシェアが下がるといいなぁ。なんなら今2系の端末はボタンひとつで一斉に爆発するといいなぁ。俺のも爆発四散するけどこの際それでいいから消えてなくなってくれないかなぁ。

これで万事解決や、と思ったんだけど、フルスクリーンモードだと一旦閉じて復帰してもonWindowFocusChangesが呼ばれない(だってActivityの方は裏に隠れたままだから)ので、Flashの再生が再開しない。結局「ロック画面出てるのに再生が再開しちゃう」のと「画面に戻ってきてもフルスクリーンを解除するまで一切反応しない」のだと後者のが深刻なので最初のonResumeのとこまで戻した。はー。

完全に余談だけどブラウザもこの問題解決できてないようで、デフォルトブラウザとBoat BrowserはonResumeで、Opera Mobile ClassicはonWindowFocusChangesでやってるらしい挙動だなってのが分かった。Operaに親近感を感じる。

「スマートフォンについて何か喋ってよ」というかなり大雑把なネタ振りをされたので、第五回ライブドア・テクニカルセミナーでこんな感じの話をしてきました。

あとで動画や資料は公開されるはずですが、大変申し訳ないことにテンパりまくって見るに耐えない感じになっちゃってると思うので、一応何を言いたかったのかを補足しておきます。

iOSとAndroidは違うものだという話

開発環境の違いについてはまぁいいとして、例えばアプリの設計思想。iOSでは「まずアプリケーションという大きなプログラムがあって、その中で画面を表示したり通信したりしてる」って感じの構成になってるのだけど、Androidの場合「画面とかデータ管理機能とかそういう独立した部品があって、それらをまとめたものに名前を付けてアプリケーションという風に見せる」という感じになっていて、後者の方が若干まどろっこしい感じはある。一方でアプリ間でのやりとりについて考えてみるとiOSでは「openURLで他のアプリを起動する」とか「Keychainだとかアルバムだとかを使って(限定的に)データを共有する」とかその程度に留まっているのだけど、Androidの場合はOSが提供する機能も同じアプリ内の機能も他のアプリの機能も全て「部品」という形になっているので、きちんと設計しさえすればデータや機能をシームレスに連携させられて大分可能性が広がる。この考えかたの違いはアプリの作り方の違いにモロに出てくる。

それから、デバイスの違い。iOSの場合はiPhone/iPod touch系とiPad系の二系統しかなく、どちらもApple一社が開発しているので、OSや周辺サービスも含めたプラットフォーム全体のコンセプトから良くも悪くも外れない。一方のAndroidはと言うと、Googleが主導してはいるものの、端末メーカー各社それぞれがOSにカスタマイズを加えてたり、独自のハードウェア(キーボードが付いてたり、先行してFeliCa対応してたり、裸眼立体視ディスプレイが付いてたり、そう言えばゲームのコントローラが付いてたりするようなのも出るんだっけ?)を搭載してたり、小さな画面のものがあったり、横固定だったり、多種多様だ。ハードウェアの違いがソフトウェアに影響を与えないわけが無く、「使い易いアプリって何?」って問いの答えはiOSとAndroidでは当然違うし、Android端末間でも異なってくることもある。

ここまでは技術者視点だけど、当然ビジネス的にも文化的にも異なる展開をするだろうね。と、いうことを考えたら、アプリの企画や開発をするのに「iOSとAndroidは違うもの」って認識は当然持っておく必要はあるだろう。

iOSアプリとAndroidアプリはどっちが作り易いか

率直に言うと、最初のとっつき易さに関して言えばAndroidの方が圧倒的に上。僕の元々のお仕事はPerlやRubyでWebアプリケーションを開発することで、今もアプリ作りのかたわらWebやってるのだけど、そういう人が、ObjCとXcode/IBでアプリを作りクローズドなプラットフォームでお仕事するのと、JavaやJVM上で動く言語とEclipse+ADTでアプリを作りオープンなプラットフォームでお仕事するのとで、どっちが大変かなんてのは言うまでもない。

で、だからAndroidのが素晴しいって話になるのかというとそれはそれで違うんだよってのが、無茶苦茶RTされまくってハッシュタグを埋めてしまった件のスライド(本当にすいませんでした!)の意図。「最初は薔薇色の世界に見えるAndroidも作り続けて行くと段々難しさに気付いていく」「最初はとても理解できないと思ったiOSも作り続けて行くと良さに気付いていく」「だからつまり、最初のとっつき易さだけでどっちかが圧倒的に優れていると言えるわけではない、最初のハードルを越えたら別の問題が見えてくる」っていうのを(一部の人には)視覚的に分かってもらえるように、と思ってあのイラストを入れたんだけど、前日にまどか☆マギカの8話を観て「正直やりすぎた」と反省はしました。あのスライドを見て「きっとこの人はAndroidよりiPhone派で、あと赤い子が好きなんだろう」ってコメントしてる人がいて、いやうん割とその通り(「杏子に『食うかい?』って言ってもらう」ことを願って白い獣と契約しかねないです)だけど、いくらなんでもアレに例えなきゃいけないほどAndroidを悪くは思ってない。Androidが孵す卵はもう少し夢のあるものだって信じてますし、iOSのあの林檎を輝かせ続けるために何が犠牲になってるかに思い至らないわけでもないですし。

魔法少女の話は置いといて、じゃあその「別の問題」ってなんなの、という話でいうと、まず一つはマルチデバイス/OS対応。Androidは自由度の高さが最大の売りだけど最大の欠点でもあって、Androidに取り組んでるところは軒並「端末やキャリアによってお約束事が違い戸惑う」とか「バージョンアップしない端末があってどのバージョンのOSを対象にするか迷う」とかそういうことに悩んでいる。正直、Android開発に関わってない人が想像してる以上にデバイスごとの差異が大きいし、今後収束するどころかもっと個性的なものが増える方向に進むだろう、だってそれが一番の売りなんだから。それに真面目に対応していくことの面倒臭さは、iOSアプリの最初の学習コストを上回ると思う。iOSだってもちろんiPhoneとiPadで両対応ってのは凄く大変だし、最近はケータイだと思って買っていくので家にパソコンも無くアップデートしないみたいな人も増えてると聞くので、その問題からは無縁じゃないんだけど、それでも現時点ではAndroidよりは幾分マシだ。

それから、パフォーマンスチューニング。いくらスマートだスマートだって言ったって所詮はモバイルデバイスなので、タイトなリソースをやりくりしていかなきゃいけない。今まで組み込み機器やローレイヤーなところの開発をしてた人からすれば「何を贅沢な」「ゆとりめ」と冷笑されそうな話だけどさ。スマートフォンアプリに求められる「リッチさ」はどんどん上がって行く一方だけど手持ちの戦力は高性能な戦闘機と言ったところで、その気になれば空母の艦隊を投入できるサーバサイドの開発とは全然違う。これに関して言えばiOSでもAndroidでも同じで、そういう段階に至ったらどっちにしてもそれなりに「難しい」。

クロスプラットフォーム開発に関して思うこと

きっと伊藤直也さんが肯定的な文脈で言及するに違いないと思って(実際してた)内心ビクビクしながら話してたんだけど、どうしても言わざるを得なかったので言ってしまった。一つのコードで両方のプラットフォームに対応するのは、現時点ではあまり現実的じゃないと思う

もちろんアリだろうと思ってる領域もあって、例えばゲームエンジンみたいなものは、凄く効果が高いと思う。最近は一般的な携帯ゲーム機向けのものと同じくらいのクオリティの高いゲームがどんどん増えてるけど、ああいうのは大概UIから自前で作ってしまうし、中のロジックもiPhoneだからAndroidだからということも無いだろうから、細かい差異はライブラリに吸収させちゃうのが定石だろう。それから、コアな部分をHTML5+JSで作って、ブラウザ上でできないことやパフォーマンスが必要なことをやらせるためにネイティブのUIでWebViewをラップする、というハイブリッド戦略も良いと思う。SNSとかブラウザゲームとかだと期間限定のイベントがあったり速いペースで機能改善していったりする必要があって、それで言うとアプリ開発して(iOSならAppleの審査を通して)ユーザにアップデートしてもらう、というプロセスを通すと遅すぎて全然上手くいかない。うちで作ってるアプリだとロケタッチとかは部分的にそんなアプローチをしてるし、今後そういうアプリも増えると思う。

懐疑的なのはトランスレータとかミドルウェアとかの話。Titaniumとかが盛り上がってるのはもちろん知ってるし追ってるけど、最近だと「一つのコードで両方のプラットフォームに対応」みたいな話より「iPhone(or Android)のアプリ開発を楽にする」って文脈で語られることのが多くなってきてるかなーという印象を受ける。まだまだ発展途上でどっちかのプラットフォームのサポートは手厚いけどもう片方は追いついてないだけで、今後は次第に改善していくだろう、ってところもあったりするんだけど、それとは別に個人的には「これだけ"違い"があるんだから、無理して同じ扱いをするよりそれぞれに合ったやりかたをした方がいいんじゃないか」って思ってる。特にツール系のアプリはそうで、それぞれにUIのお作法があるのでちゃんとそれぞれの特性を活かしたUIにした方がよくて、そうなったときに内部のロジックだって無理に同じにしておくより別プロジェクトにしておいた方がかえってメンテしやすかったりする。将来的にどうなるかは分かんないけども、今現在両方のアプリを作ってての実感としてはそんなところ。

というところまで書いて

そんな話が全然出来てなかった自分の話下手っぷりに絶望したのでもう少し練習しないとな、と思いました。あたしって、ほんとバカ。

謝辞

キュゥべえドロイドくんはSENさんに、杏子の林檎はニチロさんに描いてもらいました。素敵なイラストありがとうございました!

↑このページのトップヘ