約二年半ぶりにAndroid向け喫煙管理アプリSmoking Noteのアップデートを行いました。Google PlayやAndroid OS、開発環境などが変わったことにより、単純にソースコードを修正してビルドしアップロードしておしまい、とはいかず予想していたよりも工数がかかりました。せっかくですので今回のアップデート作業についてまとめておきます。
環境の変化
現在Google Playではアプリの対象 API レベル(targetSdkVersion)要件を遵守することを義務付けています。対象 API レベルは毎年のようにバージョンアップされるAndroid OSに対してアプリの互換性を維持するために使われます。アプリに最新の対象 API レベルを設定することで、ユーザー側のセキュリティ、プライバシー、パフォーマンスが向上します。
アプリの対象 API レベル要件を以下に記します。
定義
新しいアプリ | Play ストアでまだ公開されていないアプリ(最新のアプリなど) |
アプリのアップデート | Play ストアですでに公開されているアプリの新しいバージョン |
既存のアプリ | アップデートが配信されていない公開中のアプリ |
要件
Android OS のバージョン | Google Play アプリがこの API レベルを対象とすることが義務付けられる時期 | ||
新しいアプリ | アプリのアップデート | 既存のアプリ | |
Android 12(APIレベル31) | 2022年8月1日 | 2022年11月1日 | 2023年11月1日 |
Android 11(APIレベル30) | 2021年8月2日 | 2021年11月1日 | 2022年11月1日 |
引用元:Google Play アプリの対象 API レベル要件
さしあたり2022年11月1日までにアプリの対象APIレベルを30以上にしないとAndroid 11以上のデバイスを使用しているユーザーからはアプリをインストールしてもらえなくなります。
また、対象APIレベル要件は以下のルールで今後も更新されていくようです。
アップデートのない既存の Google Play アプリ: Android の最新のメジャー バージョンのリリースから 2 年を過ぎてもその Android API レベルをターゲットにしていないアプリは、それ以降のバージョンの Android OS を搭載したデバイスの新規ユーザーからアクセスできなくなります。
Google Play の対象 API レベルに関するポリシー
参考までに最近のAndroid OSのバージョン履歴を以下に記します。
コードネーム | バージョン | リリース日 | API レベル |
Q | 10 | 2019年9月3日 | 29 |
R | 11 | 2020年9月8日 | 30 |
S | 12 | 2021年10月4日 | 31 |
Sv2 | 12L | 2022年3月7日 | 32 |
Tiramisu | 13 | – | 33 (予定) |
引用元:Androidのバージョン履歴 – Wikipedia
開発環境
前回(1.3.3)と今回(1.4.0)の開発環境を記します。
Smoking Note | 1.3.3 (2019/11) | 1.4.0 (2022/6) |
Cordova | 9.0.0 | 11.0.0 |
Cordova Android | 8.1.0 | 10.1.2 |
targetSdkVersion | 28 | 31 |
- UIフレームワークOnsen UI(Monaca Version)はv2.10.10で変更なし
- minSdkVersionは21から23に変更
アップデート作業
ローカルストレージのデータが読み込めない
本アプリではローカルストレージに当日分の喫煙時間を記録し、日付変更時間を経過したとき当日分の記録を整形してファイルへ保存しています。アップデート版では前バージョンで作成したローカルストレージやファイルのデータにアクセスすることになります。
アプリのアップデートによるデータ引継ぎ動作を確認したところ、ローカルストレージからデータが読み込めていませんでした。原因はCordova Androidのバージョンを8.1.0から10.1.2に上げたことでした。
Cordova Android 10.0.0からWebViewAssetLoaderがサポートされ、リソースのアクセス方法が変更されました。
Cordova Android 10より、WWWディレクトリ配下のリソースアクセスの方法が更新されました。
従来はfileスキームによるリソースアクセスでしたが、http(s)スキームによるアクセスに変わりました。そのため、Cordova Android 10より古いバージョンでローカルストレージやクッキー等に保存したデータは、スキームが異なるCordova Android 10ではアクセスができなくなります。
Cordova Android 10.1.1 サポート – モナカプレス
アップデート版から前バージョン(fileスキーム)で作成したローカルストレージのデータを読み込むためには以下の設定が必要になります。
下記の設定をconfig.xmlに指定することで、Cordova Android10においてfileスキームを継続して利用できます。fileスキームの利用は、Androidにて非推奨となりましたが、暫定対応として設定できます。
<preference name=”AndroidInsecureFileModeEnabled” value=”true” />
Cordova Android 10.1.1 サポート – モナカプレス
ちなみにファイルアクセスについては上記の設定がなくても問題ありませんでした。
ターゲットSDKバージョンを31に設定したらアプリが立ち上がらない
アプリのターゲットSDKバージョンを30に設定していたときは問題なく動作していたのですが、ターゲットSDKバージョンを31に上げたところアプリが立ち上がらなくなりました。原因はCordova AdMob PlusプラグインでGoogle Mobile Ads SDKのバージョンを設定しなかったことでした。
詳細については前回の記事をご参照ください。
Cordova InAppBrowserプラグインが動作しない
Smoking Note | 1.3.3 (2019/11) | 1.4.0 (2022/6) |
cordova-plugin-inappbrowser
|
3.0.0 | 5.0.0 |
Cordova InAppBrowserプラグインを利用するとInAppBrowserウィンドウ(専用のWebブラウザ)からWebサイトを安全に開くことができます。
本アプリではインストールから2週間経過したときにレビューのお願いダイアログを表示し、[評価する] ボタンを押すとGoogle PlayストアをInAppBrowserウィンドウで開きます。
今回のアップデート作業とは直接関係がない機能でしたがたまたま動作確認したところ [評価する] ボタンを押しても何も起こらなくなっていました。
調べたところ v4.0.0 でwindow.open関数をcordova.InAppBrowser.open関数で上書きするコードが削除されたようです。このため自分のソースコード上でwindow.openの記述をcordova.InAppBrowser.openに書き換えて対応しました。
グラフ描画のJSライブラリでWarningが発生
USBデバッグ時にグラフをタッチスライドすると以下のWarningが大量に発生しました。
[Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.
対策としてcancelableがtrue(イベントがキャンセル可能)の場合のみpreventDefaultメソッドを実行するように修正しました。これによりWarningが発生しなくなりました。
// 変更前
if (event[preventDefault])
event[preventDefault]()
else
event.returnValue = false
// 変更後
if (event[preventDefault]) {
if (event.cancelable) {
event[preventDefault]()
}
} else {
event.returnValue = false
}
ons-dialogに配置したボタンが表示されない
本アプリのご利用者から日付変更時間の設定ができないというお問い合わせを頂きました。確認してみると日付変更時間の設定ダイアログ上にあるはずの [キャンセル/設定] ボタンが消えていました。よくみると[18:00] の横にあるはずの [21:00] のボタンも表示されていません。
このときのindex.htmlを以下に記します。[キャンセル/設定] ボタン(48-51行)と [21:00] のボタン(40-43行)はちゃんと記述してあります。
<ons-template id="dateline.html">
<ons-dialog id="dateline_dlg">
<div class="alert-dialog-title alert-dialog-title--material" data-i18n="setting.dateline.title"></div>
<div class="alert-dialog-content alert-dialog-content--material">
<div class="dateline_container">
<span style="margin: 0 0 10px 0; font-size: 14px;" data-i18n="setting.dateline.forward"></span>
<div class="segment st_btn_segment">
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="0" checked>
<div class="segment__button">0:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="3">
<div class="segment__button">3:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="6">
<div class="segment__button">6:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="9">
<div class="segment__button">9:00</div>
</div>
</div>
<span style="margin: 24px 0 10px 0; font-size: 14px;" data-i18n="setting.dateline.backward"></span>
<div class="segment st_btn_segment">
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="12">
<div class="segment__button">12:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="15">
<div class="segment__button">15:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="18">
<div class="segment__button">18:00</div>
</div>
<div class="segment__item">
<input type="radio" class="segment__input" name="segment-a" value="21">
<div class="segment__button">21:00</div>
</div>
</div>
</div>
</div>
<div class="alert-dialog-footer alert-dialog-footer--material">
<ons-alert-dialog-button onclick="onCloseDateline('ok')" data-i18n="btn.set"></ons-alert-dialog-button>
<ons-alert-dialog-button onclick="onCloseDateline('cansel')" data-i18n="btn.cancel"></ons-alert-dialog-button>
</div>
</ons-dialog>
</ons-template>
このときのDOMは以下のようになっていました。[21:00] のボタンを内包する4つ目のdev.segment__item要素と [キャンセル/設定] ボタンを内包するdiv要素がまるごとありません。とりあえず表示上の問題ではないことはわかりました。
かなりやっかいな状況でしたが、ソースコードを見直したりいろいろ試しているうちにうまくいきました。根本的な原因はわかりませんでしたがHTMLのons-templateの記述をtemplateに変更したところ意図したとおりのダイアログが表示されました。
Onsen UIの公式ガイドには以下のように記されています。
テンプレート – Onsen UIガイド
<ons-template>
要素も同じ目的で利用できますが、バージョン2.4.0以降はネイティブの<template>
要素を使うことを推奨します。
また、以下のようなissueも見つけました。こちらでもons-templateコンポーネントの代わりにtemplate要素を使用する必要があると記されています。
Deprecate or remove ons-templat – Onsen UI issues#2965
なぜons-templateコンポーネントを使用すべきではないのか、使用するとどうなるのか等の具体的な情報は見つけることができませんでした。ons-templateコンポーネントのコンパイルやパーサーに関わる問題でしょうか?なんにせよ難しそうな感じがします。
最新版のOnsen UI v2.12.0ではons-templateコンポーネントは削除されたようです。
LocalStorageからNativeStorageへ変更したときの初期化処理
これまでローカルストレージを利用していましたが、今回からCordova NativeStorageプラグインを利用することにしました。プラグインについては以前に記事にしましたのでご参照ください。
アプリ起動時の初期化処理について、変更前の動作フローの概要を以下に記します。まずLocalStorageの読み込みを行い、次にinitイベントのリスナーでパラメータAを参照した処理、最後にdevicereadyイベントのリスナーでパラメータBを参照した処理を行います。
NativeStorageプラグインを利用する場合、Cordovaの初期化が完了するのを待つ必要があるためdevicereadyイベントのリスナーでNativeStorageの読み込みを行います。パラメータを参照して行う処理はこの後に行う必要があるため、initイベントのリスナーで行っていた処理はdevicereadyイベントのリスナーへ移動させる必要があります。
実は実装時にinitイベントリスナー内のパラメータを参照する処理の移動が抜けてしまいました。たった一行、パラメータを参照する関数呼び出しが隠れていました。おかげでSmoking Note v1.40のリリースから一週間足らずで修正版のv1.4.1をリリースする羽目になりました。
ということで、喫煙管理アプリ Smoking Note は Google Play ストアから無料ダウンロードできます。