5

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

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

Unity 文档指定了这种请求 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
}

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

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

4

4 回答 4

4

似乎它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
}
于 2019-11-06T11:00:35.883 回答
2

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

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

请查看此 GitHub 链接以查看完整项目

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();
    }
}
于 2020-07-14T15:45:12.850 回答
1

众所周知,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”,它们仍然有效),甚至更好,我可以为您完成:

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);
    }
}
于 2019-11-07T19:49:49.003 回答
-1

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

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);
        }
}

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

干杯!

于 2019-11-06T16:22:11.733 回答