Unity + Android: 如何请求多个权限?

Unity + Android: How to ask for multiple permissions?

我正在使用目标平台 Android 开发一个 Unity(版本 2019.1.11)项目,我的应用程序需要具有以下权限才能运行:

android.permission.CAMERA
android.permission.RECORD_AUDIO
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_PHONE_STATE

Unity 文档指定了 this 请求 Android 权限的方式。我的目标是在应用程序启动期间对所有必需的权限进行初步检查,如下所示:

private void AskPermissions()
{
#if UNITY_ANDROID
    if (!Permission.HasUserAuthorizedPermission(Permission.Microphone))
    {
        Permission.RequestUserPermission(Permission.Microphone);
    }
    if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
    {
        Permission.RequestUserPermission(Permission.Camera);
    }
    if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
    {
        Permission.RequestUserPermission(Permission.ExternalStorageWrite);
    }
    if (!Permission.HasUserAuthorizedPermission("android.permission.READ_PHONE_STATE"))
    {
        Permission.RequestUserPermission("android.permission.READ_PHONE_STATE");
    }
#endif
}

但是,这不起作用:该应用程序只显示第一个未授权权限的对话框,而不显示以后正在检查的未授权权限的对话框。

如何确保始终检查所有权限?

似乎 Permission.RequestUserPermission 以某种方式异步工作,并且在已经显示对话框时不会显示对话框 - 因此在找到第一个未授权的权限后简单地跳过所有其他权限。
我可以绕过这样的问题:

private IEnumerator AskForPermissions()
{
#if UNITY_ANDROID
    List<bool> permissions = new List<bool>() { false, false, false, false };
    List<bool> permissionsAsked = new List<bool>() { false, false, false, false };
    List<Action> actions = new List<Action>()
    {
        new Action(() => {
            permissions[0] = Permission.HasUserAuthorizedPermission(Permission.Microphone);
            if (!permissions[0] && !permissionsAsked[0])
            {
                Permission.RequestUserPermission(Permission.Microphone);
                permissionsAsked[0] = true;
                return;
            }
        }),
        new Action(() => {
            permissions[1] = Permission.HasUserAuthorizedPermission(Permission.Camera);
            if (!permissions[1] && !permissionsAsked[1])
            {
                Permission.RequestUserPermission(Permission.Camera);
                permissionsAsked[1] = true;
                return;
            }
        }),
        new Action(() => {
            permissions[2] = Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite);
            if (!permissions[2] && !permissionsAsked[2])
            {
                Permission.RequestUserPermission(Permission.ExternalStorageWrite);
                permissionsAsked[2] = true;
                return;
            }
        }),
        new Action(() => {
            permissions[3] = ermission.HasUserAuthorizedPermission("android.permission.READ_PHONE_STATE");
            if (!permissions[3] && !permissionsAsked[3])
            {
                Permission.RequestUserPermission("android.permission.READ_PHONE_STATE");
                permissionsAsked[3] = true;
                return;
            }
        })
    };
    for(int i = 0; i < permissionsAsked.Count; )
    {
        actions[i].Invoke();
        if(permissions[i])
        {
            ++i;
        }
        yield return new WaitForEndOfFrame();
    }
#endif
}

我在做的一个项目中遇到了同样的问题。 一个简单的解决方法是使用协程请求权限并在每个权限之间 yield return 等待几秒钟。或者只是在应用程序的不同地方请求权限。例如,当用户要使用它时只请求相机权限,或者当它即将获得访问权限时只请求写外部存储。由于这些是敏感权限,用户最好事先知道为什么要求他授予这些权限。

IEnumerator Start() {
        // Ask for camera permission
        if(!Permission.HasUserAuthorizedPermission(Permission.Camera)) {
            Permission.RequestUserPermission(Permission.Camera);
        }
        yield return new WaitForSeconds(2.5f);
        // Ask for external storage permission
        if(!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) {
            Permission.RequestUserPermission(Permission.ExternalStorageWrite);
        }
}

上面的代码有效。您可以调整等待秒数的值,并添加您需要的权限。请注意,我仅在尚未授予权限时才请求权限。

