Unity 之 Android 【获取设备的序列号 (Serial Number)/Android_ID】功能的简单封装

Unity 之 Android 【获取设备的序列号 (Serial Number)/Android_ID】功能的简单封装

目录

Unity 之 Android 【获取设备的序列号 (Serial Number)/Android_ID】功能的简单封装

一、简单介绍

二、获取设备的序列号 (Serial Number) 实现原理

1、Android

2、 Unity

三、注意实现

四、案例实现简单步骤

五、关键代码


一、简单介绍

Unity 是一个功能强大的跨平台游戏引擎,广泛用于开发视频游戏和其他实时3D互动内容,如模拟器和虚拟现实应用。

游戏引擎:

  •     Unity:Unity Technologies 开发的跨平台游戏引擎,支持2D和3D图形、物理引擎、音频、视频、网络和多平台发布。
  •     跨平台支持:Unity 支持在多个平台上发布,包括 Windows、macOS、Linux、iOS、Android、WebGL、PlayStation、Xbox、Switch 等。

开发环境:

  •     Unity Editor:用于创建和管理 Unity 项目的集成开发环境(IDE)。开发者可以在其中创建场景、设计关卡、编写代码和调试游戏。
  •     场景(Scene):Unity 中的基本构建块,一个场景可以被视为一个关卡或一个游戏中的独立部分。

编程语言:

  •     C#:主要编程语言。Unity 使用 C# 脚本来控制游戏对象和实现游戏逻辑。
  •     UnityScript(已弃用):曾经支持的 JavaScript 变种,但已经被弃用。

Unity 进阶开发涉及更复杂的技术和更深入的知识,以创建高性能、复杂和专业的游戏和应用程序。以下是一些 Unity 进阶开发的关键领域和技术:

设计模式:

  •     学习和应用常见的设计模式(如单例模式、工厂模式、观察者模式)以便更好地组织和管理代码。

异步编程:

  •     使用 C# 的 async 和 await 关键字进行异步编程,以提高应用的响应速度和性能。

依赖注入:

  •     使用依赖注入(如 Zenject)来管理对象的依赖关系,减少耦合,提高代码的可测试性和可维护性。

二、获取设备的序列号 (Serial Number) 实现原理

1、Android

在 Android 10 及以上版本,由于隐私和安全原因,获取设备的序列号 (SN) 变得更加受限。

在 Android 10 及以上版本,可以使用 Build.getSerial() 方法来获取设备的序列号。不过,这需要获取 READ_PRIVILEGED_PHONE_STATE 权限,该权限仅限于系统应用。因此,对于普通应用,可以使用 Build.SERIAL 作为替代,但在 Android 10 上,这个常量已经被弃用并会返回一个占位符值。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    String serial = Build.getSerial();
} else {
    String serial = Build.SERIAL;
}

需要注意的是,在 Android 9(API 28)及以下版本,Build.SERIAL 可以直接使用。在 Android 10 及以上版本,需要系统权限。

2、 Unity

在 Unity 中开发 Android 应用时,获取设备的序列号(SN)也受到 Android 10 及以上版本隐私和安全策略的影响。以下是如何在 Unity 中实现这些功能的方法:

对于 Android 10 及以上版本,获取设备序列号需要使用 Build.getSerial() 方法,并且需要特定的权限。由于 Unity 使用 C# 代码,你需要通过调用 Java 方法来实现这一点。

1)首先,需要在 AndroidManifest.xml 文件中声明权限:

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

2)然后,使用 Unity 的 AndroidJavaObject 类来调用 Java 代码:

using UnityEngine;

