.NET MAUIでDOCXを外部アプリ編集すると上書き保存できない原因と対策(Android FileProvider/content URI)

.NET MAUI(Android / API 33)でDOCXをFileProviderのcontent:// URIとしてWordなど外部アプリに渡すと、編集はできるのに保存が「コピーとして保存(Save a copy)」になって上書きできないことがあります。原因は不具合というよりAndroidの共有・権限設計です。本記事では仕組みと現実的な回避策をまとめます。

目次

現象:外部アプリで編集できるのに「元ファイルに保存」できない

MAUIアプリからWord文書(.docx)を開かせる際、Androidでは実ファイルパスをそのまま渡すのではなく、FileProviderを経由したcontent:// URIを外部アプリへ渡すのが定石です。ところが、次のような挙動に遭遇することがあります。

項目起こることユーザーの体感
開くWord/Office系アプリやエディタで文書が開ける問題なく編集できそう
編集文字入力や置換などは可能普通に編集できる
保存「元のファイルに上書き保存」ができず、「コピーとして保存」に誘導される同じファイルに保存したいのに増殖する

「書き込み権限を付ければ直るのでは?」と考えがちですが、残念ながらフラグやマニフェスト設定だけで必ず直る種類の問題ではありません。理由を理解しておくと、現実的な設計判断がしやすくなります。

なぜ「Save a copy」になりやすいのか

Android 11以降は“実パス共有”を避け、ストリーム共有が基本

Android 11(API 30)以降はScoped Storage(スコープ付きストレージ)の考え方が強まり、アプリ間で「/storage/…」のような実パスを共有して自由に読み書きする設計は避けられています。代わりに、アプリは外部へファイルを渡すときにcontent:// URI(ContentProvider経由)を使い、外部アプリはそれをストリームとして読み書きするのが基本動作です。

ここで重要なのは、content:// URIは“元の保存場所”そのものではないという点です。FileProviderのURIは、あくまであなたのアプリが「このファイルを一時的に共有してよい」と宣言した“仮想的な入口”です。外部アプリから見れば、そこは「読み取れる(あるいは書き込めるかもしれない)ストリーム」にすぎません。

外部アプリは“上書き保存”を安全に行える保証がない

Wordなどの編集アプリが「上書き保存」に踏み切るには、少なくとも次を確信する必要があります。

  • そのURIが書き込み可能である(WRITE権限が付与されている)
  • 書き込みは確実に成功する(途中で権限が失効しない、プロバイダが書き込みモードを許可する)
  • 上書きに失敗したときにユーザーの作業を失わない

しかし、FileProvider経由のURIは多くの場合、外部アプリにとって「書き込みできるかもしれないが、確実ではない」「永続的な場所ではない」ものです。編集アプリ側が堅牢性を優先しているほど、保存は次のような方針になります。

  • 上書きではなく、ユーザーに保存先を選ばせる(=コピー保存)
  • 自分の管理領域(アプリのドキュメントフォルダ等)へ別名保存して安全を確保する

つまり「Save a copy」は、外部アプリの“意地悪”というよりデータ損失を避けるための合理的な防御策です。ここが「直せるバグ」というより「そうなりがちな設計」の理由です。

FileProviderとStorage Access Frameworkは“同じcontent://でも性格が違う”

content:// URIには大きく2系統あります。

系統代表外部アプリから見た性格上書き期待値
共有用(FileProvider)com.yourapp.fileprovider「一時的に渡された添付ファイル」感が強い低〜中(アプリ次第)
ドキュメント用(SAF / DocumentsProvider)com.android.providers.downloads.documents など「ユーザーが選んだ正式なドキュメント」感が強い中〜高(それでもアプリ次第)

同じcontent://でも、外部アプリが“上書き保存の対象”として扱いやすいのはSAF起点です。ただし、最終的には編集アプリ側の実装ポリシーに左右されます。Wordが「この受け取り方の場合は常にコピー保存にする」と決めていれば、こちらの設定だけで強制することはできません。

まず確認したい設定チェックリスト

「仕様上、コピー保存になりやすい」ことは前提としても、設定ミスで“確実に上書きできない”状態を作っているケースはよくあります。まずは、上書き可能性を最大化するために次を確認してください。