干杯!

Android 众所周知,运行时权限很麻烦,在您的情况下,问题是 Android 的权限请求 UI 面板在您的第一个 Permission.RequestUserPermission, 暂停您的应用程序并阻止执行以下代码

到目前为止,我提出的处理运行时权限的最干净的解决方案是 运行 无限循环到协同程序中,贪婪地检查您需要的权限,并提示用户在没有权限时授予每个权限

乍一看这听起来很丑陋,但考虑到当请求权限时循环被中断,原因与您的代码不起作用的原因相同:因为权限请求 UI 面板占据了前台,并且您的应用程序被暂停,当焦点返回时您的循环再次开始检查缺少的权限,另外您可以使用 yield return new WaitForSeconds(0.2f)

减慢循环

这是经过修饰的代码,用于通知用户在未授予所有所需权限的情况下无法继续:

private bool _locationPermissionAsked;
private bool _microphonePermissionAsked;
private bool _cameraPermissionAsked;
private bool _storagePermissionAsked;

private void Start()
{
#if UNITY_ANDROID && !UNITY_EDITOR
    StartCoroutine(RequestPermissionsRoutine());        
#else
    /***** Ready to run you app *****/
#endif
}

private IEnumerator RequestPermissionsRoutine()
{
    yield return new WaitForEndOfFrame();
    while (true)
    {
        // For each permission you need, build a block like the following, it could 
        // have been done dynamically but given the small number of possible options 
        // I preferred to keep it extended
        if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation) && !_locationPermissionAsked)
        {
            // This flag keeps track of the user choice against the permission panel
            //
            // if he choose to NOT grant the permission we skip the permission request
            // because we are gonna notify him that he needs to grant the permission later 
            // using a message in our App
            _locationPermissionAsked = true; 
            
            // You can even ask permissions using android literal definition instead of Unity's Permission.FineLocation
            yield return Permission.RequestPermission("android.permission.ACCESS_FINE_LOCATION").WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && !_microphonePermissionAsked)
        {
            _microphonePermissionAsked = true;
            yield return Permission.RequestPermission(Permission.Microphone).WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && !_cameraPermissionAsked)
        {
            _cameraPermissionAsked = true;
            yield return Permission.RequestPermission(Permission.Camera).WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite) && !_storagePermissionAsked)
        {
            _storagePermissionAsked = true;
            yield return Permission.RequestPermission(Permission.ExternalStorageWrite);
            continue;
        }

        // This is the part where we check if all the permissions were granted
        // and allow the user to run the App ( else condition )
        // or prompt him to grant the permissions he denied
        //
        // Note that this code is never reached before each permission have been asked 
        // once, because of the "continue;"s used before
        if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation) ||
            !Permission.HasUserAuthorizedPermission(Permission.Microphone) ||
            !Permission.HasUserAuthorizedPermission(Permission.Camera) ||
            !Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
        {
            if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation))
            {
                /***** Tell the user to grant FineLocation Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.Microphone))
            {
                /***** Tell the user to grant Microphone Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
            {
                /***** Tell the user to grant Camera Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
            {
                /***** Tell the user to grant ExternalStorageWrite Permission *****/
            }

            /***** This is where all permissions have been asked once  *****/
            /***** and one or more were NOT granted,                   *****/
            /***** you can write the code to handle this fallback here *****/
        }
        else
        {
            // I like to give some time to the Android looper before running my App, just to be safe
            yield return new WaitForSeconds(1f); 
            
            /***** Ready to run you App *****/
        }
        
        // Slow down the loop by a little bit, not strictly needed but not harmful either
        yield return new WaitForSeconds(0.2f);
    }
}

这甚至适用于以下情况,例如,用户拒绝一个权限然后强行杀死您的应用程序,或者其他一些应用程序意外抢占前台以及我目前遇到的各种其他风险情况

