以下の内容はhttps://bluebirdofoz.hatenablog.com/entry/2025/06/09/235651より取得しました。


MetaQuestでOnApplicationQuitが動作しない

本日はMetaQuestの小ネタ枠です。
MetaQuestでOnApplicationQuitの動作が動作しなかったので記事にします。

OnApplicationQuitとは

OnApplicationQuit()はUnityのMonoBehaviourクラスで提供されるメソッドです。
アプリケーションが終了する際に自動的に呼び出され、終了処理を実行するために使用されます。
データの保存やリソースの解放など、アプリケーション終了時に必要な処理を記述できます。
docs.unity3d.com

MetaQuestでのOnApplicationQuit()の動作

以下のアプリ終了時にドキュメントフォルダに終了時刻を記録したテキストを出力するスクリプトを作成しました。
スクリプトを組み込んだアプリをQuest 3実機上で起動してOnApplicationQuitの動作を確認しました。

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;

public class DocumentAccessTest : MonoBehaviour
{
#if UNITY_ANDROID
    /// <summary>
    /// アプリ終了時にドキュメントフォルダに終了時間を記録したファイルを保存する
    /// </summary>
    private void OnApplicationQuit()
    {
        string documentFolderPath = GetDocumentFolderPathPerAndroid29();
        string logFilePath = System.IO.Path.Combine(documentFolderPath, "AppExitLog.txt");
        string exitTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        string logText = $"アプリ終了: {exitTime}\n";
        try
        {
            System.IO.File.AppendAllText(logFilePath, logText);
        }
        catch (System.Exception e)
        {
            Debug.LogError("Failed to write exit log: " + e.Message);
        }
    }

    async void Start()
    {
        // AndroidSDK29以降では予めストレージへのアクセス許可をユーザに問い合わせて許可を取得する必要がある
        await GetStoragePermissionAsync();

        // ドキュメントフォルダのパスを取得する
        string documentFolderPath = GetDocumentFolderPathPerAndroid29();

        // テストとしてドキュメントフォルダにフォルダを作成する
        string folderPath = System.IO.Path.Combine(documentFolderPath, "TestFolder");
        if (!System.IO.Directory.Exists(folderPath)) System.IO.Directory.CreateDirectory(folderPath);
    }

    // 参考 https://communityforums.atmeta.com/t5/Quest-Development/Scoped-Storage-and-VR/td-p/1043602
    // AndroidSDK29以上でドキュメントフォルダへのアクセス権限を取得するための処理

    /// <summary>
    /// AndroidSDK29以降のドキュメントパスを取得する
    /// </summary>
    /// <returns></returns>
    private string GetDocumentFolderPathPerAndroid29()
    {
        // AndroidSDK29以降は/sdcard/Documents/のパスでアクセスできる
        string documentPath = "/sdcard/Documents/";
        return documentPath;
    }

    /// <summary>
    /// AndroidSDK29以降のフォルダアクセスの許可を取得する
    /// ユーザが許可するまで待機する
    /// </summary>
    /// <returns></returns>
    private async Task<bool> GetStoragePermissionAsync()
    {
        if (!UserHasManageExternalStoragePermission())
        {
            // ユーザに許可を求める
            AskForManageStoragePermission();
            // ユーザが許可するまで待機する
            while (!UserHasManageExternalStoragePermission())
            {
                await Task.Delay(100);
            }
        }

        return true;
    }

    private bool m_FolderPermissionOverride = false;

    /// <summary>
    /// アプリがフォルダアクセスの許可を持っているかどうか確認する
    /// </summary>
    private bool UserHasManageExternalStoragePermission()
    {
        bool isExternalStorageManager = false;
        try
        {
            AndroidJavaClass environmentClass = new AndroidJavaClass("android.os.Environment");
            isExternalStorageManager = environmentClass.CallStatic<bool>("isExternalStorageManager");
        }
        catch (AndroidJavaException e)
        {
            Debug.LogError("Java Exception caught and ignored: " + e.Message);
            Debug.LogError("Assuming this means this device doesn't support isExternalStorageManager.");
        }

        return m_FolderPermissionOverride || isExternalStorageManager;
    }

    /// <summary>
    /// ユーザにフォルダアクセスの許可を求める
    /// </summary>
    private void AskForManageStoragePermission()
    {
        try
        {
            using var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            using AndroidJavaObject currentActivityObject = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
            string packageName = currentActivityObject.Call<string>("getPackageName");
            using var uriClass = new AndroidJavaClass("android.net.Uri");
            using AndroidJavaObject uriObject =
                uriClass.CallStatic<AndroidJavaObject>("fromParts", "package", packageName, null);
            using var intentObject = new AndroidJavaObject("android.content.Intent",
                "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION", uriObject);
            intentObject.Call<AndroidJavaObject>("addCategory", "android.intent.category.DEFAULT");
            currentActivityObject.Call("startActivity", intentObject);
        }
        catch (AndroidJavaException e)
        {
            m_FolderPermissionOverride = true;
            Debug.LogError("Java Exception caught and ignored: " + e.Message);
            Debug.LogError("Assuming this means we don't need android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION (e.g., Android SDK < 30)");
        }
    }
#endif
}

終了時の処理を確認するため、以下のドキュメントフォルダへのアクセス方法を利用しています。
bluebirdofoz.hatenablog.com

実機での動作確認

Quest実機上では以下のメニュー画面の[閉じる]ボタンを実行してアプリを閉じてみました。

結果としてOnApplicationQuitは呼び出されず、ドキュメントフォルダにテキストは出力されませんでした。
QueatでOnApplicationQuitが呼び出されない問題はQuest2の時からの動作のようです。
ayousanz.hatenadiary.jp




以上の内容はhttps://bluebirdofoz.hatenablog.com/entry/2025/06/09/235651より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14