【個人開発 #4】PWAを"本物のアプリ"にしたい — Star Hopper Android化の全記録
💡 この記事について AIアシスタント(OpenClaw/スピカ)との会話を元にした学習記録です。 内容は変更される可能性があるため、実際の開発では公式ドキュメントを確認の上、ご自身の判断でお願いします。
🌟 Star Hopper とは? スマホを夜空にかざすと「あの明るい星は何?」がわかる星座ナビアプリ。明るい星を手がかりに次の星座を見つけていく「スターホッピング」をガイドしてくれます。現在 Google Play でテスト中🚀


この記事で分かること:
- PWAからAndroidアプリ(Google Play配布)にする方法
- Capacitorの仕組みと選んだ理由
- Kotlinでネイティブセンサープラグインを自作する流れ
- ビルド時にハマった「JDKバージョン問題」と「署名の壁」
PWAじゃダメなの?問題
セブ
スピカ、ずっとWebアプリとして作ってた Star Hopper なんだけど…Google Playに出してみたくなったんだよね。
スピカ AI
Star Hopper、PWAでもインストールできるのに、なんでわざわざ?
セブ
ちなみにPWAって何?って人もいるかもしれないから補足してもらっていい?
スピカ AI
もちろん。PWAはProgressive Web Appの略で、Googleが2015年頃に提唱した仕組み。ブラウザで動くWebサイトなんだけど、ホーム画面に追加するとアプリみたいに使える。オフラインでも動いたり、プッシュ通知も出せたりするの。
スピカ AI
インストール不要で使えるのが強みだけど、ハードウェアへのアクセスには限界があるんだよね。
セブ
そうそう、それがさ、カメラを空にかざして「あの明るい星、何?」ってやりたいんだけど、コンパスの方角がズレるんだよね。南を向いてるのに西って表示されたり。なんで?
スピカ AI
あー、Web APIの
deviceorientation イベントね。ブラウザによって実装バラバラだし、キャリブレーションもできないから精度は出にくいよね。 セブ
そうそう。星空アプリで方角がズレるって致命的じゃん。あと、Google Playに出てるだけで「ちゃんとしたアプリ」感があるっていうか…。
スピカ AI
なるほど。じゃあ整理すると、PWAとネイティブの比較はこうなるよ:
| 項目 | PWA(ブラウザ) | ネイティブアプリ |
|---|---|---|
| コンパス精度 | △ ブラウザ依存 | ◎ ハードウェア直接 |
| カメラアクセス | ○ 基本OK | ◎ フル制御 |
| 重い処理 | △ JSエンジン経由 | ◎ ネイティブ速度 |
| ストア配布 | × 不可 | ◎ Google Play |
| 既存コードの活用 | ◎ そのまま | △ 書き直し? |
セブ
最後の行がポイントで。何ヶ月もかけて作ったコードを全部書き直すのは無理なんだよね。
スピカ AI
そこで出てくるのが Capacitor だね。
React NativeでもFlutterでもない選択肢
セブ
で、スピカに「Androidアプリ作りたいんだけど、どうしたらいい?」って相談したら、最初に出てきたのがFlutterだったんだよね。
スピカ AI
Flutterは Google が作ったクロスプラットフォームのフレームワークで、Android と iOS のアプリを1つのコードベースから同時に作れるの。人気も高いし、最初の選択肢としてはおすすめしやすかったんだよね。
セブ
でも調べてみたら、Flutter使うと今まで書いたコードが全部使えなくなるって分かって。React Nativeっていうのもあったけど、それも同じ。なんで?
スピカ AI
FlutterはGoogleが作った Dart っていう専用言語で全部書き直す仕組みだし、React Nativeは React(JSX) が前提なの。どっちも「そのフレームワークのルールに従って最初から作る」必要があるから、セブが素のJavaScriptで書いた Star Hopper のコードをそのまま持っていけないんだよね。
セブ
えー、じゃあ何ヶ月も書いたコード、捨てるしかないの?って思ったんだけど、スピカに相談したら**「それならCapacitorがいいよ」**って。
スピカ AI
そこでCapacitor。これはIonic社が作ったオープンソースのツールで、もともとApache Cordova(昔のPhoneGap)っていう「Webアプリをネイティブに変換する」技術があったんだけど、その後継として2019年に登場した。Cordovaの課題だったパフォーマンスやメンテナンス性を改善して、モダンなWeb技術にちゃんと対応してるのがポイント。
セブ
へー、こういう「Webアプリをアプリにする」って発想、昔からあったんだ。
スピカ AI
そう。で、Capacitorは「既存のWebアプリをそのままAndroidアプリにする」ためのツール。仕組みはシンプル:
🌐 既存のWebアプリ(HTML/CSS/JS)
↓ そのまま
📱 Android WebView にラッピング
↓ 必要な部分だけ
🔧 Kotlin/Swiftでネイティブ機能を追加
セブ
つまり、自分のアプリはそのまま動いて、コンパスとかカメラとか「ブラウザだけじゃ無理な部分」だけ追加で書くってこと?
スピカ AI
そういうこと。Webとネイティブのいいとこ取りだね。セットアップもコマンド2つで終わるし。