public class DeviceInfo : MonoBehaviour
{
    public string GetSerialNumber()
    {
        string serialNumber = "Unknown";

        if (Application.platform == RuntimePlatform.Android)
        {
            try
            {
                using (AndroidJavaClass buildClass = new AndroidJavaClass("android.os.Build"))
                {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                    {
                        using (AndroidJavaObject buildObject = buildClass.CallStatic<AndroidJavaObject>("getSerial"))
                        {
                            serialNumber = buildObject.Call<string>("toString");
                        }
                    }
                    else
                    {
                        serialNumber = buildClass.GetStatic<string>("SERIAL");
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError("Error getting serial number: " + e.Message);
            }
        }

        return serialNumber;
    }
}

三、注意实现

  1. 权限管理:确保在运行时请求必要的权限,特别是在 Android 6.0(API 23)及以上版本中,动态权限请求是必需的。
  2. 兼容性检查:由于不同 Android 版本的行为可能不同,代码中需要进行版本检查。
  3. 隐私合规:获取设备的敏感信息时,请确保应用符合相关的隐私政策和法律法规,通知用户并获得必要的同意。

通过上述步骤,你应该能够在 Unity 中成功获取 Android 10 及以上版本设备的序列号。

四、案例实现简单步骤

1、创建 Unity 工程

2、新建脚本 DeviceInfo ,编写代码获取设备的序列号

3、由于这个可能需要权限申请,编写代码申请权限

4、实现申请电话权限,获取设备序列号(TestDeviceInfo.cs)

5、把脚本 TestDeviceInfo.cs 挂载到场景中

6、在 Player Settings 中的 Build 中勾选 Custom Main Manifest ,添加相关 Phone 权限申请

7、打包运行,可能报如下错误

Exception getting serial number: java.lang.SecurityException: getSerial: The uid 10170 does not meet the requirements to access device identifiers.

错误表明尝试获取设备序列号时遇到了权限问题。在 Android 10(API level 29)及以上版本中,获取设备的序列号需要特殊权限,而这些权限通常不适用于普通应用程序。使用 Settings.Secure.ANDROID_ID 是一种更合适的替代方案,因为它不需要这些特殊权限。

8、下面是如何正确使用 Settings.Secure.ANDROID_ID 来获取设备的 Android ID:

    /// <summary>
    /// 获取设备的 Android ID
    /// </summary>
    /// <returns></returns>
    public static string GetAndroidId()
    {
        string deviceId = string.Empty;

#if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

                using (AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver"))
                {
                    using (AndroidJavaClass secure = new AndroidJavaClass("android.provider.Settings$Secure"))
                    {
                        deviceId = secure.CallStatic<string>("getString", contentResolver, "android_id");
                    }
                }
            }
        }
        catch (AndroidJavaException e)
        {
            Debug.LogError("Exception getting Android ID: " + e.Message);
        }
#endif

        return deviceId;
    }

9、打包运行,能获取到 设备的 Android ID

10、获取设备的 Android ID的 注意事项

  • 唯一性

    • ANDROID_ID 在设备重置时可能会更改,因此不能作为硬件唯一标识符使用,但对于大多数应用场景,它足够可靠。
  • 权限

    • 由于不涉及敏感权限,你不需要在 AndroidManifest.xml 中添加任何额外权限。

五、关键代码

1、DeviceInfo


using UnityEngine;

public class DeviceInfo 
{

    /// <summary>
    /// 获取设备序列号
    /// 需要系统权限
    /// </summary>
    public static string GetSerialNumber()
    {
        string serialNumber = string.Empty;

#if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            using (AndroidJavaClass buildClass = new AndroidJavaClass("android.os.Build"))
            {
                if (GetSDKInt() >= 26) // Build.VERSION_CODES.O == 26
                {
                    serialNumber = buildClass.CallStatic<string>("getSerial");
                }
                else
                {
                    serialNumber = buildClass.GetStatic<string>("SERIAL");
                }
            }
        }
        catch (AndroidJavaException e)
        {
            Debug.LogError("Exception getting serial number: " + e.Message);
        }
#endif

        return serialNumber;
    }

    /// <summary>
    /// 获取版本
    /// </summary>
    /// <returns></returns>
    private static int GetSDKInt()
    {
        int sdkInt = 0;

#if UNITY_ANDROID && !UNITY_EDITOR
        using (AndroidJavaClass versionClass = new AndroidJavaClass("android.os.Build$VERSION"))
        {
            sdkInt = versionClass.GetStatic<int>("SDK_INT");
        }
#endif

        return sdkInt;
    }

    /// <summary>
    /// 获取设备的 Android ID
    /// </summary>
    /// <returns></returns>
    public static string GetAndroidId()
    {
        string deviceId = string.Empty;

#if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

                using (AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver"))
                {
                    using (AndroidJavaClass secure = new AndroidJavaClass("android.provider.Settings$Secure"))
                    {
                        deviceId = secure.CallStatic<string>("getString", contentResolver, "android_id");
                    }
                }
            }
        }
        catch (AndroidJavaException e)
        {
            Debug.LogError("Exception getting Android ID: " + e.Message);
        }
#endif

        return deviceId;
    }
}

2、PermissionWrapper


using UnityEngine;
using UnityEngine.Android;

public class PermissionWrapper : MonoSingleton<PermissionWrapper>
{
    /// <summary>
    /// 请求权限
    /// </summary>
    /// <param name="permission">权限名称</param>
    /// <param name="callback">权限回调:true 权限获取成功回调,false 权限获取失败回调</param>
    public void RequestPermission(string permission, System.Action<string, bool> callback)
    {
        if (Permission.HasUserAuthorizedPermission(permission))
        {
            callback?.Invoke(permission, true);
        }
        else
        {
            StartCoroutine(RequestAndCheckPermission(permission, callback));
        }
    }

