在Unity3D中实现Android 6.0权限

6
我已经安装了Android支持库,但是在developer.android网站上说要实现它,我需要编辑我的build.gradle文件,但我没有这个文件,因为它是一个Unity项目。
我创建了一个build.gradle文件,复制了此网站的内容:http://gradleplease.appspot.com/,并将该文件放在我的Unity项目根目录下,但是当我尝试使用库时,它不起作用。
if (ContextCompat.checkSelfPermission(thisActivity,
                                      Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
                                                            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                                          new String[]{Manifest.permission.READ_CONTACTS},
        MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

你应该直接在AndroidManifest.xml文件中添加权限。 - Ilona Hari
这是Android 6.0 Marshmallow。它不再起作用了。 - mithrandir
5个回答

27

您需要Java代码来请求权限,并且需要从Unity的C#运行时接口进入该Java代码。 您需要创建一个Unity插件来实现此功能。

下面是我创建的插件,用于在运行时授予WRITE_EXTERNAL_STORAGE权限。

您需要像这样的项目结构:

Plugins/
  Android/
    NoodlePermissionGranter/
      project.properties
      AndroidManifest.xml
      NoodlePermissionGranter.cs
      libs/
        NoodlePermissionGranter.jar

NoodlePermissionGranter.cs:

      ///////////////////////////////////////////////////////////
     ///////////////// NoodlePermissionGranter /////////////////
    /// Implements runtime granting of Android permissions. ///
   /// This is necessary for Android M (6.0) and above. //////
  ///////////////////////////////////////////////////////////   
 //////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////

using UnityEngine;
using System.Collections;
using System;

public class NoodlePermissionGranter : MonoBehaviour {

    // subscribe to this callback to see if your permission was granted.
    public static Action<bool> PermissionRequestCallback;

    // for now, it only implements the external storage permission
    public enum NoodleAndroidPermission
    {
        WRITE_EXTERNAL_STORAGE
    }
    public static void GrantPermission(NoodleAndroidPermission permission) 
    {
        if (!initialized)
            initialize ();

        noodlePermissionGranterClass.CallStatic ("grantPermission", activity, (int)permission);
    }







      //////////////////////////////
     /// Initialization Stuff /////
    //////////////////////////////

    // it's a singleton, but no one needs to know about it. hush hush. dont touch me.
    private static NoodlePermissionGranter instance;
    private static bool initialized = false;

    public void Awake()
    {
        // instance is also set in initialize.
        // having it here ensures this thing doesnt break
        // if you added this component to the scene manually
        instance = this;
        DontDestroyOnLoad (this.gameObject);
        // object name must match UnitySendMessage call in NoodlePermissionGranter.java
        if (name != NOODLE_PERMISSION_GRANTER)
            name = NOODLE_PERMISSION_GRANTER;
    }


    private static void initialize()
    {
        // runs once when you call GrantPermission

        // add object to scene
        if (instance == null) {
            GameObject go = new GameObject();
            // instance will also be set in awake, but having it here as well seems extra safe
            instance = go.AddComponent<NoodlePermissionGranter>();
            // object name must match UnitySendMessage call in NoodlePermissionGranter.java
            go.name = NOODLE_PERMISSION_GRANTER; 
        }

        // get the jni stuff. we need the activty class and the NoodlePermissionGranter class.
        noodlePermissionGranterClass = new AndroidJavaClass("com.noodlecake.unityplugins.NoodlePermissionGranter");
        AndroidJavaClass u3d = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
        activity = u3d.GetStatic<AndroidJavaObject> ("currentActivity");

        initialized = true;
    }







      ///////////////////
     //// JNI Stuff ////
    ///////////////////

    static AndroidJavaClass noodlePermissionGranterClass;
    static AndroidJavaObject activity;
    private const string WRITE_EXTERNAL_STORAGE="WRITE_EXTERNAL_STORAGE";
    private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match NoodlePermissionGranter.java
    private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match NoodlePermissionGranter.java
    private const string NOODLE_PERMISSION_GRANTER = "NoodlePermissionGranter"; // must match UnitySendMessage call in NoodlePermissionGranter.java

    private void permissionRequestCallbackInternal(string message)
    {
        // were calling this method from the java side.
        // the method name and gameobject must match NoodlePermissionGranter.java's UnitySendMessage
        bool permissionGranted = (message == PERMISSION_GRANTED);
        if (PermissionRequestCallback != null)
            PermissionRequestCallback (permissionGranted);
    }
}

NoodlePermissionGranter.java:

package com.noodlecake.unityplugins;


      ///////////////////////////////////////////////////////////
     ///////////////// NoodlePermissionGranter /////////////////
    /// Implements runtime granting of Android permissions. ///
   /// This is necessary for Android M (6.0) and above. //////
  ///////////////////////////////////////////////////////////   
 //////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////

import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;
import java.io.File;
import com.unity3d.player.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;

public class NoodlePermissionGranter
{
    // Only implements WRITE_EXTERNAL_STORAGE so far.
    // Implement the rest by matching the enum in NoodlePermissionGranter.cs
    // to the getPermissionStringFromEnumInt below.

    private final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "NoodlePermissionGranter";
    private final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
    private final static String PERMISSION_GRANTED = "PERMISSION_GRANTED"; // this will be an arg to the above method
    private final static String PERMISSION_DENIED = "PERMISSION_DENIED";

    public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
    {
        switch (permissionEnum)
        {
            case 0:
                return Manifest.permission.WRITE_EXTERNAL_STORAGE;
            // "and the rest is still unwritten" - Natasha Bedingfield
        }
        Log.e("NoodlePermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
        throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
    }

    public static void grantPermission(Activity currentActivity, int permissionEnum)
    {
        // permission enum must match ordering in NoodlePermissionGranter.cs
        final Activity act = currentActivity;
        Log.i("NoodlePermissionGranter","grantPermission " + permissionEnum) ;
        if (Build.VERSION.SDK_INT < 23) {
            Log.i("NoodlePermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
            return;
        }

        try
        {
            final int PERMISSIONS_REQUEST_CODE = permissionEnum;
            final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
            if (currentActivity.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
                Log.i("NoodlePermissionGranter", "already granted");
                UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                return;
            }

            final FragmentManager fragmentManager = currentActivity.getFragmentManager();
            final Fragment request = new Fragment() {

                @Override public void onStart()
                {
                    super.onStart();
                    Log.i("NoodlePermissionGranter","fragment start");
                    String[] permissionsToRequest = new String [] {permissionFromEnumInt};
                    Log.i("NoodlePermissionGranter","fragment start " + permissionsToRequest[0]);
                    requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
                }

                @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
                {
                    Log.i("NoodlePermissionGranter", "onRequestPermissionsResult");
                    if (requestCode != PERMISSIONS_REQUEST_CODE)
                        return;

                    if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        // permission was granted, yay! Do the
                        // contacts-related task you need to do.
                        Log.i("NoodlePermissionGranter", PERMISSION_GRANTED);
                        UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                    } else {

                        // permission denied, boo! Disable the
                        // functionality that depends on this permission.
                        Log.i("NoodlePermissionGranter",PERMISSION_DENIED);
                        UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
                    }


                    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                    fragmentTransaction.remove(this);
                    fragmentTransaction.commit();

                    // shouldBeOkayToStartTheApplicationNow();
                }
            };

            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(0, request);
            fragmentTransaction.commit();
        }
        catch(Exception error)
        {
            Log.w("[NoodlePermissionGranter]", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
        }
    }

}

BuildNoodlePermissionGranter.sh

export JAVA_HOME=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
ClASSPATH=$UNITY_ROOT"/Unity.app/Contents/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar"

javac NoodlePermissionGranter.java -bootclasspath $ANDROID_SDK_ROOT/platforms/android-23/android.jar -classpath $ClASSPATH -d .
javap -s com.noodlecake.unityplugins.NoodlePermissionGranter
jar cvfM NoodlePermissionGranter.jar com/
rm -rf com

为了让Unity打包一个位于Plugins/Android/libs之外的jar文件,您需要project.properties和一个虚拟的AndroidManifest.xml

project.properties

target=android-9
android.library=true

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.noodlecake.unityplugins.noodlepermissiongranter"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:targetSdkVersion="23" />
</manifest>

如果PermissionRequestCallback可以提供所请求的权限枚举作为参数,那将是很好的,但UnityPlayer.UnitySendMessage仅支持单个字符串参数,并且我决定不实现字符串序列化(使用JSON来做这件事是一个不错的选择)。


1
如果您使用targetSdk=23,则必须在运行时请求危险权限,因为它们不会在安装时授予。如果您想了解更多信息,请查阅Android 6上的运行时权限。 - Jason Knight
2
这也是第一个普通人可以轻松编写Unity Android插件的教程。太棒了!开发者必须确保使用此代码请求的权限在Unity项目的主AndroidManifest中也声明过。否则会出现问题。另外,用于编译jar文件的JVM必须与Unity配置的JVM匹配。 - tomurka
1
我目前正在尝试在Android Studio中实现这个,但它抱怨“final Fragmen request = new Fragment()”部分,说“Fragments应该是静态的,以便系统可以重新实例化它们,而匿名类不是静态的”。 我不是Java开发人员,所以我真的不知道该怎么做才能解决这个问题 :/ - Acimaz
2
类路径已更改[使用Unity 2017.1]新的路径是“/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar” - Jimmar
我遇到了同样的问题,并尝试根据此链接进行调整以使其按照 https://stackoverflow.com/questions/41260604/fragments-should-be-static-such-that-they-can-be-re-instantiated-by-the-system 的要求工作,如果成功,我会发布一个答案。 - jhocking
显示剩余10条评论

6

这是针对Unity 5.3.3及以上版本的一段优秀代码,(我使用的是5.4)我在清单中添加了以下内容以阻止Unity在启动时自动询问:

 <application>
     <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
 </application>

2
其他答案(特别是Jason Knight的)对我很有帮助,但我不得不调整代码才能使其正常工作,所以在这里分享这些更改。
如评论中所述,该代码在Android Studio中有一个错误:final Fragment request = new Fragment(); 部分说“Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static”(片段应该是静态的,这样它们可以被系统重新实例化,而匿名类不是静态的)。
现在我不是Java专家,所以可能做错了事情,但我试图按照这里的说明进行调整:Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static 主要是将Fragment拆分为一个新类,这样它就不是匿名类了。因此,现在有两个Java文件:
package com.synapse.unityplugins;

import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;

import com.unity3d.player.UnityPlayer;

public class PermissionGranter {
    public final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "SynapsePlugin_listener";
    public final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
    public final static String PERMISSION_GRANTED = "PERMISSION_GRANTED";
    public final static String PERMISSION_DENIED = "PERMISSION_DENIED";

    // only implemented WRITE_EXTERNAL_STORAGE so far
    public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
    {
        switch (permissionEnum) {
            case 0:
                return Manifest.permission.WRITE_EXTERNAL_STORAGE;
            // "and the rest is still unwritten" - Natasha Bedingfield
        }
        Log.e("PermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
        throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
    }

    public static void grantPermission(int permissionEnum)
    {
        final Activity act = UnityPlayer.currentActivity;
        Log.i("PermissionGranter","grantPermission " + permissionEnum) ;
        if (Build.VERSION.SDK_INT < 23) {
            Log.i("PermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
            return;
        }

        try {
            final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
            if (act.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
                Log.i("PermissionGranter", "already granted");
                UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                return;
            }

            final Fragment request = PermissionRequestFragment.newInstance(permissionEnum);
            final FragmentManager fragmentManager = act.getFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(0, request);
            fragmentTransaction.commit();
        }
        catch(Exception error)
        {
            Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
        }
    }
}

那是主插件类,这是片段:

package com.synapse.unityplugins;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;

import com.unity3d.player.UnityPlayer;

public class PermissionRequestFragment extends Fragment {
    public static PermissionRequestFragment newInstance(int permissionEnum) {
        PermissionRequestFragment frag = new PermissionRequestFragment();
        Bundle args = new Bundle();
        args.putInt("requested", permissionEnum);
        frag.setArguments(args);
        return frag;
    }

    @Override
    public void onStart() {
        super.onStart();
        int permissionEnum = getArguments().getInt("requested");
        final int PERMISSIONS_REQUEST_CODE = permissionEnum;

        try {
            final String permissionFromEnumInt = PermissionGranter.getPermissionStringFromEnumInt(permissionEnum);
            String[] permissionsToRequest = new String[]{permissionFromEnumInt};
            Log.i("PermissionGranter", "fragment start " + permissionsToRequest[0]);
            requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
        } catch (Exception error) {
            Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        Log.i("PermissionGranter", "onRequestPermissionsResult");
        int permissionEnum = getArguments().getInt("requested");
        final int PERMISSIONS_REQUEST_CODE = permissionEnum;
        if (requestCode != PERMISSIONS_REQUEST_CODE)
            return;

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // permission was granted, yay! Do the task now
            Log.i("PermissionGranter", PermissionGranter.PERMISSION_GRANTED);
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_GRANTED);
        } else {

            // permission denied, boo! Disable the functionality that needed it
            Log.i("PermissionGranter", PermissionGranter.PERMISSION_DENIED);
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
        }

        final Activity act = UnityPlayer.currentActivity;
        final FragmentManager fragmentManager = act.getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(this);
        fragmentTransaction.commit();
    }
}

为了完整起见,这里是Unity中的C#代码:

using UnityEngine;
using System.Collections;
using System;

public class SynapsePlugin : MonoBehaviour {

    // subscribe to this callback to see if your permission was granted.
    public static Action<bool> PermissionRequestCallback;

    // for now, it only implements the external storage permission
    public enum AndroidPermission {
        WRITE_EXTERNAL_STORAGE
    }

    public static void GrantPermission(AndroidPermission permission) {
        if (!initialized)
            initialize ();

        PermissionGranterClass.CallStatic ("grantPermission", (int)permission);
    }







    //////////////////////////////
    /// Initialization Stuff /////
    //////////////////////////////

    private const string PLUGIN_LISTENER_NAME = "SynapsePlugin_listener"; // must match UnitySendMessage call in Java

    // it's a singleton, but no one needs to know about it. hush hush. dont touch me.
    private static SynapsePlugin instance;
    private static bool initialized = false;

    static AndroidJavaClass PermissionGranterClass;
    private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match Java
    private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match Java

    // runs automatically when making calls, or can pre-init manually
    public static void initialize() {

        // add object to scene
        if (instance == null) {
            GameObject go = new GameObject();
            go.name = PLUGIN_LISTENER_NAME;

            // instance will also be set in awake, but having it here as well seems extra safe
            instance = go.AddComponent<SynapsePlugin>();
        }

        // get the jni stuff
        new AndroidJavaClass("com.synapse.unityplugins.PermissionGranter");

        initialized = true;
    }

    public void Awake() {
        DontDestroyOnLoad (this.gameObject);

        // instance is also set in initialize.
        // having it here ensures this thing doesnt break
        // if you added this component to the scene manually
        instance = this;

        if (name != PLUGIN_LISTENER_NAME)
            name = PLUGIN_LISTENER_NAME;
    }

    // we're calling this method from the java side.
    // the method name and gameobject must match Java's UnitySendMessage
    private void permissionRequestCallbackInternal(string message) {
        bool permissionGranted = (message == PERMISSION_GRANTED);
        if (PermissionRequestCallback != null)
            PermissionRequestCallback(permissionGranted);
    }
}

1
Action<bool> 是一个委托,它接受一个布尔参数,而不是一个布尔本身。换句话说,您可以像这样给它一个要调用的方法:SynapsePlugin.PermissionRequestCallback += OnPermissionRequest,其中有一个方法 private void OnPermissionRequest(bool granted) - jhocking
谢谢,这很有道理。我有点明白了...但是在你的代码中,这个方法调用的是什么?if (PermissionRequestCallback != null) PermissionRequestCallback(permissionGranted); ////(注意:这是你的代码最底部的内容) - Big T Larrity
顺便说一下,我放了debug.Log("message = " + message);,然后我得到了PERMISSION_GRANTED,所以我知道我已经完成了一半的工作哈哈..是否值得我把它变成一个完整的问题,这样你就可以适当地回答它,我也可以标记它为答案?我想不是因为人们不喜欢重复的问题。 - Big T Larrity
1
被调用的方法是OnPermissionRequest(或者你分配给该操作的任何名称)。换句话说,调用PermissionRequestCallback实际上是调用已经分配的任何方法;Action基本上是一个存储函数(即函数指针)而不是int或string等的变量。我强烈建议您查阅委托以了解它们的工作原理。 - jhocking
1
没有什么神奇的,这只是一个需要布尔型参数的方法。就像游戏中的其他某个部分调用插件中的GrantPermission一样,另一部分也会分配一个方法给PermissionRequestCallback。 - jhocking
显示剩余4条评论

2
除了Jason Knight的帖子(我用于处理运行时权限的Unity插件),我还使用了Android Studio来创建一个插件。我遵循了以下网站上的说明,效果非常好:http://www.thegamecontriver.com/2015/04/android-plugin-unity-android-studio.html 我还添加了另一种方法,使用shouldShowRequestPermissionRationale()函数,以便在用户拒绝权限并选中“不再询问”复选框后隐藏某些UI元素。

2

我使用了Jason Knight的答案创建了这个插件,整个代码都在Github存储库中可用。

还有Unity包文件可供轻松集成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接