☝️ 実際のStar Hopper画面。北斗七星から北極星への道筋を、ステップバイステップでガイドしてくれる。こういうリアルタイムな方位連動が、ネイティブセンサーの恩恵。
# ① プロジェクトをCapacitor対応にする(設定ファイルが生成される)
npx cap init
# ② Androidプラットフォームを追加(androidフォルダが自動生成される)
npx cap add android
セブ
え、コマンド2つだけ? これ何やってるの?
スピカ AI
cap init は「このWebアプリをCapacitorで管理するよ」って宣言。cap add android で「Androidアプリとしてのひな形を作って」って感じ。 スピカ AI
そう。この2コマンドで
android/ フォルダが生成されて、Android Studioで開けるプロジェクトになる。あとは npx cap sync でWebアプリのビルド結果をAndroidプロジェクトにコピーするだけ。 セブ
実際やってみたら、本当にindex.htmlがAndroidアプリの中で動いた時は感動したよ。「え、これだけ?」って。
「南を向いたのに北って出る」事件 🧭
セブ
で、本題。スピカに教えてもらいながらコンパスの機能を追加したんだけど、ここからが地獄だった。
スピカ AI
どんな問題が起きたの?
セブ
コンパス自体は動くようになったんだけど…スマホを縦に持つと方角が逆になる。南を向いてるのに「北」って出る。なにこれ?
スピカ AI
ちなみにコンパスは
TYPE_ROTATION_VECTOR っていうネイティブ専用のセンサーを使ってるの。加速度計・ジャイロ・地磁気を統合して方位を出す高精度なやつで、PWAのブラウザAPIじゃアクセスできない。だからこそKotlinで自作した意味があったんだけど…それは**「座標系リマップ」**の問題だね。 セブ
座標系…? なんで持ち方で方角が変わるの?
スピカ AI
Androidのセンサーって、デフォルトだとスマホをテーブルに置いた状態が基準なの。でも星を見るときって縦に持って空にかざすでしょ? そうすると座標軸の向きが入れ替わって、方角がめちゃくちゃになるの。
remapCoordinateSystem っていう変換処理を入れないといけない。 セブ
それって難しいの?
スピカ AI
修正自体はたったこれだけなんだけど:
// ポートレート(縦持ち)用に座標系をリマップ
SensorManager.remapCoordinateSystem(
rotationMatrix,
SensorManager.AXIS_X, // X軸はそのまま
SensorManager.AXIS_Z, // Y軸→Z軸に変換
adjustedMatrix
)
スピカ AI
たった3行なのに、これがないと南北逆転するっていうね。
セブ
3日悩んだよ…。エミュレータだとセンサーの挙動が実機と違うから、実機テストじゃないと気づけなかったかもしれない。

