RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/アプリ開発
アプリ開発/2026-05-29中級

Android 壁紙アプリ v2.1.0 公開後の保守 — Crashlytics と Claude in Chrome で回す日々の運用

Beautiful 4K/HDR Wallpapers v2.1.0 と Ukiyo-e Wallpapers v1.8.0 の段階公開後、Crashlytics 監視と新規クラッシュ対応を Claude in Chrome に任せる運用に切り替えました。RecyclerView の防御的コピーや Glide の desugar など、実機で踏んだ落とし穴を保守目線でまとめます。

Android43保守3Crashlytics13段階公開2RecyclerView3Glide2個人開発187Claude in Chrome4

2014年から個人開発で iOS/Android アプリを公開してきて、累計5,000万ダウンロードを超えた壁紙アプリ群を1人で保守しています。先日 Beautiful 4K/HDR Wallpapers の v2.1.0(versionCode 49)と Ukiyo-e Wallpapers の v1.8.0(versionCode 41)が Google Play 審査を通り、段階公開フェーズに入りました。公開してからの2週間で見えた保守の手触りと、Claude in Chrome に任せている部分を、保守目線で書き残します。

公開直後の最初の48時間が一番怖い

Android の段階公開(Phased Rollout)は、5% → 25% → 50% → 100% の4段階で1段階あたり24〜48時間というのが私の標準です。一番神経を使うのは最初の5%が当たっている48時間で、ここで Crashlytics の Crash-free users が99.7%を下回ったり、ANR が0.20%を超えたりすると、即座に halt(段階公開比率を0%に戻す)判断をします。

v2.0.0 を公開したときは、ここで2回ほど halt して旧版に戻しました。何が起きていたかというと、WallpaperPagerAdapterThumbnailViewPagerAdapter のスクロール中に IndexOutOfBoundsException が頻発し、28日間で50ユーザー以上・56イベント以上のクラッシュが計上されました。

原因はリストへの参照を直接保持していたことで、データソース更新中に RecyclerView がアクセスして競合する状態でした。mList を防御的コピー化する1行修正で v2.1.0 にて根治しました。

// Before: 共有参照
public void setItems(List<Wallpaper> list) {
    this.mList = list;
    notifyDataSetChanged();
}
 
// After: 防御的コピー
public void setItems(List<Wallpaper> list) {
    this.mList = new ArrayList<>(list);
    notifyDataSetChanged();
}

アプリを8年運用してきて分かったことは、RecyclerView のクラッシュは大抵このデータソース共有参照が原因という、地味な結論です。

minSdk 23 と Glide 5.0.5 の desugar 必須化

v2.0.0 を公開した直後、Android 6.0.1(API 23)を使っているユーザー全員が起動3秒以内にクラッシュするという報告が Crashlytics に上がりました。7日間で12イベント・4ユーザー、Crashlytics 側は「早期クラッシュ」タグを付けてきました。

原因は Glide 5.0.5 が内部で Java 8 の java.util.function.Supplier を使っており、minSdk 23 では desugar が必須になることでした。build.gradle の依存に coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' は入っていたのですが、compileOptions { coreLibraryDesugaringEnabled true } のフラグが立っていませんでした。

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
        coreLibraryDesugaringEnabled true  // ← これが落ちていた
    }
}
 
dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}

ライブラリの依存ツリーが Java 8 API を要求する場合、desugar の依存追加だけでは不十分で、compileOptions のフラグ有効化が必要だ、というのが地味だけど大事な学びでした。

Play Store の density split で消えたリソース

Google Pixel + Android 12 の一部ユーザーで、起動時に Resources$NotFoundException が出るというクラッシュが Crashlytics に上がってきました。MainActivity.onCreate:120 で参照している background_silver の Drawable が見つからないという内容です。

調べた結果、background_silver.jpgdrawable-xxhdpi/drawable-xxxhdpi/ にしか配置しておらず、Play Store の Android App Bundle が密度ごとに分割配信した際、低密度バケットの端末に届く APK にこのリソースが含まれていない、という事故でした。

修正は drawable-nodpi/ に同ファイルを配置することです。drawable-nodpi/ はどの密度の split APK にも含まれるため、確実に存在させたいリソースの置き場として安全です。

app/src/main/res/
├── drawable-xxhdpi/background_silver.jpg
├── drawable-xxxhdpi/background_silver.jpg
└── drawable-nodpi/background_silver.jpg  ← これで全密度に届く

App Bundle 時代の Android では、密度別ディレクトリと nodpi のどちらに置くべきかという判断が、デザイン部門と運用部門の境界に落ちがちです。私は「確実に必要なリソースは nodpi、画質を優先したいものは密度別」というルールに揃えてからは、この種の事故は出なくなりました。

Claude in Chrome に任せている Crashlytics 監視

公開直後の48時間は人間が張り付くしかないのですが、それ以降は新規クラッシュの一次トリアージを Claude in Chrome に毎朝任せています。

具体的にやらせていることはシンプルで、Firebase Console の Crashlytics タブを開いて、前日新規発生したクラッシュを以下の条件で並べ替えてもらいます。

  • Crash-free users が 99.7% を下回ったクラッシュ
  • 影響ユーザー数が10人を超えたクラッシュ
  • 「Active」ステータスで「Untouched」のクラッシュ