チェック項目よくあるNG推奨
IntentにWRITE権限を付与READのみFLAG_GRANT_READ_URI_PERMISSION と FLAG_GRANT_WRITE_URI_PERMISSION を付与
ClipDataの付与SetDataAndTypeだけintent.ClipData = ClipData.NewRawUri(...) を設定(機種/アプリ互換)
アクションACTION_VIEWのみ可能なら ACTION_EDIT を試す(ただしアプリにより無視)
FileProvider設定grantUriPermissions=false / pathsが合っていないmanifestとfile_paths.xmlの整合を取る
共有するファイルの場所共有中に消える場所(掃除対象のcache等)共有中に消えない場所(files/external-filesが無難)
MIME typeapplication/octet-streamapplication/vnd.openxmlformats-officedocument.wordprocessingml.document

これらを満たしても「Save a copy」になることはありますが、少なくとも“書き込みさせるための入口”は整うので、最初に潰しておく価値があります。

FileProviderの設定例

FileProviderは「どのファイル(パス)を共有して良いか」をXMLで宣言し、content:// URIとして外部に渡せるようにします。ここがズレていると、開けても保存でエラーになったり、そもそも開けなかったりします。

AndroidManifest.xml(provider定義の例)

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
  • android:exported="false":プロバイダを外部公開しない(URIを渡した相手だけに権限を付与する)
  • android:grantUriPermissions="true":Intentのフラグで一時権限を渡せるようにする
  • authorities:GetUriForFileで指定するauthorityと一致させる

file_paths.xml(共有対象のパス例)

共有するファイルの置き場所に合わせて定義します。たとえば「アプリ専用の外部領域(external-files)」や「内部files」を共有対象にします。

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="." />
    <external-files-path name="external_files" path="." />
    <cache-path name="cache" path="." />
</paths>

どこに置くかは悩みどころですが、外部アプリ編集に使うなら次の考え方が現実的です。

  • 「元のファイル」はアプリ内(internal/files等)に安全に保持
  • 外部編集用には、元をコピーした「作業用ファイル」を作って渡す(コピー保存前提の運用に繋がる)

MAUI(Android)での実装例:FileProviderでDOCXを開く

以下は、MAUIからAndroidのIntentを投げるときのイメージです(実際にはPlatform.CurrentActivityの取得や、ファイル生成の流れに合わせて調整してください)。ポイントはWRITE権限の付与とClipDataです。

// Android固有コード(例)
// using Android.Content;
// using AndroidX.Core.Content;
// using Android.Net;

var context = Android.App.Application.Context;
var file = new Java.IO.File(docxPath);

// authorityはmanifestのFileProviderと一致させる
var uri = FileProvider.GetUriForFile(context, $"{context.PackageName}.fileprovider", file);

var mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";

// 編集できそうなアプリがある場合はACTION_EDIT、なければVIEWにフォールバックする戦略もあり
var intent = new Intent(Intent.ActionEdit);
intent.SetDataAndType(uri, mime);

// 重要:一時的な権限付与
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
intent.AddFlags(ActivityFlags.NewTask);

// 重要:一部アプリはClipDataがないと権限を拾わない
intent.ClipData = ClipData.NewRawUri("docx", uri);

// アプリによってはExtraStreamも参照する
intent.PutExtra(Intent.ExtraStream, uri);

context.StartActivity(intent);

この実装は「上書きを保証する」ものではありませんが、“上書きできるアプリ”に対しては可能性を残す構成です。逆に、これでもコピー保存にしかならないなら、編集アプリ側の方針である可能性が高くなります。

対処法:アプリ内で編集し、上書き保存を自アプリで完結させる

「どうしても元ファイルに上書きしたい」という要件が強い場合、最も確実なのは編集〜保存までをアプリ内で閉じることです。外部アプリに委ねないので、保存先(上書き)をあなたのアプリが完全に制御できます。

アプリ内編集の現実ライン

  • DOCXを“Wordそのまま”の体裁で編集するのは難易度が高い(レイアウト、段落、表、スタイル、コメント等)
  • 要件を絞って、例えば「差し込み」「定型文の置換」「署名画像の挿入」「特定箇所だけ入力」などにすると成立しやすい
  • Open XML操作(置換・挿入)や、商用ライブラリの採用(例:Aspose.Words、Syncfusion DocIOなど)を検討すると早い