    /// <summary>
    /// 协程请求权限
    /// </summary>
    /// <param name="permission">权限名称</param>
    /// <param name="callback">权限回调:true 权限获取成功回调,false 权限获取失败回调</param>
    /// <returns></returns>
    private System.Collections.IEnumerator RequestAndCheckPermission(string permission, System.Action<string, bool> callback)
    {
        Permission.RequestUserPermission(permission);
        yield return new WaitForSeconds(1.0f);

        bool granted = Permission.HasUserAuthorizedPermission(permission);
        callback?.Invoke(permission, granted);
    }
}

3、TestDeviceInfo


using UnityEngine;

public class TestDeviceInfo : MonoBehaviour
{
    /// <summary>
    /// TAG 
    /// </summary>
    const string TAG = "[TestDeviceInfo] ";

    // Start is called before the first frame update
    void Start()
    {

        GetSerialNumber();
    }

    private void GetSerialNumber()
    {
        string permission = "android.permission.READ_PHONE_STATE";
        string sn = string.Empty;
        PermissionWrapper.Instance.RequestPermission(permission, (permission, isGranted) => {
            if (isGranted)
            {
                //sn = DeviceInfo.GetSerialNumber();
                sn = DeviceInfo.GetAndroidId();
            }
            else
            {
                sn = "unknown";
            }

            Debug.Log(TAG + "GetAndroidId() = " + sn);
        });

    }
}

4、AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unity3d.player"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- 获取 sn 的权限 Start-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/> 
    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
    <!-- 获取 sn 的权限 End-->
    
    <application>
        <activity android:name="com.unity3d.player.UnityPlayerActivity"
                  android:theme="@style/UnityThemeSelector">
            <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" />
        </activity>
    </application>
</manifest>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/652780.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【leetcode2765--最长交替子数组】

要求&#xff1a;给定一个数组&#xff0c;找出符合【x, x1,x,x-1】这样循环的最大交替数组长度。 思路&#xff1a;用两层while循环&#xff0c;第一个while用来找到符合这个循环的开头位置&#xff0c;第二个用来找到该循环的结束位置&#xff0c;并比较一下max进行记录。 …

C++模板使用

文章目录 目录 文章目录 前言 一、交换函数(泛型编程) 二、函数模板 2.1 函数模板概念 2.2函数模板格式 2.3使用方法 2.4 函数模板的原理 2.4.1库中的swap 2.5 函数模板的实例化 2.6 模板参数的匹配原则 三、类模板 3.1 类模板的定义格式 3.2类模板声明和定义分离 前言 C语言阶…

Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况实战总结

介绍 本篇对Springboot事务控制中A方法调用B方法Transactional生效与不生效情况进行实战总结&#xff0c;让容易忘记或者困扰初学者甚至老鸟的开发者&#xff0c;只需要看这一篇文章即可立马找到解决方案&#xff0c;这就是干货的价值。喜欢的朋友别忘记来个一键三连哈&#x…

抖音本地生活服务商入驻指南分享!

当前&#xff0c;各大平台的团购外卖业务持续火爆&#xff0c;并逐渐成为众多创业赛道中的大热门。其中&#xff0c;本地生活服务更是在短时间内杀出重围&#xff0c;成为创业者们的首选。 根据抖音生活服务近日发布的《2023年度数据报告》&#xff0c;2023年&#xff0c;抖音生…

微信小程序图片懒加载如何实现?

微信小程序开发时&#xff0c;对于有图片的列表在加载时&#xff0c;为了用户体验更好&#xff0c;必需要对图片做懒加载。 如下图所示&#xff0c;页面在打开时&#xff0c;图片会按需加载&#xff0c;这样用户体验没有那么生硬。 以下将介绍图片懒加载的步骤&#xff1a; 1.…

R18 NTN中的RACH-less HO

在看R18 38.300时,发现NTN场景 增加了如下黄色字体的内容,R18 NTN支持了RACH-less HO,索性就简单看了看。 NTN RACH less HO相关的描述主要在38.331,38.213和38.321中。38.300中的描述显示:网络侧会通过RRCReconfiguration消息将RACH-less HO相关的配置下发给UE, 其中会包…

Java语言ADR药物不良反应系统源码Java+IntelliJ+IDEA+MySQL一款先进的药物警戒系统

Java语言ADR药物不良反应系统源码JavaIntelliJIDEAMySQL一款先进的药物警戒系统源码 ADR药物不良反应监测系统是一个综合性的监测平台&#xff0c;旨在收集、报告、分析和评价药品在使用过程中可能出现的不良反应&#xff0c;以确保药品的安全性和有效性。 以下是对该系统的详细…

