Google Play Console に「ネイティブコードを含む App Bundle にはデバッグシンボルがありません」と出たまま、何を ZIP にしてどこへアップロードするのか分からない――。本記事は .NET MAUI/Android(net9.0-android 前提)で生成される app_shared_libraries や mapping.txt の正体を解きほぐし、正しい ZIP 構成と Play Console 上の操作、よくある落とし穴、CI 連携までを一気通貫で整理します。
想定読者とゴール
本記事は .NET MAUI で AAB を配信しており、Play Console の警告を解消してクラッシュ/ANR のスタックトレースをシンボリケートしたい開発者を対象とします。最終的に以下を達成します。
- Release ビルドでネイティブデバッグシンボル(未圧縮
.so)を生成できる arm64-v8aとx86_64を正しく ZIP 化できる(余計な親フォルダー無し)- Play Console の「デバッグシンボル」欄に ZIP をアップロードできる
mapping.txt(R8/ProGuard)の取り扱いを理解し、別枠でアップロードできる
先に結論:何をどこへアップロードするか
最初に結論だけ示します。詳細は後述します。
| Play Console の項目 | アップロードするもの | 拡張子 | 生成場所(既定) | ポイント |
|---|---|---|---|---|
| デバッグシンボル(Native debug symbols) | フォルダー arm64-v8a と x86_64 を並べて ZIP 化 | .zip | obj\Release\net9.0-android\app_shared_libraries\ | .so.manifest は ZIP に入れなくてよい。ZIP のルート直下に ABI フォルダーが並ぶ構造にする。 |
| 難読化解除ファイル(Deobfuscation files) | mapping.txt | .txt(単体) | bin\Release\net9.0-android\mapping.txt | Java/Kotlin 側のスタックトレース用。ネイティブとは別枠でアップロードする。 |
用語の整理:ネイティブシンボルと mapping.txt は別モノ
- ネイティブデバッグシンボル:C/C++ やランタイムが生成する 未圧縮の
.so(ストリップ前のシンボルを含むライブラリ)。Play Console の「デバッグシンボル」に ZIP としてアップロードします。 - mapping.txt:R8/ProGuard の難読化解除用マップ。Java/Kotlin のクラッシュを可読化します。「難読化解除ファイル」に そのままアップロードします(ZIP 不要)。
前提条件
- .NET SDK 9 以降(ターゲットフレームワーク:
net9.0-android) - Release ビルドで AAB(App Bundle)を作成
- Play Console で該当バージョンコードのリリースが作成済み
手順 1:Release ビルドでネイティブシンボルを生成する
まずは生成を有効化します。.csproj または Directory.Build.props に以下を追加(または確認)してください。
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AndroidGenerateNativeDebugSymbols>true</AndroidGenerateNativeDebugSymbols>
<!-- 必要なら:ストリップを無効化(サイズ肥大に注意)
<AndroidStripNativeDebugSymbols>false</AndroidStripNativeDebugSymbols>
-->
</PropertyGroup>
この設定で Release ビルド時、各 ABI 向けにシンボル付きの .so が app_shared_libraries 配下へ複製されます。生成を確認するには、以下のいずれかでビルドします。
dotnet publish -c Release -f net9.0-android -p:AndroidPackageFormat=aab
ビルド後の代表的な出力ツリーは次のとおりです。
obj\
Release\
net9.0-android\
app_shared_libraries\
arm64-v8a\
libmonodroid.so
libxa-internal-api.so
lib{YourApp}.so
...(その他 .so)
.so.manifest
x86_64\
libmonodroid.so
lib{YourApp}.so
...(その他 .so)
.so.manifest
bin\
Release\
net9.0-android\
{YourApp}.aab
mapping.txt
補足:プロジェクトの ABI 設定によっては armeabi-v7a 等が現れる場合があります。AAB に含めた ABI は すべて ZIP に同梱してください(片方だけだと Play 側で「ABI が足りない」扱いになります)。
手順 2:アップロード用 ZIP を正しく作る
Play Console が期待する ZIP 構成は「ZIP のルート直下に ABI フォルダー(例:arm64-v8a、x86_64)が並び、その下に .so が置かれている」形です。余計な親ディレクトリを作ると弾かれます。
OK な例(推奨)
native-symbols-v42.zip
├─ arm64-v8a/
│ ├─ libmonodroid.so
│ ├─ lib{YourApp}.so
│ └─ ...
└─ x86_64/
├─ libmonodroid.so
├─ lib{YourApp}.so
└─ ...
NG 例(Play でエラーになりやすい)
| ZIP 内の構成 | 問題点 |
|---|---|
native-symbols-v42.zip > app_shared_libraries > arm64-v8a ... | 余計な親フォルダー(app_shared_libraries)が含まれている |
native-symbols-v42.zip > arm64-v8a > *.so(x86_64 等が無い) | AAB に含めた全 ABI を同梱していない |
native-symbols-v42.zip に .so.manifest だけ入っている | 必要なのは .so 本体。.manifest は Play で不要 |
Windows(PowerShell)での作り方
プロジェクトルートから以下を実行します。obj\Release\net9.0-android\app_shared_libraries の直下にある フォルダーを指定するのがコツです。
$base = "obj\Release\net9.0-android\app_shared_libraries"
Compress-Archive -Path "$base\arm64-v8a","$base\x86_64" -DestinationPath "native-symbols-v42.zip" -Force
同梱する ABI が増えた場合は配列に追加してください(例:"$base\armeabi-v7a")。
ZIP の中身を確認する
誤った親フォルダーが含まれていないか、作成後に中身を必ず確認しましょう。Windows でも 7-Zip 等で階層を見れば一発ですが、コマンドで確認するなら OS に応じて次のようにチェックできます。
:: PowerShell(ZIP のルートに arm64-v8a/ x86_64/ が見えること)
[IO.Compression.ZipFile]::OpenRead("native-symbols-v42.zip").Entries |
ForEach-Object FullName | Select-Object -First 5
手順 3:Play Console にアップロードする
- Play Console で該当アプリを開き、アプリ > リリース から対象トラック(内部テスト/アルファ/製品版 など)を選び、対象のリリース編集画面へ進みます。
- 画面下部にある デバッグシンボル セクションで、作成した
native-symbols-*.zipをアップロードします。 - 同ページの 難読化解除ファイル(Deobfuscation files)セクションにて、
bin\Release\net9.0-android\mapping.txtをそのままアップロードします(ZIP 化不要)。 - 変更を保存してリリースを更新します。処理が完了すると、対象バージョンコードのクラッシュ/ANR が順次シンボリケートされます。
重要:シンボルは バージョンコード ごとにひも付けられます。AAB を差し替える場合は versionCode をインクリメントするのが安全です(同一コードの差し替えは紐付け不整合の原因)。
.manifest は ZIP に入れるべき?
結論:入れなくて構いません。.so.manifest はビルド成果物のメタ情報で、Play Console の「デバッグシンボル」要件は「ABI フォルダー配下に未圧縮の .so があること」です。.manifest は ZIP から除外しても処理に影響しません(プロジェクトから削除する必要もありません)。
よくあるつまずきと対処法
| 症状/エラーメッセージ | 原因 | 対処 |
|---|---|---|
| 「シンボルファイルの形式が無効」 | ZIP のルートに余計な親フォルダーがいる / .so が直下に置かれている | ZIP のトップに arm64-v8a、x86_64 など ABI フォルダーが並ぶ形に修正 |
| 「対象 ABI が足りない」 | AAB に含めた ABI と ZIP の ABI が一致していない | プロジェクトの ABI 設定を確認し、ZIP に同じ ABI 群を入れる |
| クラッシュがシンボリケートされない | バージョンコード不一致 / シンボル処理未完了 | 対象クラッシュの versionCode を確認。必要なら再アップロード。以後は常に versionCode をインクリメント |
Release なのに app_shared_libraries が空 | <AndroidGenerateNativeDebugSymbols> が false / Debug でビルドしている | Release 構成に切り替え、該当プロパティを true に設定 |
mapping.txt が見つからない | R8/ProGuard 無効 / 生成パスを見誤っている | bin\Release\net9.0-android を確認。なければ R8 を有効化(通常は既定で有効) |
| 「同じファイルが既にアップロードされています」 | 同じ ZIP を再送 | 不要。差し替え時は ZIP を作り直す(内容が違えば自動で置き換えられる) |
生成・アップロードの一連作業を再現性高く回す
手作業での取り違えを防ぐため、次のような小さなスクリプトで ZIP 作成を自動化すると堅牢です。
PowerShell スニペット(プロジェクト直下で実行)
# 1. Release Publish(AAB と mapping.txt を生成)
dotnet publish -c Release -f net9.0-android -p:AndroidPackageFormat=aab
# 2. ネイティブシンボル ZIP を作成
$base = "obj\Release\net9.0-android\app_shared_libraries"
$abis = @("arm64-v8a","x86_64") # 追加 ABI があれば増やす
$paths = $abis | ForEach-Object { Join-Path $base $_ }
Compress-Archive -Path $paths -DestinationPath "native-symbols-$((Get-Date).ToString('yyyyMMdd-HHmm')).zip" -Force
# 3. 出力の所在を表示
Write-Host "AAB: bin\Release\net9.0-android*.aab"
Write-Host "Symbols ZIP: native-symbols-*.zip"
Write-Host "mapping.txt: bin\Release\net9.0-android\mapping.txt"
GitHub Actions の例(成果物を固めて配布用に)
name: build-android
on: [workflow_dispatch]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Publish AAB
run: dotnet publish -c Release -f net9.0-android -p:AndroidPackageFormat=aab
- name: Zip Native Symbols
shell: pwsh
run: |
$base = "obj\Release\net9.0-android\app_shared_libraries"
$abis = @("arm64-v8a","x86_64")
$paths = $abis | ForEach-Object { Join-Path $base $_ }
Compress-Archive -Path $paths -DestinationPath "native-symbols.zip" -Force
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: android-release
path: |
bin\Release\net9.0-android\*.aab
bin\Release\net9.0-android\mapping.txt
native-symbols.zip
このように AAB、mapping.txt、native-symbols.zip をセットで保管しておくと、後から Play Console にアップロードし直す時も迷いません。
なぜ app_shared_libraries を ZIP にするのか
.NET MAUI の Android パイプラインは、パッケージング中にストリップ前(またはストリップ直後)の .so を app_shared_libraries に複製します。Play Console は「ネイティブスタックのシンボリケーション」に未圧縮の .so を必要とするため、この複製がそのまま「デバッグシンボル」として機能します。.so.manifest は各 .so の列挙リストですが、Play 側は ZIP を展開して ABI 配下の .so を直接読み取るため、.manifest 自体は不要です。
プロジェクト設定のベストプラクティス
- Release のみに限定して生成:Debug では不要。条件付き
PropertyGroupを用いる(本記事のサンプル参照)。 - バージョンコードを毎回インクリメント:シンボルとの紐付けが崩れる事故を防止。
- ABI を意識:
arm64-v8aが主流。エミュレーター検証用にx86_64を含めるなら、ZIP も両方同梱。 - サイズとプライバシー:未圧縮
.soは大きい場合があります。リポジトリには含めず、アーティファクト管理で保管。
チェックリスト(公開前に確認)
<AndroidGenerateNativeDebugSymbols>true</...>が Release で有効になっている- Publish 後、
obj\Release\net9.0-android\app_shared_librariesに ABI ごとの.soが見える - ZIP のトップレベルに
arm64-v8a/とx86_64/(必要に応じて追加 ABI)だけが並ぶ .so.manifestは ZIP に含めない(含めても通る場合はあるが要件外)bin\Release\net9.0-android\mapping.txtを別枠「難読化解除ファイル」でアップロード- Play Console の対象リリースがアップロードした AAB の
versionCodeと一致している - クラッシュが発生したら該当バージョンのレポートでシンボリケートされているか確認
発展トピック:C/C++ の独自ネイティブコードを含む場合
サードパーティや独自の NDK ライブラリ(libxyz.so)をバインド/同梱している場合でも、AndroidGenerateNativeDebugSymbols により app_shared_libraries 配下に未圧縮の .so が現れます。基本的にそのまま ZIP に含めれば OK です。もし別形式のシンボル(例:Breakpad/NDK の .sym)を運用しているプロジェクトでも、Play Console は「未圧縮 .so」でのアップロードを最も素直に受け付けます。迷ったら 実際に AAB に含まれている ABI と 同じ ABI の .so を ZIP に集めることを優先しましょう。
トラブルシュート:ケース別の深掘り
ケースA:app_shared_libraries がない/空
Release でビルドしているか、ターゲットフレームワークが net9.0-android か、プロパティ名のタイプミスがないかを確認してください。CI では構成が Debug のままになっていることがよくあります。
ケースB:ABI を減らしたらクラッシュが読めなくなった
過去のバージョンに対しては、当時の ABI 群に対応したシンボルが必要です。特定 ABI を除外した新バージョンで検証しても、旧バージョンのクラッシュには影響しません。対象クラッシュの versionCode と ABI を必ず確認します。
ケースC:versionCode を変えずに AAB を差し替えた
Play 側のシンボル・クラッシュ紐付けは versionCode に依存します。差し替えでバイナリが変わると、スタックの解決に失敗することがあります。差し替えは避け、ビルド更新時は versionCode をインクリメントしましょう。
ケースD:リリース画面のどこに「デバッグシンボル」があるか分からない
Play Console の UI はときどき名称や配置が変わりますが、原則として「リリースを作成/編集」画面の下部に付属セクションが並びます。項目名は「デバッグシンボル」「Native debug symbols」「難読化解除ファイル(Deobfuscation files)」などです。見つからない場合は対象のリリース編集画面を開いて下までスクロールしてください。
実運用のコツ
- クラッシュ再現用の小さなスイッチ:内部テストビルドに限定した「強制クラッシュ」ボタンを用意すると、シンボル適用の検証が容易です。
- アーティファクトをバージョン単位で束ねる:
{versionCode}/のディレクトリに.aab、mapping.txt、native-symbols.zipを保管しておくと調査が速い。 - ZIP 名にバージョンを刻む:例
native-symbols-v123.zip。Play 上でも識別しやすくなります。
FAQ
- Q:
.so.manifestは ZIP に入れるべき?
A:不要です。.so群だけで充分です(入っていても通る場合はありますが、要件ではありません)。 - Q:
mapping.txtは ZIP にまとめる?
A:いいえ。単体ファイルを「難読化解除ファイル」にアップロードします。 - Q:どのタイミングでアップロードすべき?
A:対象 AAB と同じversionCodeのリリース作成時(または直後)が最適。遅れても同一コードなら後追いでシンボリケーションされます。 - Q:ABI が
arm64-v8aのみでも良い?
A:アプリのサポート方針次第です。AAB に含めた ABI と ZIP の ABI が一致していれば問題ありません。
まとめ
Play Console の「ネイティブコードを含む App Bundle にはデバッグシンボルがありません」という警告は、.NET MAUI/Android でも手順さえ把握すれば確実に解消できます。ポイントは ① Release で AndroidGenerateNativeDebugSymbols を有効化して未圧縮 .so を生成、② app_shared_libraries 直下の ABI フォルダーをそのまま ZIP 化(余計な親階層は作らない)、③ mapping.txt は別枠でアップロード、の三点です。以降、バージョンコードを正しく運用し、AAB・mapping・シンボル ZIP をワンセットで管理しておけば、クラッシュ解析の生産性は大きく向上します。
付録:すぐ使えるテンプレ集
プロジェクト設定テンプレ(Release のみ)
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<TargetFramework>net9.0-android</TargetFramework>
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidGenerateNativeDebugSymbols>true</AndroidGenerateNativeDebugSymbols>
<!-- 必要に応じて:AOT/PGO 等は別設定 -->
</PropertyGroup>
ZIP に入れる/入れない早見表
| 対象 | 入れる? | 理由 |
|---|---|---|
arm64-v8a\*.so | 入れる | ネイティブシンボル本体 |
x86_64\*.so | 入れる | 同上 |
*.so.manifest | 入れない(任意) | Play で要求されていないメタ情報 |
mapping.txt | 入れない | 難読化解除ファイルは別セクションに単体でアップロード |
{YourApp}.aab | 入れない | リリース本体はシンボル ZIP と別管理 |
チェックコマンド断片(目視確認用)
# PowerShell: ZIP 直下の最初の 10 件を表示
[IO.Compression.ZipFile]::OpenRead("native-symbols.zip").Entries |
Select-Object -First 10 | ForEach-Object FullName
以上の手順で、Play Console の警告は解消され、クラッシュ/ANR レポートは可読なスタックトレースへと復元されます。日々の配信フローに組み込み、安定した障害解析基盤を構築しましょう。

コメント