手元のメモには「2026年9月に完全義務化」と書いてありました。Android の開発者認証の話です。9月といえば来月で、私が個人で運用しているアプリのうち2本は Google Play で配信しています。慌てて公式の説明を読み直したところ、そこに並んでいたのは4つの国名でした。
ブラジル、インドネシア、シンガポール、タイ。
2026年9月30日に始まるのはこの4か国での運用であって、世界全体ではありません。グローバル展開は2027年と明記されています。「9月に全部止まる」という理解のまま8月を過ごしていたら、優先順位を大きく間違えていました。
とはいえ、この4か国が自分にとって小さいかどうかは別の話です。壁紙アプリのように言語依存が薄いカテゴリでは、新興国からの流入が想像より大きくなります。「4か国だけ」で安心できるかは、自分の数字を見ないと決められません。この記事は、その確かめ方の記録です。
確定している日程と、対象になるストア
まず、いま公式に確定している部分を並べておきます。推測を混ぜないために、日付と国名は Android Developer Console のヘルプに記載された内容だけを使います。
時期 内容
2025年11月 早期アクセス登録者が、Google Play 外で配信するアプリの認証を開始
2026年3月 Android Developer Console が全開発者に開放
2026年9月30日 ブラジル・インドネシア・シンガポール・タイ で、認証済み開発者が登録したアプリのみが対象ストアから新規インストール可能に
2027年 認定 Android 端末向けの配信全体へグローバル展開
そして、この初期フェーズで対象になるストアが明示されています。ここが読み飛ばされがちな部分でした。
事業者 ストア
Google Google Play
Honor HONOR App Market
OPlus OPPO App Market
Samsung Galaxy Store
Transsion Palm Store
vivo V-Appstore
Xiaomi GetApps
一覧の先頭に Google Play が入っています。私は当初、この制度をサイドロード規制の話だと受け取っていました。APK を直接配る人向けの制度で、Play にしか出していない自分には関係が薄い、と。実際には逆で、Play 経由のインストールも認証の対象に含まれます。「Play にしか出していないから無関係」という読み方が、いちばん危ない誤読でした。
サイドロード自体が禁止されるわけでもありません。未認証の開発者のアプリは、リスクを承諾したうえで一度きりの設定を済ませる「高度なフロー」を経ればインストールできますし、ADB は開発用の手段として従来どおり使えます。ただしこれは端末を触れる本人向けの逃げ道であって、ストアから普通に落としてもらう配信の話ではありません。
「4か国だけ」が自分にとって小さいかを先に出す
ここからが本題です。4か国という範囲が確定したなら、次に必要なのは「その4か国が自分のインストールの何割か」という一つの数字だけです。この数字が1%なら9月30日は緊急事項ではなく、20%なら8月中に手を動かす必要があります。
Play Console の「統計情報」から国別のインストール数を CSV で書き出して集計します。ここで一つ、毎回つまずく点があります。Play Console が配布するレポート CSV は UTF-16LE(BOM 付き)で、UTF-8 として読むと列名の照合に失敗します。列が見つからないというエラーが出たら、まずエンコーディングを疑ってください。
// scripts/enforcement-share.mjs
// Play Console > 統計情報 > エクスポート で取得した国別インストール CSV を集計します。
// 使い方: node scripts/enforcement-share.mjs installs_country_202607.csv
import fs from "node:fs" ;
// 2026-09-30 に運用が始まる4か国(ISO 3166-1 alpha-2)
const ENFORCED = new Map ([
[ "BR" , "ブラジル" ],
[ "ID" , "インドネシア" ],
[ "SG" , "シンガポール" ],
[ "TH" , "タイ" ],
]);
const file = process.argv[ 2 ];
if ( ! file) {
console. error ( "使い方: node scripts/enforcement-share.mjs <国別インストールCSV>" );
process. exit ( 1 );
}
// ⚠ Play Console のレポートは UTF-16LE + BOM。utf8 で読むと列名が一致しません。
const raw = fs. readFileSync (file, "utf16le" ). replace ( / ^ / , "" );
const lines = raw. trim (). split ( / \r ? \n / );
if (lines. length < 2 ) {
console. error ( "行が読めていません。エンコーディング(UTF-16LE)を確認してください。" );
process. exit ( 1 );
}
const cols = lines[ 0 ]. split ( "," ). map (( c ) => c. trim ());
const iCountry = cols. findIndex (( c ) => /country/ i . test (c));
// 「Store listing acquisitions」など複数の指標が並ぶため、端末単位の指標を優先します
const iInstalls = cols. findIndex (( c ) => /install/ i . test (c) && /device | unique/ i . test (c));
const iFallback = cols. findIndex (( c ) => /install/ i . test (c));
const target = iInstalls >= 0 ? iInstalls : iFallback;
if (iCountry < 0 || target < 0 ) {
console. error ( "列名を特定できませんでした。実際のヘッダー:" , cols. join ( " | " ));
process. exit ( 1 );
}
let total = 0 ;
const hits = new Map ();
for ( const line of lines. slice ( 1 )) {
const cells = line. split ( "," );
const code = (cells[iCountry] ?? "" ). trim (). toUpperCase ();
const n = Number ((cells[target] ?? "0" ). replace ( / [ ^ \d.-] / g , "" )) || 0 ;
if ( ! code || n <= 0 ) continue ;
total += n;
if ( ENFORCED . has (code)) hits. set (code, (hits. get (code) ?? 0 ) + n);
}
const affected = [ ... hits. values ()]. reduce (( a , b ) => a + b, 0 );
const share = total > 0 ? (affected / total) * 100 : 0 ;
console. log ( `集計対象インストール: ${ total . toLocaleString () }` );
for ( const [ code , label ] of ENFORCED ) {
const n = hits. get (code) ?? 0 ;
const pct = total > 0 ? ((n / total) * 100 ). toFixed ( 2 ) : "0.00" ;
console. log ( ` ${ label }(${ code }): ${ n . toLocaleString () } (${ pct }%)` );
}
console. log ( `4か国合計: ${ affected . toLocaleString () } (${ share . toFixed ( 2 ) }%)` );
console. log (
share >= 5
? "→ 8月中の着手を推奨します(登録待ちが発生しても間に合う日程)"
: "→ 9月30日は緊急事項ではありません。2027年のグローバル展開までに整えれば十分です"
);
期待する出力はこの形になります。
集計対象インストール: 412,880
ブラジル(BR): 21,043 (5.10%)
インドネシア(ID): 38,512 (9.33%)
シンガポール(SG): 1,204 (0.29%)
タイ(TH): 9,870 (2.39%)
4か国合計: 70,629 (17.11%)
→ 8月中の着手を推奨します(登録待ちが発生しても間に合う日程)
しきい値を5%に置いたのは、私の判断です。個人開発の場合、登録作業そのものは長くても数時間で終わります。数時間の作業で守れる配信が全体の5%を超えるなら、後回しにする理由がないという線引きです。ここは事業の規模で変わるので、数字を見てから自分で決めてください。
なお、この集計を各アプリで走らせると、比率がアプリごとに大きく違うことに気づきます。日本語圏に寄せたアプリと、言語をほとんど使わないアプリでは、同じ開発者が出していても新興国比率がまるで別物になります。私が運用しているアプリでも、壁紙のようにテキストが少ないものほど東南アジアの比率が高く出ます。「うちのアプリは国内向けだから」という感覚は、アプリ単位で確かめないと当てになりません。
詰まるのは締切ではなく、その前段の待ち時間でした
日程を確認して安心しかけたところで、もう一つ気づいたことがあります。この制度で個人開発者が本当に間に合わなくなるとしたら、原因は9月30日という締切ではなく、その前段にある待ち時間です。
法人として登録する場合、D-U-N-S 番号が必要になります。持っていなければ無料で取得できますが、公式の説明では発行までに最大28日かかるとされています。
今日が8月11日です。ここから28日を足すと9月8日。9月30日まで22日の余裕がありますが、書類の不備で差し戻されれば一往復で消える幅です。個人名義で登録するのか法人名義で登録するのかを「まだ決めていない」状態のまま8月を過ごすと、選択肢が片方だけ消えます。名義の決め方そのもので迷っている場合は、アプリを公開すると本名は表示されるのか で整理した観点が判断材料になります。
登録の種類も整理しておきます。
種類 費用 配布範囲 本人確認
フル配布アカウント $25 制限なし 政府発行 ID が必要
限定配布アカウント 無料 アプリ数は無制限・最大20台 政府発行 ID は不要
限定配布は学生や趣味の開発者、身内に配る用途を想定したものです。20台という上限があるため、ストアで一般公開するアプリの受け皿にはなりません。ただ、本番アプリとは別に検証用のパッケージを配っている場合、その受け皿としては現実的な選択肢になります。
棚卸しの単位はアカウントではなくアプリでした
登録が「開発者の身元確認」だけで完結するなら、作業は1回で済みます。実際にはアプリの登録も伴うため、確認すべき単位は開発者アカウントではなく、配信しているアプリの識別子そのものになります。
これは想像していたより厄介でした。年単位で運用していると、ストアに並んでいる本番アプリのほかに、検証用に作ったパッケージ、名前を変えて出し直した旧バージョン、フォームファクタ違いで別扱いになっているものが混ざります。Play Console の一覧を眺めるだけでは、どれが現役でどれが残骸なのかの判断がつきません。
Expo や Rork で作ったプロジェクトを複数持っている場合は、リポジトリ側から機械的に識別子を集めたほうが速く済みます。
// scripts/inventory-identifiers.mjs
// 複数の Expo / Rork プロジェクトから applicationId と bundleIdentifier を集めて一覧にします。
// 使い方: node scripts/inventory-identifiers.mjs ~/projects/*
import fs from "node:fs" ;
import path from "node:path" ;
const roots = process.argv. slice ( 2 );
if (roots. length === 0 ) {
console. error ( "使い方: node scripts/inventory-identifiers.mjs <プロジェクトディレクトリ...>" );
process. exit ( 1 );
}
const rows = [];
for ( const root of roots) {
if ( ! fs. existsSync (root) || ! fs. statSync (root). isDirectory ()) continue ;
// app.json / app.config.json のどちらでも拾えるようにします
const candidates = [ "app.json" , "app.config.json" ]. map (( f ) => path. join (root, f));
const configPath = candidates. find (( p ) => fs. existsSync (p));
if ( ! configPath) {
// app.config.js(実行が必要な形式)はここでは静的に読めないため、明示的に印を残します
if (fs. existsSync (path. join (root, "app.config.js" ))) {
rows. push ({ project: path. basename (root), android: "要手動確認 (app.config.js)" , ios: "要手動確認" , channel: "-" });
}
continue ;
}
const cfg = JSON . parse (fs. readFileSync (configPath, "utf8" ));
const expo = cfg.expo ?? cfg;
// eas.json があれば、どのプロファイルが production 配信に使われているかも併記します
let channel = "-" ;
const easPath = path. join (root, "eas.json" );
if (fs. existsSync (easPath)) {
const eas = JSON . parse (fs. readFileSync (easPath, "utf8" ));
channel = eas?.build?.production?.channel ?? "production (channel 未指定)" ;
}
rows. push ({
project: path. basename (root),
android: expo?.android?.package ?? "未設定" ,
ios: expo?.ios?.bundleIdentifier ?? "未設定" ,
channel,
});
}
if (rows. length === 0 ) {
console. error ( "Expo 設定が見つかりませんでした。パスを確認してください。" );
process. exit ( 1 );
}
console. table (rows);
const missing = rows. filter (( r ) => r.android === "未設定" );
if (missing. length > 0 ) {
console. warn (
` \n ⚠ android.package が未設定のプロジェクトが ${ missing . length } 件あります。` +
" \n ビルド時に自動採番された識別子で配信されている可能性があるため、Play Console 側の実値と突き合わせてください。"
);
}
android.package を明示していないプロジェクトが出てきた場合は、そこを優先して確認してください。設定ファイルに書かれていない識別子は、開発者本人の記憶にも残っていないことが多く、棚卸し表から落ちる筆頭です。
署名鍵の持ち主も一緒に確認しておく
識別子を並べたら、そのアプリが「誰の鍵で署名されているか」も同じ表に足しておくことをおすすめします。認証の登録作業とは直接の関係がありませんが、この機会にしか触らない情報だからです。
ビルド済みの成果物から署名者を確認するコマンドは、APK と AAB で異なります。ここは間違えやすいところです。
# APK の場合: apksigner で証明書のフィンガープリントを確認します
apksigner verify --print-certs app-release.apk | grep -i "SHA-256 digest"
# AAB の場合: apksigner は AAB を検証できません。keytool を使います
keytool -printcert -jarfile app-release.aab | grep -i "SHA256:"
# Play App Signing を使っている場合、ここで出るのは「アップロード鍵」です。
# 端末に届く配信物の署名鍵は Google 側が保持しているため、
# Play Console > 設定 > アプリの署名 に表示される値と照合してください。
期待する出力はこの形です。
SHA-256 digest: 3f:aa:19:...:c2:70
apksigner verify に AAB を渡すと、形式が違うという趣旨のエラーで止まります。私はここで一度「署名が壊れた」と思い込んで時間を使いました。壊れているのではなく、そもそも対象外というだけでした。署名まわりでアップデートを止めてしまった経験がある方は、半年ぶりのアップデートで署名が切れていた で書いた期限監視の仕組みも合わせて用意しておくと、次の更新が楽になります。
予想と逆だったこと
一連の確認を終えて、事前の想定と食い違っていた点が3つありました。
1つ目は、影響範囲が思っていたより狭かったことです。「9月に完全義務化」という理解のままだと、8月は全アプリの緊急対応月になります。実際には4か国で、グローバル展開は2027年です。慌てる必要があるかどうかは、自分の国別比率を1回集計すれば決まります。
2つ目は、それでも Google Play が対象に含まれていたことです。サイドロード規制の文脈で語られることが多い制度ですが、初期フェーズの対象ストア一覧の先頭に Play が入っています。「Play にしか出していないから関係ない」は成り立ちません。
3つ目が、いちばん意外でした。作業の律速になるのは締切ではなく、法人登録に必要な番号の発行待ちだったことです。技術的な作業はほぼゼロで、コードを1行も書き換える必要がありません。それなのに間に合わなくなる可能性があるとすれば、理由は書類の往復だけです。技術で解決できない種類の締切だと分かった時点で、着手の優先度を上げました。
締切前に踏む手順
数字を出したあとの流れは、こうなります。
Play Console から国別インストール CSV を書き出し、前掲のスクリプトで4か国比率を出します。5%を下回るなら、ここで作業は終わりです。
個人名義で登録するか法人名義で登録するかを決めます。法人を選ぶなら、D-U-N-S 番号の取得を先に開始してください(最大28日)。
リポジトリ側から android.package を集め、Play Console に並んでいるアプリと突き合わせます。どちらか一方にしか存在しない識別子が、この作業でいちばん重要な出力です。
ストアで公開しているアプリと、20台以内で足りる検証用パッケージを分けます。後者は限定配布アカウントへ寄せられます。
Android Developer Console で身元確認を済ませ、手順3で確定したアプリを登録します。
手順3までは確認作業で、後戻りしにくい操作が入るのは手順5だけです。逆に手順1を飛ばして手順5から始めると、登録しなくてよいものまで抱え込むことになります。
私が8月中にやると決めたこと
数字を出したうえで、こう決めました。
Google Play で配信している2本については、8月中に Android Developer Console の登録まで進めます。4か国比率が無視できる水準ではなく、かつ作業自体は数時間で終わるからです。名義は個人のまま進めます。法人名義に切り替える判断は、この締切とは切り離して考えるべきものだと整理しました。締切に追われて名義を決めるのは、順番が逆です。
検証用に配っているパッケージは、限定配布アカウントの20台という上限に収まるため、そちらへ寄せます。
そして最後に一つ。この記事に書いた日程・費用・対象国は、2026年8月11日時点で公式に案内されている内容です。この種の制度は運用開始の直前に細部が変わることがあります。実際に登録へ進む前に、必ず一次情報で現在の内容を確認してください。私自身、参照していたメモが「9月に完全義務化」のまま古くなっていたせいで、危うく優先順位を間違えるところでした。
まずやることは一つだけです。手元の国別インストール CSV を1本、この記事のスクリプトに通してみてください。出てきたパーセンテージが、8月をどう使うかを決めてくれます。
お読みいただきありがとうございました。