Android访问sd卡权限,各个版本有不同的方式。使用ARFoundation编程android项目时,可以使用unity提供的Permission申请android权限,提示有UnauthorizedAccessException的报错,基本为权限问题。

Android中sd卡的路径一般为:/storage/emulated/0/…

比如/storage/emulated/0/DCIM 为相机目录。

从《ARFoundation从零开始3-arfoundation项目》创建项目

一、android各版本的权限申请

1.安卓6.0(API 23)以下,访问只需要在清单文件中添加如下权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2. 在安卓6.0(API 23)及以上系统,还需要在代码中动态申请权限:

ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST);

3. 安卓10系统以上,还需要在清单文件中application节点加上:android:requestLegacyExternalStorage="true"

二、unity配置

1.新增自定义项AndroidManifest.xml,用来设置android权限的配置。Edit-ProjectSettings-Player-Publishing Settings,勾选自定义Manifest:

2.进入当前项目目录\Assets\Plugins\Android,修改AndroidManifest.xml文件:

修改主Activity为com.unity3d.player.MainActivity,文件内容修改为:

<?xml version="1.0" encoding="utf-8"?>

<!-- GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools">

  <application android:requestLegacyExternalStorage="true">

    <activity android:name="com.unity3d.player.MainActivity" android:theme="@style/UnityThemeSelector" android:screenOrientation="fullSensor" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:hardwareAccelerated="false">

      <intent-filter>

        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />

      </intent-filter>

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

      <meta-data android:name="android.notch_support" android:value="true" />

    </activity>

    <meta-data android:name="unity.splash-mode" android:value="0" />

    <meta-data android:name="unity.splash-enable" android:value="True" />

    <meta-data android:name="notch.config" android:value="portrait|landscape" />

    <meta-data android:name="unity.build-id" android:value="…" />

    <meta-data android:name="com.google.android.ar.API_KEY" android:value="…" />

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

    <meta-data android:name="com.google.ar.core" android:value="required" />

  </application>

  <uses-feature android:glEsVersion="0x00030000" />

  <uses-permission android:name="android.permission.INTERNET" />

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

  <uses-feature android:name="android.hardware.location.gps" android:required="True" />

  <uses-feature android:name="android.hardware.location" android:required="True" />

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  <uses-feature android:name="android.hardware.touchscreen" android:required="false" />

  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />

  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />

  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:minSdkVersion="31" />

  <uses-permission android:name="android.permission.CAMERA" />

  <uses-feature android:name="android.hardware.camera.ar" android:required="true" />

  <uses-feature android:name="com.google.ar.core.depth" android:required="true" />

</manifest>

2.进入当前项目\Assets\Plugins\Android目录,新建MainActivity.java文件,作为主启动Activity:

3. 进入当前项目\Assets\Plugins\Android目录,mainTemplate.gradle.meta文件repositories修改为:

google()

       mavenCentral()

与android studio中保持一致!

4.unity中-Project Settings-Player-Other Settings-Write Permission选择External:

5.MainActivity.java代码:

package com.unity3d.player;

import android.os.Bundle;

import android.view.KeyEvent;

import android.widget.Toast;

public class MainActivity extends UnityPlayerActivity{

    private static MainActivity instance;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        instance = this;

    }

    public static MainActivity GetInstance()

    {

        return instance;

    }

    @Override public boolean dispatchKeyEvent(KeyEvent event)

    {

        if(event.getKeyCode() == KeyEvent.KEYCODE_BACK){

            onBackPressed();

            return true;

        }

        if (event.getAction() == KeyEvent.ACTION_MULTIPLE)

            return mUnityPlayer.injectEvent(event);

        return super.dispatchKeyEvent(event);

    }

    //------

    @Override

    public void onBackPressed() {

        runOnUiThread(new Runnable() {

            @Override

            public void run() {

                mUnityPlayer.quit();

            }

        });

        super.onBackPressed();

    }

    //unity调用android的方法

    public int Sum(int x, int y)

    {

        return x + y;

    }

    public String OnUnityFinished(String str){

        runOnUiThread(new Runnable() {

            @Override

            public void run() {

                Toast.makeText(

                        instance,

                        "OnUnityFinished=" + str,

                        Toast.LENGTH_LONG).show();

            }

        });

        return "ok";

    }

    public void CallUnityFun(String str)

    {

        String receiveObj = "MainCamera";//unity中脚本挂载的Object的name

        String receiveMethod = "Receive";//unity中Object挂载脚本的方法名

        String params = str + " Android Call Unity.";//方法要穿的参数

        //android调用unity,

        UnityPlayer.UnitySendMessage(receiveObj, receiveMethod, params);

    }

}

三、unity申请android权限代码

Unity中自带的Android权限申请类Permission:

Unity - Scripting API: Permission

其中主要用到的方法:

HasUserAuthorizedPermission方法用来判断是否获得了该权限:

Permission.HasUserAuthorizedPermission("android.permission.READ_PHONE_STATE")

RequestUserPermission方法用来请求该权限:

Permission.RequestUserPermission("android.permission.READ_PHONE_STATE");

1.编写android权限申请类,项目Scripts文件夹下新建AndroidPermissions.cs文件,挂载到AR Session Origin下:

代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.Android;

namespace FrameworkDesign.Example

{

    public class AndroidPermissions : MonoBehaviour

    {

        private static AndroidPermissions instance;

        private int index;

        private List<string> permissionList = new List<string>();

        private void Awake()

        {

            if(instance == null)

            {

                instance = this;

                Init();

            }                                 

        }

        //初始化,权限申请要尽可能早

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]

        public void Init()

        {

            //权限添加进列表

            permissionList.Clear();

            index = 0;

            //permissionList.Add("android.permission.READ_PHONE_STATE");

            permissionList.Add(Permission.ExternalStorageRead);

            permissionList.Add(Permission.ExternalStorageWrite);

           // permissionList.Add(Permission.FineLocation);

           // permissionList.Add(Permission.CoarseLocation);

            StartCheckPermission(0.02f); //开始申请

            Logger.Log("权限申请完毕");

        }

        public void StartCheckPermission(float time)

        {

            //Logger.Log("开始权限申请");

            if (permissionList.Count > 0)

            {

                Get(permissionList[index], time);

            }

        }

        /// <summary>

        /// 判断并申请权限

        /// </summary>

        /// <param name="type">权限名</param>

        /// <param name="time">如拒绝延迟多久再次申请</param>

        void Get(string type, float time)

        {

            if (!Permission.HasUserAuthorizedPermission(type))

            {

                Permission.RequestUserPermission(type);

                Logger.Log("正在获取的权限:" + type);

                StartCoroutine(Check(type, time));

            }

            else

            {

                Logger.Log("权限已经获取:" + type);

                if (index < permissionList.Count - 1)

                {

                    index += 1;

                    Get(permissionList[index], time);

                }

            }

        }

        IEnumerator Check(string type, float time)

        {

            yield return new WaitForSeconds(time);

            Get(type, time);

        }

    }

}

2.打包运行:

  

当用户尚未授权时,程序弹出Android权限申请界面,点击授权即可。

四、unity导出lib库

unity导出lib库,(先删除原来导出的lib库文件夹下所有文件,确保修改的文件更新):

五、android studio升级

1.java JDK升级到11,配置环境变量。

2.Android studio升级到2021.2.1:下载安装包安装,提示删除旧版,点击确定安装新版。

3. Android studio配置gradle,file-project structure-project,配置7.2.1和7.3.3:

点击SDK Location-Gradle Settings-Gradle JDK,选择11:

六、android studio导入unity库

1.复制unityLibrary到android studio工程根目录(先删除原有unityLibrary文件夹),启动android studio

2.gradle.properties文件注释掉android.enableR8=false:
#android.enableR8=false

3.app的build.gradle的buildToolsVersion :
buildToolsVersion '30.0.3'

4.打包出错:

原因是androidx兼容问题,android代码使用了androidx,删除unityLibrary的build.gradle内以下内容:

打包成功

MainActivity 继承UnityPlayerActivity,实现与unity的接口调用,相关代码都在MainActivity中编写。

三、android打包运行

如未配置,参看《ARFoundation从零开始3-arfoundation项目》。

安装运行:

  

对于Unity工程不单独打包,而是接入到其他android工程里的情况,android权限的申请最好在android端进行,不能确保在android申请的时候再在unity端申请。

Android端申请权限代码:



//----1.android端申请权限,以下代码放到当前Activity类里-----------------

//------2.在当前Activity的onCreate方法里调用checkPermission();

String[] permissions = new String[]{Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_SETTINGS};

List<String> mPermissionList = new ArrayList<>();



private static final int PERMISSION_REQUEST = 1;

// 检查权限

private void checkPermission() {

    mPermissionList.clear();

    //判断哪些权限未授予

    for (int i = 0; i < permissions.length; i++) {

        if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {

            mPermissionList.add(permissions[i]);

        }

    }

    //判断是否为空

    if (mPermissionList.isEmpty()) {//未授予的权限为空,表示都授予了



    } else {//请求权限方法

        String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);//将List转为数组

        ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST);

    }

}

/**

 * 响应授权

 * 这里不管用户是否拒绝,都进入首页,不再重复申请权限

 */

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {

        case PERMISSION_REQUEST:



            break;

        default:

            super.onRequestPermissionsResult(requestCode, permissions, grantResults);

            break;

    }

}

//----android端申请权限-----------------

四、常见问题

(unity的gradle没有与android studio同步,仍使用的gradle6.7.1+jdk1.8,没有升级到gradle7.3.3+jdk11)

五、参考资料

1. Unity api:

Unity - Scripting API: Permission

2.ARFoundation示例:

GitHub - Unity-Technologies/arfoundation-samples: Example content for Unity projects based on AR Foundation

3.ARCore文档:

在 Unity (AR Foundation) 应用中执行光线投射  |  ARCore  |  Google Developers

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