Java 面向对象编程(OOP)

面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是Java编程语言的核心思想之一。通过OOP&#xff0c;Java提供了一种结构化的编程方式&#xff0c;使代码更易于维护和扩展。 一、类和对象 1. 类的定义 类是对象的蓝图或模板&#xff0c;定…

【qt】一次性学会所有对话框

对话框 一.前言二.文件对话框1.选择一个文件2.选择多个文件3.选择目录4.保存文件 三.颜色对话框1.获取颜色 四.字体对话框1.获取字体 五.输入对话框1.输入文本2.输入整数3.输入小数4.输入条目 六.消息对话框1.问题框2.信息框3.警告框4.危机框5.关于框6.关于qt框七.总结 一.前言…

CSS学习笔记:动画——使用animation添加动画效果

过渡和动画 啥是过渡? 例如transition: all 0.5s; -> 拥有该属性的标签&#xff0c;在样式改变时&#xff0c;将在设定的时间内逐渐过渡到另一个样式 啥是动画&#xff1f; 和过渡有点类似&#xff0c;只不过常常用于实现多个状态间的变化过程&#xff0c;动画过程可控…

基于PHP+MySQL组合开发的720VR全景小程序源码系统 一键生成三维实景 前后端分离带网站的安装代码包以及搭建教程

系统概述 这款源码系统是专门为实现 720VR 全景展示而设计的。它结合了先进的技术和创新的理念&#xff0c;能够将真实场景以全景的形式呈现给用户&#xff0c;让用户仿佛身临其境。该系统采用 PHP 进行后端开发&#xff0c;MySQL 作为数据库管理系统&#xff0c;确保了系统的…

【JAVA |Object类重写实例】Cloneable 接口、Comparable接口、比较器

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; &#x1f388;丠丠64-CSDN博客&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起…

阿贝云免费虚拟主机及免费云服务器评测

阿贝云是一家提供免费虚拟主机和免费云服务器的公司&#xff0c;其服务质量备受用户好评。用户可以通过阿贝云的网站 https://www.abeiyun.com 进行申请并获得免费服务。首先&#xff0c;我们来看看阿贝云的免费虚拟主机服务。免费虚拟主机提供了足够的存储空间和带宽&#xff…

HackTheBox-Machines--Cronos

文章目录 0x01 信息收集0x02 命令注入漏洞0x03 权限提升 Cronos 测试过程 0x01 信息收集 1.端口扫描 发现 SSH&#xff08;22&#xff09;、DNS&#xff08;53&#xff09;、HTTP&#xff08;80&#xff09;端口 nmap -sC -sV 10.129.227.2112.53端口开启&#xff0c;进行DNS…

靶机Moonraker_1练习报告

Moonraker: 1靶机练习实践报告 一、安装靶机 靶机是.ova文件&#xff0c;需要用VirtualBox打开&#xff0c;但我习惯于使用VMWare,因此修改靶机文件&#xff0c;使其适用于VMWare打开。 解压ova文件&#xff0c;得到.ovf文件和.vmdk文件。 直接用VMWare打开.ovf文件即可。 …

【VTKExamples::Utilities】第四期 CameraModifiedEvent

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例CameraModifiedEvent,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. CameraModifi…

OpenMV的VisionBoard视觉识别开发板学习记录

此篇博客仅用于对VisionBoard的开发板的学习研究记录&#xff0c;没有教学内容。 一、资料来源 开发板资料链接 开发板环境搭建手册 开发板视频教程 板子的资料网站 openmv官方的网站 目录 一、资料来源二、针对 VisionBoard的目标识别和定位总结1. 目标识别功能1.1 物体检测…

react ant 表格实现 拖拽排序和多选

项目背景 : react ant 要实现 : 有多选功能(实现批量删除 , 也可以全选) 可以拖拽(可以复制 , 方便顶部的搜索功能) 要实现效果如下 1 这是最初的拖拽功能实现 , 不能复制表格里的内容 , 不符合要求 2 更改了ROW的内容 , 实现了可以复制表格内容 代码 //控制是否可以选中表格…

Oracle数据库操作问题汇总

一、简介 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性好、使用方便、功能强&…

数据结构--二叉树--顺序存储判断是否二叉搜索树(2022统考真题)

数据结构–二叉树–顺序存储判断是否二叉搜索树(2022统考真题) 题目描述&#xff1a; 思路 二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称BST&#xff09;是一种具有以下性质的二叉树&#xff1a; 对于树中的每个节点 N&#xff0c;它的左子树&#xff08;如果…