カメラを買って以来すっかり写真を撮るのに夢中になってて、その上最近はリスなんか飼っちゃったりしてて、んまーめっきり週末プログラマーしてなかったもんで、しまったこっちのブログ全然書いてないやーと思って久々に見たら最後の記事が去年の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に親近感を感じる。