Visual StudioでAndroid/.NET MAUIを.NET 8から.NET 9へ更新すると、「TargetPlatformVersion 34.0は無効(有効:35.0)」のビルドエラーが出ることがあります。多くはcsprojに残った古いターゲット指定が原因。net9.0-androidへの修正、manifestのtargetSdkVersion、NU1608/XA0141警告の考え方をまとめます。
発生するエラーと「いま何を直すべきか」
まずは状況を整理します。.NET 8時代にAndroidのターゲットを明示していたプロジェクトを、プロパティ画面などで.NET 9へ上げると、次のようなエラーが出ることがあります。
34.0 is not a valid TargetPlatformVersion for Android. Valid versions include: 35.0
このエラーは「.NET 9のAndroidビルドが、API 34をターゲットプラットフォームとして受け付けない(少なくともその構成では35しか選べない)」という意味です。多くのケースでは、TargetFramework(TFM)に残った「android34」の指定が原因で、TargetPlatformVersionを自分で35に“合わせに行く”というより、指定を外してテンプレート推奨の形に戻すのが近道です。
| 論点 | 結論(一般的なMAUI/Androidアプリ) | やること | 注意点 |
|---|---|---|---|
| Target OS(TargetPlatformVersion)を34→35に変えるべき? | 「34固定」をやめて、net9.0-androidに戻すのが基本 | csprojのTargetFrameworkを修正 | 特殊要件でAPI固定が必要なら.NET 8維持が現実的 |
| AndroidManifest.xmlのandroid:targetSdkVersionも35に変えるべき? | 通常は手で変更しなくてよい | 出力manifestでtargetSdkVersionが自動反映されるか確認 | 手書きmanifestは「マージ」の衝突要因になりやすい |
| NU1608などNuGet警告 | アプリが正常動作しているなら当面は許容し、更新で追従 | 関連パッケージをまとめて更新・依存関係を可視化 | CIでWarningsAsErrorsを有効にしている場合は対処が必要 |
| XA0141(libopenal32.soのアラインメント) | 多くは配布側(ライブラリ)の問題。更新が最優先 | 該当パッケージの更新/差し替え/不要なら除外 | アプリ側設定だけで根治できない場合がある |
TargetFrameworkとTargetPlatformVersionを混同しない
この手の移行トラブルは、「TargetFramework(TFM)」「TargetPlatformVersion」「SupportedOSPlatformVersion」「AndroidManifestのtargetSdkVersion」という似た言葉が並ぶことで混乱しがちです。まず役割を切り分けると判断が一気に楽になります。
| 設定/概念 | 主に書く場所 | 役割(ざっくり) | 移行時のポイント |
|---|---|---|---|
| TargetFramework(例:net9.0-android) | csproj | .NETとプラットフォームの組み合わせを決める | まずここをテンプレート推奨へ戻す |
| TargetPlatformVersion(例:35.0) | 内部的/明示指定も可能 | Android側のAPIレベル選択に影響 | .NET 9では明示指定が足を引っ張ることがある |
| SupportedOSPlatformVersion | csproj | 「このOSバージョン以上を対象」としてAPI警告や一部設定に影響 | 必要に応じて設定(例:31.0)。ただし無闇に上げない |
| android:targetSdkVersion | AndroidManifest.xml(最終的には生成物) | Androidの互換性挙動(ターゲットAPI)を決める | 通常はビルドで自動生成・マージされる |
ポイントは、.NET 9のAndroid/MAUIプロジェクトでは「TFMにAPI番号を固定で書かない」運用が基本になっていることです。つまり、net9.0-android35のような明示指定を常用するより、net9.0-androidとしてSDKの既定値に任せるほうが、将来の更新やテンプレートの意図に沿います。
.NET MAUIで複数のTargetFrameworksを使っている場合の注意
MAUIアプリはAndroidだけでなく、iOSやMac Catalystなど複数のTFMを同時にターゲットにする構成が一般的です。その場合、<TargetFramework>ではなく<TargetFrameworks>になっていることが多く、Androidだけが古い指定のまま残っていると、今回のようなエラーに繋がります。
例えば、次のようにAndroidはnet9.0-androidへ戻し、必要に応じてSupportedOSPlatformVersionを条件付きで設定します。
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
31.0
この形にしておくと、将来のSDK更新でAndroid側の既定値(targetSdkVersionなど)が変わっても追従しやすくなり、プロジェクトファイルの差分も小さく保てます。
ビルドエラーの原因:プロパティ画面の自動変換で「android34」が残る
.NET 8でnet8.0-android34のように書かれていたプロジェクトを、Visual Studioの更新・移行操作で.NET 9へ上げると、プロジェクトファイルが機械的に置換されて次のような状態になることがあります。
<!-- ありがちなNG例:.NET 9なのにandroid34が残る -->
<PropertyGroup>
<TargetFramework>net9.0-android34</TargetFramework>
</PropertyGroup>
この状態だと、.NET 9側が想定する有効なTargetPlatformVersion(例:35.0)と食い違い、エラーになります。ここで「じゃあ34を35に書き換えればいいのでは?」と考えがちですが、テンプレート推奨の形に戻すなら、やるべきは次です。
<!-- 推奨:.NET 9テンプレート寄りの指定に戻す -->
<PropertyGroup>
<TargetFramework>net9.0-android</TargetFramework>
</PropertyGroup>
実際、.NET 9のテンプレートから新規Android/MAUIプロジェクトを作成すると、TFMはこの形になっていることが多く、ビルド時に最終manifestへandroid:targetSdkVersionが自動的に埋め込まれます。つまり、プロジェクト側で中途半端なAPI番号を抱え続ける必要はありません。
最小限の修正手順
- プロジェクトの
.csprojを直接開く(プロパティ画面だけで完結させない) net9.0-android34のような末尾のAPI番号を削り、net9.0-androidにする- いったん
binとobjを削除してクリーンビルドする - 必要なら
SupportedOSPlatformVersionを設定する(例:31.0)
Android SDK(API 35)が導入されているかも確認
.NET 9側の既定がAPI 35(Android 15)相当になっている環境では、Android SDKのプラットフォームやビルドツールが不足していると、別のエラーで止まることがあります。Visual Studioの「Android SDK Manager」でAPI 35が入っているか確認するか、CLIでワークロード更新を行い、環境を整えましょう。
dotnet workload list
dotnet workload update
MAUIプロジェクトの場合は、AndroidだけでなくMAUIワークロードの更新も合わせて行うと依存関係が安定しやすくなります。
削除すべきはTargetFrameworkだけとは限りません。過去に試行錯誤した結果、次のようなプロパティが残っていると、意図しない挙動を招くことがあります。
<TargetPlatformVersion>34.0</TargetPlatformVersion>のような明示指定<AndroidTargetSdkVersion>など、古い記事を参考に追加したカスタム指定- 条件付きPropertyGroupで、構成ごとにバージョンが食い違っているケース
AndroidManifest.xmlのtargetSdkVersionは「手で35にする」必要がある?
結論から言うと、通常はAndroidManifest.xmlのandroid:targetSdkVersionを手書きで35へ変更する必然性は低いです。理由はシンプルで、.NET MAUIやSDKスタイルのAndroidプロジェクトでは、manifestは複数のソース(プロジェクト、依存ライブラリ、テンプレート)をマージして最終的なmanifestを生成するためです。
多くの場合、あなたが編集するのは「入力用のAndroidManifest」であり、実際にapk/aabへ入るのはビルド中に生成される「出力manifest」です。確認したいのは後者です。
確認すべき場所:生成されたmanifest
ビルド後、次のようなパス配下に生成物のmanifestが出力されます(構成や環境により多少変わります)。
obj\\Release\\net9.0-android\\android\\manifest\\AndroidManifest.xmlobj\\Debug\\net9.0-android\\android\\manifest\\AndroidManifest.xml
このファイルを開き、android:targetSdkVersion="35"が入っていれば、少なくともビルド結果としては「.NET 9が期待するターゲット」で出力できています。逆に、手書きのAndroidManifest.xmlに無理にtargetSdkVersionを固定すると、次のようなデメリットが出ることがあります。
- 依存ライブラリが要求する属性と衝突してマージエラーになりやすい
- SDKやMAUIの更新で既定値が変わったときに追従しづらい
- 意図せず古いtargetSdkVersionに固定され、後から原因調査が難しくなる
どうしても手書きmanifestで制御したい事情(企業内のポリシー、特定ストア要件、特定SDKの制約など)がある場合のみ、「最終的な生成物を確認しながら」慎重に適用するのがおすすめです。
SupportedOSPlatformVersionは何のために設定する?
.NETのSDKスタイルプロジェクトでは、SupportedOSPlatformVersionは「このプロジェクトがサポートするOSの最小バージョン」をコンパイラやアナライザに伝える役割があります。これを適切に設定しておくと、例えば「特定APIはこのOS以上でないと使えない」といった警告が出て、実装の事故を減らせます。
一方で、ここをむやみに上げすぎると、アプリが動作できる端末範囲を狭める判断になり得ます。一例として31.0を指定するのは、
- 「Android 12(API 31)以上しかサポートしない」設計にしたい
- 古いAPI互換の分岐を減らして実装をシンプルにしたい
- 利用するSDKが新しいAPI前提である
といった合理的な理由がある場合に向きます。逆に、広い端末を狙う一般公開アプリでは、テンプレート既定値のまま(またはプロジェクト要件に合わせて別の値)にするほうが安全です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-android</TargetFramework>
<!-- 必要に応じて:この例ではAndroid 12以上を想定 -->
<SupportedOSPlatformVersion>31.0</SupportedOSPlatformVersion>
</PropertyGroup>
</Project>
ここで押さえておきたいのは、SupportedOSPlatformVersionは「targetSdkVersionを35にしろ」という意味ではないことです。SupportedOSPlatformVersionはあくまで「サポート範囲(最小)」の概念で、targetSdkVersionは「ターゲットAPI(互換性挙動)」の概念です。役割が違うので、片方を変えたからもう片方も必ず変える、という関係ではありません。
.NET 9移行後に出がちなNU1608警告の読み解き方
.NET 9へ更新してビルドが通るようになった一方で、NU1608などの警告が大量に出るケースがあります。NU1608は一言でいうと、依存関係が要求するバージョン範囲と、実際に解決されたバージョンが一致していないことを知らせる警告です。
例えば典型的には、AndroidX系(Xamarin.AndroidX.*)のあるパッケージが「2.8.5.x未満を想定」と指定しているのに、他の依存関係の都合で「2.8.7.x」が入ってしまう、といった形で発生します。これは「動かない」と断定できるものではありませんが、放置し続けると将来的に不具合の温床になり得ます。
警告の緊急度を判断する基準
NU1608を見たときに、まず「全部すぐ直すべきか?」と焦りがちですが、実務では次の観点で切り分けると判断がしやすいです。
| 状況 | 優先度 | 理由 | おすすめアクション |
|---|---|---|---|
| ビルドは成功し、実機でも主要機能が問題なく動く | 低〜中 | 警告は将来リスクだが、直近の障害とは限らない | まずは更新で解消する方針で様子見 |
| 特定画面でクラッシュ/挙動不審が出る | 高 | 依存関係の不整合が実害に繋がっている可能性 | 関連パッケージを揃える/ダウングレード含め検討 |
| CIでWarningsAsErrorsが有効でビルドが落ちる | 高 | 警告でも出荷プロセスが止まる | 一時的にNoWarn、恒久的には依存関係整理 |
| ストア審査/セキュリティ監査で依存関係の整合性を求められる | 中〜高 | 監査観点で「不整合」が指摘されることがある | 可能な範囲で揃える。難しければ根拠を残す |
原因の多くは「.NET 9対応の移行期ギャップ」
NU1608が増える原因は、個別の開発ミスというより、移行期に起きがちな構造要因です。
- 一部のXamarin.AndroidX系パッケージの依存関係範囲が、まだ最新構成に追従しきれていない
- MAUIや周辺SDKを先に上げたことで、間接依存(transitive dependency)が想定より新しい版に引っ張られる
- プロジェクト側で明示参照しているパッケージが古く、解決結果がチグハグになる
このため、対処も「アプリのコードを直す」というより、依存関係の整理が中心になります。
まずやるべき:依存関係を可視化する
警告を見て闇雲にパッケージを上げ下げする前に、今どう解決されているかを一覧で把握すると効率が上がります。
dotnet list package --include-transitive
出力結果を見て、警告に出ているパッケージ(例:LiveData、Navigation、Lifecycleなど)がどこから入ってきているかを確認します。特定のパッケージが古い版で固定されているなら、そこを更新するだけで一気に収束するケースがあります。
現実的な落としどころ:段階的に揃える
NU1608を「0にする」こと自体は目標として良いのですが、移行直後は次の順で対処すると、手戻りが少なくなります。
- MAUI関連パッケージを同じ系列に揃える(例:9.0.xなら9.0.xで統一)
- AndroidX系は、警告に出ている“周辺一式”をまとめて更新する(断片的に1つだけ更新しない)
- それでも残る警告は、実害がないか(実機/テスト)を確認してから判断
具体例として、.NET MAUIを使っている場合は、Microsoft.Maui / Microsoft.Maui.Controls などテンプレートが参照するMAUI関連NuGetを、同じ9.0.x系列へまとめて更新すると、依存関係の揺れが減ります。実際に、9.0.10など同一系列へ揃えることでビルドエラーが解消し、残るのは警告の整理だけ、という状態に持ち込めるケースがあります。
依存関係を揃える設計としては、複数プロジェクトを抱える場合はDirectory.Packages.propsでバージョンを集中管理するのも有効です。更新の単位を小さくでき、原因追跡も楽になります。
どうしてもノイズになる場合の一時対応
「警告は理解したが、今は作業を進めたい」「CIのログが埋まって困る」といった状況では、警告を一時的に抑制する選択肢もあります。ただし抑制は“解決”ではないため、理由と期限を決めて運用するのがおすすめです。
<PropertyGroup>
<!-- 一時的にNU1608を抑制(恒久対応は推奨しない) -->
<NoWarn>$(NoWarn);NU1608</NoWarn>
</PropertyGroup>
抑制するなら、どの警告をなぜ抑制しているかをチームで共有し、将来のアップデート時に外せるようにしておくと安全です。
XA0141(libopenal32.soのアラインメント警告)の意味と対処
もう1つ、移行後によく目にするのがXA0141のようなネイティブライブラリに関する警告です。代表例として、NuGetパッケージに含まれるlibopenal32.soが「推奨するページサイズ/アラインメントに従っていない」といった内容が出ます。
ここで大事なのは、この警告はアプリのC#コードやcsproj設定のミスというより、同梱されている.soファイル自体のビルド品質に依存することです。つまり、アプリ側で“簡単に直せる”タイプではない場合があります。
実務的な対処の優先順位
- 該当ライブラリ(NuGet)の最新版があるか確認し、更新する
- そのライブラリが自分のアプリで本当に必要か確認し、不要なら参照を外す
- 必要だが更新がない場合、ライブラリ提供元の修正を待ちつつ警告として許容する
- どうしても問題になるなら、同等機能の別ライブラリに置き換える/自前ビルドを検討する
特にゲーム系や音声/3D系のライブラリはネイティブ依存が強く、.NET 9やAndroidの環境変化の影響を受けやすい傾向があります。アプリの目的(ストア公開か、社内配布か、検証用途か)に応じて、警告許容の線引きを決めるのが現実的です。
特殊要件:targetSdkVersionを32など特定値に固定したい場合
一般的なAndroidアプリではターゲットAPIを上げていく方向が自然ですが、VR/業務端末/特定プラットフォーム向けなど、targetSdkVersionやAPIレベルを特定値に固定しないと動かないプロジェクトもあります。
このようなケースで、.NET 9へ上げた瞬間に「指定したAPIがサポートされない」「35以外を受け付けないように見える」といった挙動に遭遇することがあります。もし要件としてAPI固定が絶対条件なら、現実的な回避策は次の2つです。
.NET 8に留まる(安全策)
要件を満たすAPIレベルで安定運用できているなら、無理に.NET 9へ上げず、net8.0-androidXXの形で必要なバージョンを維持するのが最も安全です。特にストアやSDKの制約が強い案件では、フレームワーク更新よりも要件遵守が優先になります。
SDKを固定して再現性を確保する(global.json)
チーム開発やCI環境では、開発者ごとに入っているSDK差で挙動が変わると厄介です。.NET SDKのバージョンを固定して再現性を上げる手段として、リポジトリ直下にglobal.jsonを置く方法があります。
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestPatch"
}
}
versionは例として8.0.100を記載しています。チームやCIで採用している8.0系SDKのバージョンに合わせて調整してください(固定する目的は「同じSDKで同じ結果を得る」ことです)。
これにより、特定のSDKでのビルドを基本線にし、環境差による突然の破壊的変更を減らせます(ただし、利用するSDKバージョンはプロジェクト要件に合わせて選んでください)。
移行が落ち着くまでにやっておくと効くチェックリスト
「ビルドが通ったけれど警告が多い」「環境によって通ったり通らなかったりする」段階では、次のチェックが効きます。原因切り分けのスピードが上がります。
- bin/objを削除してからビルド(古い生成物が悪さをすることがある)
- Androidワークロードが更新されているか確認(Visual Studioやdotnet workload)
- MAUI関連パッケージの系列を揃える(9.0.xで統一など)
- 出力manifestでtargetSdkVersionが期待値になっているか確認
- 実機で主要導線のテスト(起動、権限、通知、WebView、カメラ、位置情報など)
- リリースビルドでも確認(AOT/Linker/Trimが絡むと挙動が変わることがある)
よくある落とし穴と対策
プロパティ画面を信用しすぎてcsprojが崩れる
Visual StudioのUI操作は便利ですが、移行時は「過去の指定をそのまま置換」することがあり、今回のようにandroid34が残ってしまうことがあります。更新直後は必ず.csprojを目視し、テンプレートの形と比較するのが安全です。
DebugとReleaseで条件分岐していて片方だけ壊れる
PropertyGroupに条件が付いていると、Debugでは通るのにReleaseで落ちる、といった事故が起きがちです。TargetFrameworkやAndroid関連プロパティは、できるだけ一箇所に集約し、条件分岐を増やしすぎないのがコツです。
AndroidManifestをいじりすぎてマージが地獄になる
「targetSdkVersionを固定したい」「権限を追加したい」などでAndroidManifest.xmlを直接編集すること自体は悪くありませんが、やりすぎるとマージの衝突原因になります。まずはビルド生成物のmanifestを確認し、差分が本当に必要なものだけを入力manifestへ反映する流れがおすすめです。
推奨設定の例とまとめ
一般的な.NET 9 + Android/.NET MAUIプロジェクトで、今回のエラーを避けつつ安定させるなら、基本は次の形に寄せるのが無難です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-android</TargetFramework>
<!-- 必要に応じて:サポートする最小OSを宣言(例) -->
<SupportedOSPlatformVersion>31.0</SupportedOSPlatformVersion>
</PropertyGroup>
</Project>
今回の「34.0 is not a valid TargetPlatformVersion…」は、ターゲットAPIをどうするかという議論に見えて、実際はTFMに残った古い指定が原因であることが多いです。まずはnet9.0-androidへ戻し、生成されるmanifestでtargetSdkVersionが期待値になっているか確認する。これが最短ルートです。
その上で、NU1608やXA0141などの警告は「全部ゼロにしないと危険」と決めつけず、実害・運用条件・CI設定の3点で優先度を付けるのが現実的です。移行期は警告が増えやすいので、まずは動く状態を確保し、ライブラリ側の追従(更新)に合わせて段階的に整えていく運用が、結局いちばん安全に進みます。

コメント