我再补充一点: 如果您正在使用 Google ARCore,请注意它会覆盖原始 Unity 的权限请求机制 (https://github.com/google-ar/arcore-unity-sdk/issues/151),从而鼓励开发人员使用自己的 GoogleARCore.AndroidPermissionsManager 而不是 UnityEngine.Android.Permission,因此UnityEngine.Android.Permission 将不起作用,您仍然可以使用此脚本,但您需要将所有“Permission.RequestUserPermission”替换为“AndroidPermissionsManager.RequestUserPermission”(您不需要替换“Permission.HasUserAuthorizedPermission"s,它们仍然有效),或者更好,我可以为你做:

private IEnumerator RequestPermissionsRoutine()
{
    yield return new WaitForEndOfFrame();
    while (true)
    {
        if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation) && !_locationPermissionAsked)
        {
            _locationPermissionAsked = true; 
            yield return AndroidPermissionsManager.RequestPermission("android.permission.ACCESS_FINE_LOCATION").WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && !_microphonePermissionAsked)
        {
            _microphonePermissionAsked = true;
            yield return AndroidPermissionsManager.RequestPermission(Permission.Microphone).WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && !_cameraPermissionAsked)
        {
            _cameraPermissionAsked = true;
            yield return AndroidPermissionsManager.RequestPermission(Permission.Camera).WaitForCompletion();
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite) && !_storagePermissionAsked)
        {
            _storagePermissionAsked = true;
            yield return AndroidPermissionsManager.RequestPermission(Permission.ExternalStorageWrite);
            continue;
        }

        if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation) ||
            !Permission.HasUserAuthorizedPermission(Permission.Microphone) ||
            !Permission.HasUserAuthorizedPermission(Permission.Camera) ||
            !Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
        {
            if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation))
            {
                /***** Tell the user to grant FineLocation Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.Microphone))
            {
                /***** Tell the user to grant Microphone Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
            {
                /***** Tell the user to grant Camera Permission *****/
            }

            if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite))
            {
                /***** Tell the user to grant ExternalStorageWrite Permission *****/
            }
            /***** This is where all permissions have been asked once  *****/
            /***** and one or more were NOT granted,                   *****/
            /***** you can write the code to handle this fallback here *****/
        }
        else
        {
            yield return new WaitForSeconds(1f); 
            
            /***** Ready to run you App *****/
        }
        
        yield return new WaitForSeconds(0.2f);
    }
}

就我而言,我使用了 unity 的 OnApplicationFocus 回调函数。当权限 window 打开时,应用程序将失去其焦点模式,当权限 window 关闭(用户接受或拒绝权限)时,应用程序将再次获得其焦点模式。每次调用 OnApplicationFocus 回调。

看起来很脏,但效果很好。您还必须在清单文件中添加权限。

Please see this GitHub link for see the full project

public class AndroidPermissionHandler : MonoBehaviour
{
    bool isItPermissionTime = false;
    string nextPermission;
    Stack<string> permissions = new Stack<string>();

void Start()
{
    OpenAllPermissions();
}

public void OpenAllPermissions()
{
    isItPermissionTime = true;
    CreatePermissionList();

}
void CreatePermissionList()
{
    permissions = new Stack<string>();
    permissions.Push(Permission.ExternalStorageWrite);
    permissions.Push(Permission.Camera);
    permissions.Push(Permission.CoarseLocation);
    AskForPermissions();
}
 void AskForPermissions ()
{
    if (permissions == null || permissions.Count <= 0)
    {
        isItPermissionTime = false;
        return;
    }
    nextPermission = permissions.Pop();

    if (nextPermission == null)
    {
        isItPermissionTime = false;
        return;
    }
    if (Permission.HasUserAuthorizedPermission(nextPermission) == false)
    {
        Permission.RequestUserPermission(nextPermission);
    }
    else
    {
        if (isItPermissionTime == true)
            AskForPermissions();
    }
    Debug.Log("Unity>> permission " + nextPermission + "  status ;" + Permission.HasUserAuthorizedPermission(nextPermission));
}

private void OnApplicationFocus(bool focus)
{
    Debug.Log("Unity>> focus ....  " + focus + "   isPermissionTime : " + isItPermissionTime);
    if (focus == true && isItPermissionTime == true)
    {
        AskForPermissions();
    }
}