降りつぶし.net~同期するWebアプリ・スマホアプリの開発・運用~

第6回(最終回) Android用アプリケーション「降りつぶしroid」の開発

この記事を読むのに必要な時間:およそ 7 分

実装のポイント~Android開発という苦闘

ここからは実装の話となります。

i降りつぶしは,iOSが提供する機能をほぼ素直に使って実装できました。ややこしい実装はあまりせずに済んでいます。

しかし,降りつぶしroidではそうはいきませんでした。

よく「Androidはいろいろな解像度やデバイスがあるので対応が大変」と言われます。画面を作り込むホビーアプリなどはそのとおりだと思います。ただ,降りつぶしroidはぶっちゃけ,ビジネスアプリみたいなものなので(笑⁠⁠,その問題はほとんど意識しなくて済んでいます。アイコンの数だけは莫大になりましたが,これはデザインを無償でお願いしている伊藤壮氏(@souitoh)が泣いたという話で(すみませんでした⁠⁠,筆者自身の苦労にはなりませんでした。

それでも起こる問題は,提供されている機能にあるさまざまな制限や癖によるものです。スレッドの扱い,アニメーション中のタップ問題などは他のOSにも存在しますが,アクティビティやフラグメントのライフサイクルなどはAndroid独特のものです。

以下,筆者が検索した限りでは解決策があまり出てこなかった,いくつかのポイントに絞って挙げます。

実装のポイント1~データベース更新問題

まず最初の問題は,データベースの扱いです。

iOSでのSQLiteデータベースの初期処理としては,第5回4ページで触れたとおり,⁠データベースファイル自体をアプリが抱えていてそれを端末のデータとして上書きする」が定番です。

しかしこの方法には,既に存在するデータベースのアップデート時に発生するいくつかの問題があり,利用しないほうが無難と考えています。

まず,一部の機種において,データベースディレクトリへの書き込みに問題があるようです

また更新する場合に,いったんデータベースを開き,ユーザバージョンを確認し,更新が必要と判断した場合の処理に自信が持てません。当然ながらデータベースをいったん閉じ,その後データベースファイルを上書きし,改めて開く,という処理となりますが,AndroidのSQLiteライブラリはJNIで実装されており,close処理に関する問題も指摘されています。筆者がAndroidおよびSQLite3のソースコードを読んだ限りでは,今のところは上記処理には問題がなさそうですが,今後,ネイティブ層でのクローズ処理が終了しないうちにファイルの上書きが始まるような実装が登場しないとも言い切れません。

Androidはデータベースのセットアップ用に,SQLiteOpenHelperというヘルパークラスを提供しています。このクラスはコンストラクタにSQLiteのユーザバージョンを渡すと,アップデートが必要な場合にonUpgradeコールバックが呼び出されます。そこでSQL文を実行することで,データベースを更新してくださいね,ということです。

一般的なアプリなら,やはりこれを使っておくべきだ,と考え,アプリはデータベースファイルを直接抱えるのではなく,データベース構造を作成するSQL文を抱えることにしました。

しかしここで別の問題が発生します。降りつぶし.netのデータベース作成SQL文は最新バージョンで2.3Mバイトあるのですが,Androidアプリが抱えられるデータファイルは最大で1Mバイトなのです。

そしてZIPで圧縮するとこの制限を超えられますが,圧縮されたZIPファイルをアプリの中で解凍するための便利なクラスはありません。自力で解凍です。

実装のポイント2~データベースをサービス化する

先にも触れたとおり,データベースアクセス(と同期)は,Androidのサービスとして実装しました。

サービスは,アクティビティやフラグメントのライフサイクルとは無関係に,バックグラウンド実行を行うための仕掛けで,iOSになくてこちらにある,数少ない便利な機能の中の1つです。

サービスの利用で,前述のとおりに手間がかかるデータベースへの接続,また確実に多大な時間を要する同期機能を,安全に実行することができます。

ただしAndroidでは,サービスにもライフサイクルがあります。アクティビティやフラグメントから接続されているサービスやフォアグラウンドにあるサービス(ステータスバーに通知を送っている状態)は生存できますが,それ以外は常にシステムから強制終了される運命にあります。

そのことも含め,データベースクエリおよび同期実行を管理するキューを,PriorityBlockingQueueクラスで実装しました。データベースはサービス内で立てたワーカスレッドで動作させ,キューを使ってUIスレッドからリクエストを受け付けます。

図12 データベースサービス内でのリクエスト処理

図12 データベースサービス内でのリクエスト処理

もちろんその処理は単純なFIFOでよいため,キューは単純なBlockingQueueでもよいはずです。しかし,以前のアクティビティ主体の実装では,何らかの状態でアクティビティがすべて一時的に消滅し,結果としてアプリは動作中なのにサービスが終了させられてしまう可能性がありました。

その対処として,⁠すべてのサービス接続がなくなった」状態から10秒待ち,それまで再接続要求がない場合は自爆する,という設計を行ったのですが,その終了指示をワーカスレッドに送るためには単純なフラグでは不可能となり,ScheduledExecutorServiceによりキューに再優先で「終了せよ」というリクエストを行うこととしました。

それでも,これは優先順位が2種類なので本来ならBlockingDequeで済むのですが,なんとAndroidのBlockingDequeは2.3からの提供なのです。2.2に対応するために,わざわざPriorityBlockingQuereを使い,UIスレッドで正のシリアルナンバを発行して優先度を設定,終了リクエストは優先度0,という実装になったのです。

実装のポイント3~フラグメント永続化

3.0以降のAndroidの画面は,フラグメントという単位で構成されます。システム上のアプリの画面単位はアクティビティのままですが,1つのアクティビティの上に1つ以上のフラグメントが乗り,そのフラグメントをAndroidがスタック管理してくれます。もともとは,スマホだと2画面切り替えしていたものを広いタブレットの画面では1画面で同時に表示したい,というようなニーズに応えるための仕組みでしたが(2つのフラグメントを,スマホでは切り替えて表示し,タブレットでは同時に表示する⁠⁠,それ以外の大きなメリットが「永続化」です。

具体的には,Fragment#setRetainInstanceメソッドを,引数にtrueを指定して呼び出すだけなのですが,これにより「言語切り換えやフォント切り換え,またフラグメントの切り換えなどの際に,インスタンスが保持するデータが全消滅する」という,Android開発初心者が最も戸惑う問題が回避されます。

ただしこれを利用する場合には注意すべき点も多々あり,ライフサイクル問題への充分な理解は変わらず必要なのですが。

図13 フラグメント永続化によるデータベース処理

図13 フラグメント永続化によるデータベース処理

降りつぶしroidでは,データベースクエリや同期を実行するすべてのフラグメントは,共通のベースクラスDBAccessFragmentBaseを継承して実装されています。このクラスでは,onCreateで永続化を設定し,データベースサービスへの接続および結果通知を受け取る準備をし,onDestroyでサービス接続を解除します。これにより,そのインスタンスがある限り,データベース接続や更新通知を必ず受け取れる,という状態が保障されました。一方で,onDestroyViewコールバックで表示に関わるメンバにすべてnullを設定して参照を解除しなければならない,という,前近代的な対策も必要です。

著者プロフィール

よねざわいずみ

合資会社ダブルエスエフ代表社員。学習塾講師やら芸能ライターやら劇団主宰やらいろいろ経て現在はよろず請負プログラマ。最近の開発はPHP,JavaScript,Java,MTプラグインなど。お仕事随時募集中。

Twitter:@yonezawaizumi

鉄道旅行ブログ:http://feelfine.blog.izumichan.com/