向いている要件・向いていない要件

要件アプリ内編集との相性一言
テンプレートへ差し込み(氏名・日付・住所)良いOpen XMLで置換しやすい
押印/署名画像を所定位置に挿入良い固定レイアウトなら実装しやすい
自由編集(Word同等のUI/機能)難しい外部アプリ連携やクラウド編集が現実的
変更履歴・コメント・共同編集非常に難しいWord/Officeの強み領域

「外部アプリで開かせる=Wordに丸投げ」から、「アプリ内で必要十分な編集だけ提供する」へ切り替えると、上書き問題だけでなくUXやサポートコストも改善するケースがあります。

対処法:外部アプリ編集は“コピー保存前提”で運用設計する

最も導入しやすく、多くの現場で採用されるのがこの方針です。結論から言うと、外部アプリで編集させるときは「保存は別ファイルになる」ことを前提にし、編集後のファイルを取り込む導線をアプリ側で用意します。

おすすめの運用フロー

ステップユーザー操作アプリ側の動きねらい
編集開始「外部アプリで編集」ボタン元ファイルのコピーを作成し、FileProvider URIで開く元ファイルを保護しつつ編集を開始
保存外部アプリで保存(コピー保存)(外部アプリ側に任せる)外部アプリの流儀に従う
取り込み「編集後ファイルを取り込む」ドキュメントピッカーでユーザーに保存されたファイルを選ばせ、アプリ内に取り込む最終成果物をアプリが管理
置換/保存必要なら「元ファイルを更新」差分確認・バックアップを取りつつ置換“上書き”をアプリが責任を持って実行

この運用が強い理由

  • 外部アプリの実装差(Word/Google Docs/端末標準オフィスなど)に左右されにくい
  • 「保存に失敗してデータが消えた」事故を避けやすい
  • ユーザーにとっても、OS標準の保存UIに沿うので違和感が少ない

取り込み処理の実装イメージ(MAUI)

MAUIのFilePickerでユーザーに「保存されたDOCX」を選ばせ、アプリの管理領域へコピーする例です。外部アプリがどこへ保存したかは端末やアプリで変わるため、最後はユーザーに選んでもらうのが一番堅いです。

// .NET MAUI(概念例)
var pick = await FilePicker.Default.PickAsync(new PickOptions
{
    PickerTitle = "編集後のDOCXを選択",
    FileTypes = FilePickerFileType.Custom(new Dictionary<DevicePlatform, IEnumerable<string>>
    {
        { DevicePlatform.Android, new[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.document" } }
    })
});

if (pick != null)
{
    using var src = await pick.OpenReadAsync();
    var destPath = Path.Combine(FileSystem.AppDataDirectory, pick.FileName);

    using var dest = File.Create(destPath);
    await src.CopyToAsync(dest);

    // ここで「元ファイルを更新」や履歴管理などを行う
}

「元ファイルを更新する」場合は、更新前にバックアップ(例:日時付きファイル名で保存)を作るだけで、運用の安心感が大きく上がります。

取り込み時に気をつけたいUX

コピーが増えるのが嫌われるポイントなので、アプリ側で次の配慮を入れるとクレームが減ります。

  • 「外部アプリではコピー保存になります」と事前に一言だけ表示(長文説明は逆効果)
  • 取り込み画面で「どれが最新版か」を判断できるよう、ファイル名・更新日時・保存場所を表示
  • 取り込み後に「元ファイルを更新しますか?」を聞き、更新する場合はバックアップを自動作成

対処法:Storage Access Framework(SAF)起点で扱う

「どうしても“同じファイル”を編集させたい」という要件があり、かつユーザーが端末のファイル(DownloadsやDrive等)を扱う前提なら、最初からSAF(ドキュメントピッカー)で取得したUriを起点にする方法があります。

SAFのUriは「ユーザーがこのファイルをこのアプリに扱わせる」と明示的に選択したものなので、外部アプリも“ドキュメント”として扱いやすく、上書きできる確率が上がることがあります。

SAF起点の基本ステップ

  • ACTION_OPEN_DOCUMENT でユーザーにDOCXを選んでもらう
  • 必要なら takePersistableUriPermission で永続権限を取得(再起動後も扱える)
  • そのUriをアプリ内で保持し、読み書きはContentResolver経由で行う
  • 外部編集に渡すときも、そのUriをIntentに付与して開く

注意点:SAFでも上書きが保証されるわけではない

SAFは「上書きに向く入口」ではありますが、外部アプリが“コピー保存方針”なら同じ結果になります。特にOffice系アプリは、ファイルの出自(提供元プロバイダ)やメタ情報によって保存挙動を変えることがあり、端末・アプリの組み合わせで差が出ます。

どうしても“元ファイル上書き”に寄せたい場合の最終手段

要件が厳しく、かつ「アプリが管理する領域のファイルを、外部アプリで編集して同一ファイルに戻す」ことを限りなく実現したい場合、選択肢としては次のような“重い”アプローチがあります。

アプリ独自のDocumentsProviderを実装する

FileProviderではなく、あなたのアプリがDocumentsProvider(ドキュメント提供者)として振る舞い、外部アプリに対して「これは正式なドキュメントです。読み書きできます」と提示します。外部アプリがSAF対応していれば、上書き保存に近い体験になる可能性があります。

ただし実装コストは高めです。

  • ドキュメント階層やクエリ、openFileのread/write対応などを実装する必要がある
  • 権限・セキュリティ・整合性(同時更新)を自アプリで担保する必要がある
  • 結局、外部アプリが書き戻しに対応していないと意味がない

そのため、多くの案件では「コピー保存前提の取り込み運用」または「SAF起点」で十分な落としどころになります。

よくある落とし穴と切り分け方法

保存できない原因が“権限不足”なのか“外部アプリ方針”なのか

問題の切り分けは、次の観点で行うと早いです。

観点確認方法示唆
他の編集アプリでも同じかWord以外(別オフィス、ファイラー内編集など)で試す特定アプリだけなら“アプリ方針”の可能性が高い
一度も保存UIが出ない/エラーになる保存時の挙動を観察権限/URI付与ミスの可能性
常に「別名保存」へ誘導される同じ端末・同じ手順で再現外部アプリが安全策でコピー保存している可能性
SAF Uriだと上書きできるドキュメントピッカーで取得したUriで試すFileProviderの“添付ファイル扱い”が原因の可能性

「ACTION_EDITを使ったのに変わらない」場合

ACTION_EDITは万能ではありません。アプリ側が対応していなければACTION_VIEW相当の扱いになります。ACTION_EDITが効かない場合は、次を試す価値があります。

  • MIME typeを正しく設定しているか再確認する
  • ClipDataが付いているか確認する(ログ出力でintent内容を見える化)
  • 対象アプリを明示(setPackage)して挙動差を比較する

設計としておすすめの結論

この問題は、技術だけでなく要件と運用の折り合いで最適解が変わります。迷ったときは次の指針が実務では有効です。

優先したいことおすすめ方針理由
確実に元ファイルへ反映アプリ内編集、または取り込み後にアプリ側で置換外部アプリに上書き責任を持たせない
実装コストを抑えるコピー保存前提で取り込み運用外部アプリの挙動差を吸収しやすい
端末ファイルを“そのまま”扱いたいStorage Access Framework(SAF)起点ドキュメントとしての扱いになりやすい

外部アプリ編集は便利ですが、Androidでは「他アプリに保存の最終責任まで委ねる」設計が難しくなっています。上書き保存を“期待する”より、上書きに相当する処理を自アプリ側で担保する発想に切り替えると、要件に対して最短距離で落としどころを作れます。

まとめ

  • FileProviderのcontent:// URIで外部アプリに渡すと、保存時に「コピーとして保存」になりやすい
  • 原因はAndroidの共有・権限設計と、外部アプリ側の安全策(データ損失防止)
  • WRITE権限・ClipDataなどの基本設定は整えつつ、現実的には「アプリ内編集」または「コピー保存前提で取り込み」を検討する
  • “同じファイルを編集”に寄せるならSAF起点が有利だが、最終的には編集アプリ次第

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次