☝️ 修正後の画面。ちゃんと「南 194°」と表示されている。たった3行の修正で、南北逆転が解消された。
JDKバージョン地獄
セブ
コードができたら次は**アプリにまとめる作業(ビルド)**なんだけど、ここでまたエラー。
スピカ AI
何が出たの?
セブ
ビルドのボタンを押したら、なんか英語のエラーが出て止まっちゃって:
Unsupported class file major version 65
スピカ AI
これ、ざっくり言うと**「新しい道具で作ったパーツを、古い機械が読めない」**って状態。Javaにはバージョンごとに番号があって、「version 65」= JDK 21 のこと。Capacitor 6 のライブラリが JDK 21 で作られてるのに、自分のマシンに入ってる JDK 17 だと「知らない形式です」ってエラーになるの。
セブ
つまり、道具のバージョンが古いってこと? どうすれば直るの?
スピカ AI
JDK 21をインストールして、パソコンの設定を新しいバージョンに切り替えればOK。こんな感じ:
# JDKバージョン確認
java -version
# java version "21.0.x" が出ればOK
# JAVA_HOMEの設定(Mac)
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
セブ
これで直ったんだけど、なんでこんな分かりにくいの?
スピカ AI
ここがハマりポイントで、Capacitorの公式ドキュメントには「JDK 21が必要」ってはっきり書いてないの。エラーメッセージも「version 65がサポートされてません」だけだから、初心者には何のことかさっぱりだよね。
セブ
「JDK 21を入れてください」って一言書いてくれればいいのに…。こういうの、地味にきつい。
「署名」って何?キーストアの壁
スピカ AI
ビルドは通った?
セブ
デバッグビルドは通ったよ。でもGoogle Playに出すにはリリースビルドが要るでしょ。そしたら「署名付きAAB」が必要だって言われて。
スピカ AI
アプリの「身分証明書」みたいなものだね。キーストアっていうファイルに秘密鍵を入れて、それでアプリに署名する。
セブ
最初は「なんで面倒なことさせるんだ」って思ったけど、考えたら誰でも他人のアプリを勝手にアップデートできたら危険だよね。
スピカ AI
そう。たとえば悪意のある第三者が「Star Hopperのアップデートです」って偽のバージョンをアップロードできちゃったら、ユーザーのスマホにマルウェアが入るかもしれない。署名があることで「このアップデートは確かに元の開発者が作ったものです」って証明できるの。
セブ
なるほど、ユーザーを守るための仕組みなんだな。納得。
# キーストア生成
keytool -genkeypair -v \
-keystore starhopper-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias starhopper \
-storepass [パスワード] \
-keypass [パスワード] \
-dname "CN=Star Hopper, OU=Development, O=7Sapiens, L=Tokyo, C=JP"
スピカ AI
で、
build.gradle にこの署名情報を書いてあげる:// android/app/build.gradle
android {
signingConfigs {
release {
storeFile file('../starhopper-upload.jks')
storePassword '[パスワード]'
keyAlias 'starhopper'
keyPassword '[パスワード]'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
セブ
これで
./gradlew bundleRelease を叩いたら、ついに署名付き .aab ファイルが出来た! スピカ AI
おめでとう。…ところでセブ、このキーストアファイルの管理、ちゃんと考えてる?
セブ
え、普通にプロジェクトフォルダに置いてあるけど…。
スピカ AI
それ、なくしたらアプリのアップデートが二度とできなくなる可能性があるよ。
セブ
え、マジ?
スピカ AI
マジ。Google Playの仕組みを整理するね。実は2種類の鍵があるの:
| 鍵の種類 | 誰が持つ? | 役割 |
|---|---|---|
| アプリ署名鍵 | ストアでの最終署名に使う。Googleが安全に保管 | |
| アップロード鍵 | 開発者(自分) | AABをアップロードする時の本人確認用 |
セブ
つまり自分が
keytool で作ったのは「アップロード鍵」のほうで、最終的な署名はGoogleがやってくれると? スピカ AI
そう。Google Play App Signing っていう仕組みで、初回アップロード時にGoogleが「アプリ署名鍵」を自動生成して安全に保管してくれる。だからアプリの完全性はGoogleが守ってくれるの。
セブ
じゃあ自分のアップロード鍵(.jksファイル)をなくしても大丈夫ってこと?
スピカ AI
完全にアウトではないけど、かなり面倒。アップロード鍵をなくした場合は、Play Consoleからリセット申請ができる。ただし本人確認に時間がかかるし、その間アプリのアップデートが出せない。
セブ
うわ、それは困る…。
スピカ AI
しかもセブ、Mac miniとMacBookの2台で開発してるでしょ?
セブ
そうなんだよ。家ではMac mini、外ではMacBook。両方からビルドしたい。
スピカ AI
その場合、同じキーストアファイル(.jks)を両方のマシンに置く必要がある。キーストアはマシンごとに別々じゃダメ。同一のファイルじゃないと「別人がアップロードしてる」とGoogleに判断されて弾かれるよ。
セブ
じゃあ .jks ファイルを2台で同期しないといけないわけか。でも Git にキーストアを入れるのはセキュリティ的にまずいよね?
スピカ AI
その通り。
.jks ファイルは絶対に Git にコミットしちゃダメ。パスワードが漏れたら誰でもアプリを署名できちゃうから。安全な共有方法は:- iCloud Drive / Google Drive — 暗号化された場所にバックアップ
- USB経由でコピー — ネットワークを経由しない最安全
- 1Password / Bitwarden — パスワードマネージャーにファイルごと保存
セブ
自分はとりあえず iCloud Drive にバックアップして、パスワードは1Passwordに保存した。
スピカ AI
いいね。あと
.gitignore にキーストアを追加するのも忘れずに:# .gitignore に追加
*.jks
*.keystore
セブ
キーストアひとつでこんなに考えることがあるとは思わなかった…。
スピカ AI
個人開発だと自分しかいないから、鍵の管理 = アプリの生命線なんだよね。会社なら管理してくれる人がいるけど。
振り返り — 「PWA → Android」で見えた景色
セブ
振り返ると、Capacitorのおかげで既存のWeb資産を捨てずにAndroidアプリにできたのが一番大きいかもしれない。
スピカ AI
数万行のコードを書き直すのと、Kotlinで200行のプラグインを追加するのと、どっちが現実的かって話だよね。
セブ
ネイティブセンサーの精度は体感で全然違った。Web APIのコンパスが「だいたい北」なのに対して、ネイティブは「北北西350度」みたいにピタッと出る。
スピカ AI
星空アプリにはその精度が命だもんね。

✨ シリウスを見つけた時の達成画面。オリオンの三つ星からたどり着くルートをガイドして、最後にこの画面が出る。星の豆知識も読める。
セブ
つまづいたポイントをまとめると:
- 座標系リマップ — スマホの持ち方で座標軸が変わる(3日悩んだ)
- JDK 21 必須 — Capacitor 6+ の暗黙の要件
- キーストア管理 — なくしたら終わりの秘密鍵
セブ
どれもドキュメントを丁寧に読めば回避できたかもしれないけど、実際にハマって体で覚えるのも大事なのかなと思ったりします。
スピカ AI
何か他に気づいたことは?
セブ
あとね、今回強く感じたのが**「AIに任せるにも、ある程度の技術知識がないと的確な質問ができない」**ってこと。
スピカ AI
どういうこと?
セブ
Star Hopperって、星図のリアルタイム描画、GPSでの現在地取得、センサーのコンパス連動…いろんな機能が同時に動くわけじゃん。星図をスムーズに動かしたい、でもGPSもセンサーも同時に処理してる中で描画が重くなる。こういう「複数の技術が絡み合う問題」って、そもそも何が原因で遅いのか・何がボトルネックになってるのかをある程度わかってないと、AIに聞いても的外れな答えしか返ってこない。
スピカ AI
「アプリが遅いです、助けて」じゃ範囲が広すぎるもんね。「センサーのポーリング頻度が描画フレームレートとぶつかってる気がする」くらい絞れると、AIも具体的な対策を提案できる。
セブ
そうそう。AIは魔法じゃなくて「対話のパートナー」だから、こっちが投げる質問の精度で返ってくる答えの質が全然変わる。技術を知ってるほどAIを使いこなせるっていう、ちょっと矛盾してるようで大事な話だなって。
スピカ AI
次はいよいよ Google Play にこのアプリを登録する話だね。ストアの入力項目の多さに絶望する回。
セブ
ネタバレやめて。
⚠️ 注意 この記事はOpenClaw/AIアシスタントとの会話を元にした内容です。 実際の運用は自身で確認してください。 技術情報は変わる可能性があるので、公式ドキュメントの確認を推奨します。
シリーズ目次
- VPSとMac Miniの違い
- Groq APIで音声文字起こし
- 喋るだけでブログ記事になる仕組み
- PWAを”本物のアプリ”にしたい ← 今ここ
- はじめてのGoogle Play — ストア掲載編(次回)