RORK LABEN
DEADLINE — Google Play は2026年8月31日以降、新規アプリと既存アプリの更新の双方に対象 API レベル36(Android 16)以上を要求します。残り12日ですEXTENSION — 期限に間に合わない場合は Play Console の期限延長フォームから申請することで11月1日まで配信を継続できます。延長は自動ではなく、申請自体を期限内に行う必要がありますTARGET SDK — Rork が出力する Expo / React Native アプリでも targetSdkVersion は自分で確認が必要です。テンプレートが古い SDK に固定されていると、生成しただけでは要件を満たしませんRORK MAX — 従来の Rork が React Native / Expo を出力するのに対し、Rork Max はネイティブ Swift を生成します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage に対応しますFUNDING — Left Lane Capital 主導の1,500万ドルのシードラウンドが4月9日に発表されました。あわせてアプリビルダーの Paperline を買収し、エンジニアリング人材の獲得を進めていますiOS — iOS 27 は開発者向けベータ6が8月17日に配布され、秋の正式リリースが近づいています。ネイティブ Swift 出力を使うなら、新 OS への対応時期が配布計画に直接効いてきますDEADLINE — Google Play は2026年8月31日以降、新規アプリと既存アプリの更新の双方に対象 API レベル36(Android 16)以上を要求します。残り12日ですEXTENSION — 期限に間に合わない場合は Play Console の期限延長フォームから申請することで11月1日まで配信を継続できます。延長は自動ではなく、申請自体を期限内に行う必要がありますTARGET SDK — Rork が出力する Expo / React Native アプリでも targetSdkVersion は自分で確認が必要です。テンプレートが古い SDK に固定されていると、生成しただけでは要件を満たしませんRORK MAX — 従来の Rork が React Native / Expo を出力するのに対し、Rork Max はネイティブ Swift を生成します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage に対応しますFUNDING — Left Lane Capital 主導の1,500万ドルのシードラウンドが4月9日に発表されました。あわせてアプリビルダーの Paperline を買収し、エンジニアリング人材の獲得を進めていますiOS — iOS 27 は開発者向けベータ6が8月17日に配布され、秋の正式リリースが近づいています。ネイティブ Swift 出力を使うなら、新 OS への対応時期が配布計画に直接効いてきます
記事一覧/開発ツール
開発ツール/2026-05-07上級

Rorkアプリで「閉じてる間も最新」を本番品質で実装する — iOS BGTaskScheduler × Silent Push × Android WorkManager

Silent Pushが届かない、BGTaskSchedulerが発火しない。Rorkで作ったアプリのバックグラウンド更新を、iOSとAndroidそれぞれの実行モデルに合わせて本番品質へ詰めていく過程と、成功率を計測してから直した記録です。

Rork537BGTaskScheduler3Silent PushWorkManagerバックグラウンド更新2React Native227Expo172

プレミアム記事

「アプリを閉じている時間こそが、いちばん勝負どころなんですよ」と、ある先輩開発者から言われたことを今でもよく思い出します。私が個人開発を始めた頃、ユーザーがアプリを起動している時間にだけ気を配っていました。けれど数年運営してみて、本当に効くのは「閉じている間に何をしておけるか」だと痛感しました。たった3秒の起動直後に新着が見えるか、5秒経ってからやっと表示されるか。その差が翌日の継続率を1〜2ポイント動かしてしまうことを、ダッシュボードを見ながら何度も実感してきました。

Rorkで生成したアプリにこの「閉じてる間の更新」を組み込もうとすると、iOSとAndroidの仕様の違いに最初は戸惑います。Silent Pushを送ってもなぜか届かない、BGTaskSchedulerを登録したのに発火しない、AndroidのWorkManagerがDozeで止まってしまう。ドキュメント通りに書いたつもりなのに、本番では半分くらいしか動いていません。私も個人開発でこの落とし穴に何度も落ちました。原因が分からないまま数日待って、結局「登録の書き場所が1行ずれていただけ」と判明したこともあります。

以下は、その落とし穴を一つずつ潰していった記録です。動くコードと、成功率の数字を見ながら決めた運用の判断を、順番に並べています。

iOSとAndroidのバックグラウンド実行モデルを正確に理解する

最初にしておきたいのが、両OSの「バックグラウンドで何ができるか」の違いを正確に把握することです。ここを曖昧にしたまま実装を進めると、片方のOSでは動くけれどもう片方では沈黙する、という状態になります。

iOSのバックグラウンド更新は、大きく3つの経路があります。BGAppRefreshTaskは短時間(30秒前後)の軽い更新を、システムが学習したユーザーの利用パターンに合わせて発火します。BGProcessingTaskは数分〜数時間の重い処理(端末の充電中・Wi-Fi接続中などの条件付き)を担います。そしてSilent Push、つまりapns-push-type: backgroundかつcontent-available: 1を含むAPNS通知が、サーバ側からトリガーする更新の入口です。これらは互いに排他ではなく、実用上は3つを併用して「いずれかが届く確率を上げる」設計になります。

一方Androidは、API 31(Android 12)以降のWorkManagerが事実上の正解になっています。OneTimeWorkRequestPeriodicWorkRequestの組み合わせ、ExpeditedWorkRequestによる即時実行、Constraintsによるネットワーク・充電条件の指定、これらを正しく使い分けることで、Doze・App Standby下でも実用的な実行頻度を保てます。Androidの大きな利点は、JobSchedulerやAlarmManagerに比べて「OSのバージョン差を吸収してくれる」点で、Android 8〜15まで同じコードで動かせるのは個人開発者には本当にありがたい設計です。