ここまでを Claude in Chrome に出してもらった後、本当に対応が必要なクラッシュかどうかを私が判断します。スタックトレースの読み取りまでは AI に頼みますが、修正パッチを書くかどうかは、影響範囲と修正コストを見て自分で決めています。

ハマりどころとしては、Firebase Console の UI が時々レイアウト変更されるため、Claude in Chrome 側のプロンプトで「いまどのフィルタが効いているか」を毎回確認させるようにしました。これがないと、フィルタが意図せず外れていて、過去のクラッシュも新規扱いされる事故が起きます。

ad-free 状態の合成判定という設計の話

v2.0.0 公開後の保守で、一番神経を使ったのが ad-free 状態の合成判定でした。Globals.SHOW_AD の真実は、2つの独立した永続ストアから合成されます。

  • BillingManager — 永久課金やサブスクリプションの状態
  • AdFreeManager — リワード広告視聴で得た期間限定 ad-free の状態

ここで setAdFree(false) を直接呼ぶコードを1箇所でも書くと、BillingManager 側で ad-free を持っているユーザーが意図せず広告ありに戻る事故が起きます。鉄則は、常に合成判定(isAdFree || isRewardAdFree)を経由することです。

過去には BillingManager.restorePurchases() に論理逆転バグがあり、ad-free のままサブスク失効しても false に戻すパスが存在しないという事故もありました。inappsubs の両クエリを並行実行し、両方完了してから合成判定する finalEvaluation パターンに刷新し、Activity.onResume()restorePurchases() を呼ぶように変更したことで、起動中の失効も復帰時に検出できるようになりました。

個人開発で課金フローを触るときは、「Single Source of Truth が複数ある」という現実を最初に認めるところからスタートするのが、結局は近道だと思っています。

ユーザー設定 vs API デフォルトの上書きパターン

サーバー API が起動時に返す値(シャッフル、パーソナライズ広告の有効・無効など)と、ユーザーが Settings 画面で変更した値が衝突する問題があります。

私は各設定に *_USER_OVERRIDE フラグを別キーで持たせる方針で揃えました。

  • ユーザー操作からの保存は専用の *ByUser() メソッドを通す。このとき override も同時に立てる
  • システム由来の保存は override が立っていればスキップする

これによって、サーバー側でデフォルト値を変更してもユーザー設定が上書きされず、かつユーザー設定をリセットしたい場合は override を消すだけで API デフォルトに戻る、という運用が成立します。

多言語ユーザーレビュー返信のレート制限

最後に、保守の中で意外と重い作業がユーザーレビューへの返信です。4アプリ × 主要6カ国分で App Store だけで72件の代理返信を1セッションで処理したことがありますが、ここにはいくつか罠があります。

  • 1セッションで30〜40件を超える連続送信は、Google からスパム判定されるリスクがある
  • App Store 側もレート制限があり、各送信の間に約8秒の待機を入れた方が安全
  • 自動翻訳のベタ貼りは Google ポリシー違反として弾かれる可能性があるため、手書きトーンで返す
  • アフィリエイトリンク、他アプリ宣伝、SNS リンクは入れない

私は日本語・英語・繁体字中国語・イタリア語・ロシア語・韓国語・ペルシャ語・ウクライナ語・タイ語・ポーランド語・ポルトガル語(ブラジル)の11言語で返信しています。Claude in Chrome に手書きトーンで翻訳させ、各送信の間に8秒のディレイをプロンプトに明記しています。

公開後の保守は「監視 → 一次トリアージ → 修正判断」の3層

v2.0.0 から v2.1.0 までの保守を振り返って、結局やっていることは3層の繰り返しでした。

  1. 監視 — Crashlytics と Play Console の品質メトリクスを毎朝確認(Claude in Chrome に大部分を任せる)
  2. 一次トリアージ — 新規クラッシュのスタックトレースを読み、影響範囲とパターンを言語化する(AI と人間の混成)
  3. 修正判断 — 影響範囲と修正コストを見て、修正するか・段階公開を halt するか・無視するかを決める(人間が決める)

個人開発で6アプリを並行運用するためには、1の監視を人間がやるのは時間的に成立しないと感じています。AI に任せられる部分を機械的に任せて、3の修正判断に集中する時間を作る、というのが現状の落としどころです。

明日から、メディエーション設定への Liftoff・Unity Ads・InMobi の3社追加と、iOS 版のスライドショー逆移植が並行で動きます。v2.1.0 の保守は AI に任せつつ、新規開発に集中する1週間にする予定です。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-05-28
壁紙アプリにスライドショーとページジャンプを実装した手順と判断
綺麗な壁紙 4K/HDR と浮世絵壁紙の Android 版に追加したスライドショーとページジャンプの実装メモです。Handler とライフサイクル、SeekBar 連携、広告との兼ね合いで悩んだ判断を残します。
開発ツール2026-05-24
壁紙アプリ6本に Privacy Manifest を並行投入した運用ノート — 1本目の差し戻しを残り5本にどう活かしたか
iOS 壁紙アプリ6本に Privacy Manifest を並行投入した運用ノートです。1本目で受けた差し戻しを残り5本にどう適用し、段階公開リングをどう設計したかを、個人開発12年の感覚で淡々とまとめました。
開発ツール2026-05-23
Crashlytics の Velocity Alert を Claude in Chrome でトリアージした3週間の所感
壁紙アプリの Crashlytics Velocity Alert を Claude in Chrome に開かせてトリアージする運用を3週間続けました。何が楽になり、何は人の判断が必要かを正直にまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →