Androidでの.mcaddon自動生成とインポート破綻を防ぐ設計:FileProviderとScoped Storage時代のマインクラフト連携

Minecraft Bedrock Edition(統合版)向けのカスタムモブ、スキン、ビヘイビアパックをユーザーが自作・共有できるAndroidアプリを開発する際、Minecraft McAddon Template Project|Create McAddon Android App のようなテンプレート資産は、JSONジェネレータ、テクスチャピッカー、アーカイブ生成機能の骨格が揃っているため、開発初期のプロトタイピング工数を大幅に圧縮する。

【アーキテクチャの結論】
近代Android(Android 11〜15)環境下で、自作アドオンをMinecraft本体へエラーなく直接受け渡す設計の要諦は、「① Android/data/com.mojang.minecraftpe へのレガシーな直接ファイル配置を完全に廃止し、② メモリを圧迫しないストリーミングZip圧縮で .mcaddon(または .mcpack)をアプリ内部ストレージに生成した上で、③ FileProvider のセキュアな content:// URI と FLAG_GRANT_READ_URI_PERMISSION を付与した Intent.ACTION_VIEW でMinecraftプロセスへ受け渡すこと」 にある。この公式準拠のハンドオフ設計を遵守しない限り、OSのScoped Storage制約により「インポート失敗」やサイレントクラッシュが不可避となる。


1. 現場の摩擦点:Scoped Storageの壁とMIMEタイプ不一致によるインポート拒絶

Minecraftアドオン制作アプリを開発するエンジニアが最も頻繁に直面するトラブルは、「生成したアドオンがMinecraft側で認識されない」 というエクスポート障害である。

Android 10以前は、外部ストレージの /games/com.mojang/ 配下に直接ファイルをコピーする裏技が通用していたが、Android 11以降のScoped Storage制限により、他社アプリがMojangのアプリ固有ディレクトリへ直接アクセスすることはOSレベルで完全に遮断された。

そのため、アプリ側で .mcaddon(中身はビヘイビアパックとリソースパックをまとめたZip構造)を生成し、システムインテント経由でMinecraft本体をキックしてインポートさせる必要がある。しかし、ここで以下の初歩的なミスが多発する:

  • メモリ上での巨大Zip展開によるOOM(メモリ不足):高解像度テクスチャを含むパックをバイト配列としてメモリ上に一括展開し、低スペック端末でクラッシュする。

  • MIMEタイプとURIの不整合:application/zip や無効なMIMEタイプでインテントを発行した結果、Minecraft側のアドオンハンドラが起動せず、単なる汎用ファイルビューアが起動してしまう。

  • マニフェストUUIDの重複:内部の manifest.json に動的なUUID v4が正しく割り振られておらず、Minecraft側で「重複したパックが検出されました」とエラーになる。


2. 解決策:FileProviderによる安全なインテント連携とストリーミングZip出力

この課題を確実に克服するには、アプリのプライベートキャッシュ領域で安全にファイルをビルドし、Android標準の FileProvider を介して一時的な読み取り権限をMinecraftに委譲する。

以下は、メモリ枯渇を防ぎながら .mcaddon ファイルを安全にMinecraft本体へハンドオフするKotlinのプロダクション向け実装例である。

codeKotlin

import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

class McAddonExportManager(private val context: Context) {

    /**
     * アプリ内キャッシュに.mcaddonを出力し、Minecraft起動インテントを発行する
     */
    fun exportAndLaunchMinecraft(addonName: String, packFilesDir: File): Result<Unit> {
        val outputAddonFile = File(context.cacheDir, "$addonName.mcaddon")

        return try {
            // 1. メモリを圧迫しないストリーミングZip圧縮
            ZipOutputStream(FileOutputStream(outputAddonFile)).use { zipOut ->
                packFilesDir.walkTopDown().filter { it.isFile }.forEach { file ->
                    val relativePath = file.relativeTo(packFilesDir).path.replace("\\", "/")
                    zipOut.putNextEntry(ZipEntry(relativePath))
                    file.inputStream().use { input -> input.copyTo(zipOut) }
                    zipOut.closeEntry()
                }
            }

            // 2. FileProviderによるcontent URIの発行
            val authority = "${context.packageName}.fileprovider"
            val contentUri: Uri = FileProvider.getUriForFile(context, authority, outputAddonFile)

            // 3. Minecraft本体への明示的インテント発行
            val launchIntent = Intent(Intent.ACTION_VIEW).apply {
                setDataAndType(contentUri, "application/octet-stream")
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                // Minecraftがインストールされている場合はターゲットを指定
                setPackage("com.mojang.minecraftpe")
            }

            context.startActivity(launchIntent)
            Result.success(Unit)
        } catch (e: Exception) {
            // 例外発生時は中途半端なファイルを確実に削除
            if (outputAddonFile.exists()) {
                outputAddonFile.delete()
            }
            Result.failure(e)
        }
    }
}

この実装では、ファイルI/Oに use 構文を徹底してリソースリークを防ぎ、MIMEタイプを application/octet-stream に固定しつつ FLAG_GRANT_READ_URI_PERMISSION を明示付与している。これにより、Minecraftのインポートサービスがバックグラウンドで安全にファイルを読み取り、正常にアドオンの登録処理を開始できる。


3. 実機検証とアーカイブ整合性の診断アプローチ

開発したエクスポート処理が正常に機能しているかを確かめるには、単に「アプリが動いた」で終わらせず、生成されたアーカイブの物理的整合性を開発環境で自己診断する手順が欠かせない。

実機でのインポート挙動を精査するにあたり、Minecraft McAddon Template Project のソース構成 をAndroid Studioへ取り込み、アドオンビルドからMojangプロセス起動までのデータフローを追跡した。

アーカイブが破損していないかを確認する際は、端末から生成ファイルを adb pull して以下のコマンドでZip構造とマニフェストを検査するのが確実である:

codeBash

# 生成された.mcaddonのファイル構造とエラーの有無を検証
zipinfo -v my_addon.mcaddon

# manifest.jsonのフォーマットとUUIDが有効か確認
unzip -p my_addon.mcaddon "manifest.json" | jq .

診断の結果、ルートディレクトリ直下に manifest.json が配置され、無駄なトップレベルフォルダが挟まっていない構造を担保することで、Minecraft本体の「インポート開始」トーストから「インポート成功」通知までスムーズに遷移することを確認できた。


4. マインクラフトアドオン連携アプリの必須監査リスト

Google Playストアへのリリース前、およびOSアップデート追従時には、以下の5項目をアーキテクチャ監査基準として確認することを推奨する:

FileProviderのパス定義:res/xml/file_paths.xml 内に <cache-path> が正しく宣言され、セキュアな一時領域のみが外部公開されているか?

UUIDの動的生成バリデーション:生成される manifest.json 内の header.uuid および modules[].uuid に重複がなく、有効なUUID v4形式になっているか?

ストリーミングI/OによるOOM回避:数十MB規模のテクスチャ書き込み時、全データをメモリに乗せずストリーム経由で直接Zipへパイプ処理されているか?

Minecraft非インストール時のハンドリング:端末に com.mojang.minecraftpe が存在しない場合、クラッシュせずファイル共有(Intent.createChooser)画面へフォールバックするか?

一時ファイルのクリーンアップ:インテント発行完了後、あるいは次回起動時に、キャッシュ領域に溜まった過去の .mcaddon 一時ファイルが安全にパージされているか?