.NET 7 から .NET 8 に移行した .NET MAUI(Android)で、Debug は成功するのに Release の発行(Publish)だけ失敗することがあります。原因を見誤りやすい AOT 事前コンパイルの落とし穴と、まず発行を通すための具体策をまとめます。
起きている現象:Debug は通るのに Release の Publish だけ落ちる
.NET 7 では問題なく動いていた MAUI(Android)プロジェクトを .NET 8 に移行した直後、次のような状況にハマることがあります。
- Debug ビルド(端末実行・エミュレータ実行)は成功して動作もする
- Release で「発行(Publish)」するとビルド途中で停止して失敗する
- ログには AOT(Ahead-Of-Time)関連のエラーが複数出る
代表的なエラーメッセージの例は次のとおりです(実際の DLL 名やパスは環境により変わります)。
Precompiling failed ... with exit code 1
Can not open image ...\aot-in\xxx.dll
ポイントは、「Release の Publish でだけ AOT が走り、その AOT 処理が複数 DLL で連鎖的に失敗して止まる」という構図です。Debug は AOT の前提条件がそもそも違うため、同じプロジェクトでも問題が表面化しません。
| 状況 | 見え方 | 開発者が困るポイント |
|---|---|---|
| Debug は成功 | 端末起動もできる/動作確認も一応できる | 「移行は成功した」と思い込みやすい |
| Release の Publish が失敗 | AOT の Precompiling で exit code 1、aot-in の DLL が開けない | ストア提出用の AAB / APK が作れない、CI も止まる |
なぜ Release だけ失敗するのか:Debug と Release は“別物”
MAUI(Android)に限らず、.NET のモバイル発行は「Debug と Release でやっていることがまったく違う」ケースが多いです。特に .NET 8 への移行直後は、ツールチェーン更新や既定値の変化が重なり、Release のみ失敗しやすくなります。
| 観点 | Debug | Release(Publish) |
|---|---|---|
| 最適化 | 最適化は控えめ(デバッグ優先) | 最適化が強い(実行速度・サイズ最適化) |
| AOT(事前コンパイル) | 無効または限定的なことが多い | 有効になりやすい(起動速度や実行性能のため) |
| リンク(トリミング) | 緩い/無効のことがある | 強く働くことがある(不要コード削除) |
| 成果物 | ローカル実行中心 | 署名・パッケージングを含む「配布前提」 |
今回のケースは、Release Publish 時に AOT が有効になっていることが決定打になります。ログに aot-in が出ている時点で、AOT 用の入力アセンブリ(DLL)を集めて、ネイティブコードへ事前コンパイルする工程に入っています。
原因:Release 構成で AOT が有効になり、AOT 工程が失敗している
結論から言うと、原因はかなりシンプルです。
Release 構成(特に net8.0-android の Release)で AOT が有効になっており、その AOT 処理が何らかの理由で失敗している。
Can not open image ...\aot-in\xxx.dll は、AOT が参照すべき DLL を開けない(読み取れない/見つからない/途中で壊れている/ロックされている)状況で出やすいメッセージです。1 つの DLL で失敗すると、依存関係に引っ張られて複数 DLL の AOT が連鎖的に失敗し、最終的に Publish が止まります。
AOT が絡むと失敗が“複数 DLL 同時”に見える理由
AOT は「アプリ全体の実行を速くする」目的で、複数のアセンブリをまとめて処理します。つまり、AOT 工程では次のようなことが起きます。
- 入力用フォルダ(例:
...\obj\Release\net8.0-android\...\aot-in\)に DLL が集約される - AOT コンパイラが DLL を順に読み込んで事前コンパイルする
- どこか 1 つでも読み込みに失敗すると、その後もエラーが連鎖しやすい
そのためログだけを見ると「いろんな DLL が壊れているの?」と見えますが、実際には 最初に失敗した 1 点(環境・ファイル・設定)が引き金になっていることが多いです。
最短の解決策:Release の AOT を無効化して Publish を通す
まず「Release の発行を通す」ことを優先するなら、対処は次の 6 手順が最短で確実です。ポイントは、AOT を無効化したうえで、ビルド生成物を物理的にクリーンにすることです。
- プロジェクトのプロパティを開く
- Android → Options(または該当項目)へ進む
- AOT(事前コンパイル)を Release / net8.0-android で無効化(チェックを外す)
- Visual Studio をいったん閉じる
- プロジェクトフォルダの
binとobjを削除 - Visual Studio を開き直して Rebuild → Release で発行(Publish)
これで Release の発行が通るようになります。
手順の “ハマりどころ” を先に潰す
上の手順は一見シンプルですが、移行直後ほど「やったはずなのに直らない」状態になりがちです。実務でよくある落とし穴を、チェックしやすい形でまとめます。
| よくあるミス | なぜ起きるか | 回避のコツ |
|---|---|---|
| Debug 側だけ AOT を切っている | 設定が構成(Debug/Release)やターゲット(net8.0-android)ごとに分かれている | Release と net8.0-android を明示的に選んだ状態で設定する |
| Visual Studio を閉じずに bin/obj を削除 | ビルド関連ファイルがロックされ、削除が中途半端になる | 一度 VS を完全に閉じてから削除する(タスクが残っていないかも確認) |
| Clean だけで済ませる | Clean では消えない中間生成物が残ることがある | フォルダごと削除が最も確実(特に移行直後は重要) |
| 同期フォルダ(OneDrive 等)配下で作業 | 同期処理で DLL が一瞬ロックされ「開けない」が発生しやすい | ローカルの短いパス(例:C:\src\)へ移動して再試行 |
bin/obj を削除して Rebuild する理由:移行直後は“古い残骸”が一番強い敵
「AOT を無効にしたのに、まだ同じエラーが出る」場合、原因の多くは 古い中間生成物が残っていることです。移行前の .NET 7 の残骸、Android のビルドキャッシュ、インクリメンタルビルドの差分などが混ざると、AOT の入力フォルダに不整合が出やすくなります。
Visual Studio の Clean は便利ですが、移行直後のトラブル対応では “完全消去” にならないことがあります。そこで、フォルダごと削除してしまうのが最短です。
GUI 操作が面倒な場合は、次のようにコマンドで削除しても構いません(Windows の例)。
cd プロジェクトのルートフォルダ
rmdir /s /q bin
rmdir /s /q obj
また、CLI で Publish を回す運用(CI やローカル検証)をしている場合は、クリーン → Publishの流れに寄せると再現性が上がります。
dotnet clean
dotnet publish -c Release -f net8.0-android
Publish が通ったら確認したいこと:成果物と動作の最低ライン
AOT を無効にして Publish が成功したら、次は “作れたけど動かない” を防ぐために最低限の確認をします。特にストア提出前提なら、起動と主要画面だけでも押さえるのが安全です。
- Publish の出力先に APK/AAB が生成されている(運用に合わせて確認)
- 実機でインストールして起動できる
- 起動直後に落ちない(初期化・DI・設定読み込みなど)
- ログイン/通信/DB/ファイルアクセスなど、アプリの“中核”を 1 回は通す
Debug で動いていたとしても、Release はリンクや最適化の影響で挙動が変わることがあるため、Release で最低限のスモークテストを行うのがおすすめです。
AOT を無効にする影響:メリットとデメリットを理解して使い分ける
今回の対処は「まず発行を通す」ための実務的な回避策として非常に有効ですが、AOT を切る以上、トレードオフがあります。ざっくり言うと、起動速度・実行性能・特定の最適化に影響が出る可能性があります。
| 項目 | AOT 有効 | AOT 無効 |
|---|---|---|
| 起動速度 | 改善しやすい | 端末やアプリ規模によっては遅く感じることがある |
| 実行性能 | 改善するケースがある | JIT 相当の処理負荷が増える可能性 |
| ビルド時間 | 長くなりがち(AOT 工程が重い) | 短くなりやすい |
| Publish の安定性 | 環境差・キャッシュ差の影響を受けやすい | 通りやすい(今回の回避策) |
つまり、開発・検証・提出を止めないための一時回避策としては AOT 無効化が優秀で、最終的な品質(体感速度やパフォーマンス)を追求するフェーズで AOT を再検討する、という進め方が現実的です。
根本対応を目指すなら:AOT を再度有効化して検証するまでの現実的な進め方
現場では「Publish を通す」ことが最優先になることが多い一方で、リリースが近づくほど “性能” や “最適化” を取り戻したくなります。そこで、次のような段階的な進め方が失敗しづらいです。
段階的アプローチ
- まずは AOT 無効で Release Publish を安定させ、配布やストア審査に乗せられる状態にする
- その上で環境(SDK / workload / Visual Studio)を更新し、ビルドが再現可能な状態を作る
- 準備ができたら AOT を再度有効化して Publish を試し、失敗したらログを絞って原因を特定する
“AOT を戻す前” に整えておくと効くチェックリスト
次の項目は、AOT の失敗を減らす方向に効くことが多いです(移行直後ほど差が出ます)。
| チェック項目 | 狙い | 実務メモ |
|---|---|---|
| Visual Studio / .NET SDK / Android workload を更新 | ビルドツールの不整合を減らす | 複数マシンや CI があるなら、バージョン差をなくす |
| プロジェクトのパスを短くする | 生成物のパス長・ツールの制約を回避 | C:\src\ のような短いルートへ置くと改善することがある |
| 同期フォルダ配下を避ける | ビルド中のファイルロックや競合を避ける | aot-in の DLL を “開けない” 系の症状と相性が悪い |
| bin/obj を削除してから試す運用に固定 | キャッシュ由来の再現しない失敗を減らす | 「一度通ったのに次は落ちる」を避ける |
| セキュリティソフトのリアルタイムスキャンを確認 | 生成 DLL の読み取り失敗・ロックを減らす | 許可設定や除外対象の検討(組織ポリシーに従う) |
このチェックを踏んだうえで AOT を再有効化すると、単に「運が悪いと落ちる」状態から、原因を切り分けやすい状態に持っていけます。
補足:UI ではなく csproj / コマンドで切り替えたい場合
チーム開発や CI では、Visual Studio の UI 変更だけだと設定差が出ることがあります。環境を揃えたい場合は、Release の条件付きで AOT を制御できる形に寄せると運用しやすいです。
一般的には、Release 構成向けの設定をプロジェクトファイルで条件付きにしておくと安全です(設定名称はプロジェクト種類や環境により表記が異なる場合があります)。
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- Release だけ AOT を無効化(発行を通すための回避策) -->
<RunAOTCompilation>false</RunAOTCompilation>
</PropertyGroup>
CLI で一時的に切り替えるなら、Publish の引数で上書きする運用も可能です。
dotnet publish -c Release -f net8.0-android /p:RunAOTCompilation=false
「まずは出したい」「CI だけ一時的に回避したい」といった状況では、こうした切り替えが役に立ちます。
よくある質問
Debug は動くのに Release の Publish が落ちるのは、コードが悪いの?
今回のパターンは、アプリのロジックが直接の原因というより、Release 時に追加で走る工程(AOT)が環境や生成物の状態で失敗しているケースが多いです。まずは AOT を切って Publish を通し、その後に環境や設定の整備へ進むと手戻りが減ります。
AOT を無効にしたまま公開しても問題ない?
公開自体は可能なケースが多いですが、起動速度や体感性能に影響が出る可能性があります。ユーザー体験を重視するアプリほど、最終フェーズで AOT を再検討する価値があります。とはいえ、リリースを止めないことが最優先なら、まず無効化で前に進む判断は十分現実的です。
bin/obj を削除しても直らない場合は?
AOT 無効化が確実に反映されているか(Release / net8.0-android で切れているか)をまず確認し、それでもダメならプロジェクトの配置場所(同期フォルダ・深いパス)や環境差(SDK / workload / VS)を疑うのが近道です。特に移行直後は、同じマシン内でもキャッシュの影響で再現性が揺れやすいので、「完全クリーン」「短いパス」「同期外」の 3 点を揃えると改善することが多いです。
まとめ:Release Publish の AOT エラーは「まず切って通す」が最短
.NET 7 → .NET 8 移行直後の MAUI(Android)で、Debug は成功するのに Release の発行(Publish)だけ失敗し、Precompiling failed ... exit code 1 や Can not open image ...\aot-in\xxx.dll が出る場合、原因は Release 構成で AOT が有効になっていて、その AOT 工程が失敗していることがほとんどです。
対処は、Release の AOT を無効化 → Visual Studio を閉じる → bin/obj を削除 → Rebuild → Publish。まず発行を止めない状態を作り、必要に応じて環境を整備してから AOT を再度有効化して検証する、という順番が最も手戻りを減らせます。

コメント