両者で決定的に違うのは、iOSはシステム主導でいつ実行するかを決め、開発者は「動くチャンス」を最大化する設計をするのに対し、Androidは開発者が条件と頻度をある程度コントロールできる点です。この前提を共有してから、コードに入ります。

iOS: BGTaskSchedulerの本番設定パターン

Rorkで生成されたInfo.plistには、デフォルトでバックグラウンドモードの設定が入っていないことが多いので、まずexpo-task-managerexpo-background-fetchを入れたうえで、app.jsonでバックグラウンドモードを宣言します。

// app.json
{
  "expo": {
    "ios": {
      "infoPlist": {
        "UIBackgroundModes": ["fetch", "processing", "remote-notification"],
        "BGTaskSchedulerPermittedIdentifiers": [
          "net.rorklab.refresh",
          "net.rorklab.heavy-sync"
        ]
      }
    },
    "plugins": [
      ["expo-task-manager"],
      ["expo-background-fetch", { "ios": { "minimumInterval": 900 } }]
    ]
  }
}

注意点として、BGTaskSchedulerPermittedIdentifiersに登録した識別子と、コードでBGTaskScheduler.shared.registerに渡す識別子は完全一致させる必要があります。私は以前、net.rorklab.refreshnet.rorklab.app.refreshという揺らぎで2日間悩んだことがあるので、定数化してSwift側・JS側で共有することをおすすめします。同じことを繰り返さないために、識別子はconstants/background.tsのような専用ファイルにまとめるのが安全です。

JavaScript側では、expo-task-managerにタスクを定義してからBackgroundFetchに登録します。

// background-refresh.ts
import * as TaskManager from "expo-task-manager";
import * as BackgroundFetch from "expo-background-fetch";
import { fetchLatestFeed } from "./api/feed";
import { saveToCache } from "./store/cache";
 
export const REFRESH_TASK = "net.rorklab.refresh";
 
TaskManager.defineTask(REFRESH_TASK, async () => {
  const startedAt = Date.now();
  try {
    const feed = await fetchLatestFeed({ limit: 20, since: "auto" });
    await saveToCache("home_feed", feed);
    // 期待出力: console に "[refresh] 18 items in 1.2s" のような短い完了ログ
    console.log(`[refresh] ${feed.length} items in ${(Date.now() - startedAt) / 1000}s`);
    return BackgroundFetch.BackgroundFetchResult.NewData;
  } catch (error) {
    console.warn("[refresh] failed", error);
    return BackgroundFetch.BackgroundFetchResult.Failed;
  }
});
 
export async function registerBackgroundRefresh() {
  const status = await BackgroundFetch.getStatusAsync();
  if (status === BackgroundFetch.BackgroundFetchStatus.Restricted ||
      status === BackgroundFetch.BackgroundFetchStatus.Denied) {
    return;
  }
  await BackgroundFetch.registerTaskAsync(REFRESH_TASK, {
    minimumInterval: 60 * 15, // iOSはあくまで「最小間隔」のヒント
    stopOnTerminate: false,
    startOnBoot: true,
  });
}

ここでよくある間違いが2つあります。1つめはminimumIntervalを「実際の発火間隔」だと勘違いすることです。これはあくまでヒントで、iOSは端末の状態とユーザーの利用パターンから独自に判断します。実機では1日2〜4回、ユーザーが朝・昼・夜にアプリを開く習慣がある場合はその直前に集中して動く傾向があります。2つめは、戻り値を必ずNewData/NoData/Failedのいずれかで返すことです。返さないとシステムが「不誠実なアプリ」と判断し、徐々に発火頻度を絞ってきます。実装を急いでreturnを書き忘れていたために、リリース後の発火率が日に日に下がっていく事故を、私も一度経験しています。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
Silent Pushが届かない問題で詰まっていた人が、APNSのthrottlingと本番運用パターンを理解して、実用的な配信設計に切り替えられます
iOSのBGTaskSchedulerとAndroidのWorkManagerを統一インターフェースで扱うReact Nativeブリッジ実装を、動くコードとともに手に入れられます
離脱しかけたユーザーの30%を取り戻す「バックグラウンド復帰ファネル」を、自分のRorkアプリに今日から組み込めるようになります
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

開発ツール2026-05-28
Rork アプリで iOS の BGTaskScheduler.submit が Error Code=1 で失敗するときの切り分け手順
BGTaskScheduler.submit が Error Code=1 を返すとき、原因は 6 種類に収まります。Expo 構成に固有の identifier の落とし穴と、getPendingTaskRequests による自己確認まで、実際に踏んだ順番で整理しました。
開発ツール2026-07-30
Renovate が触ってよい依存と、触ってはいけない依存 — Expo SDK 57 の123パッケージを機械的に切り分ける
Rork 生成アプリに依存の自動更新を入れると、Expo が版を管理している123パッケージまで巻き込まれます。2026年7月30日時点の実測では、うち6件が npm の最新版へメジャー更新される差でした。除外リストを手で書かず SDK から機械生成する手順をまとめます。
開発ツール2026-07-28
prebuild --clean で消える手入れを、上げる前に数える — Expo SDK 57 移行の棚卸し
Expo SDK 57 への移行前に、ios/ へ加えた手入れが prebuild の再生成でいくつ失われるかを数える手順をまとめました。素の差分649行のうち意味を持つのは3行。ノイズを正規化して実質的な変更だけを抽出するスクリプトと、config plugin への移し替えを実測つきで共有します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →