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

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

目录

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

一、简单介绍

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

1、Android

[2、 Unity](#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 上,这个常量已经被弃用并会返回一个占位符值。

java 复制代码
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 文件中声明权限:

XML 复制代码
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />

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

cs 复制代码
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:

cs 复制代码
    /// <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

cs 复制代码
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

cs 复制代码
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

cs 复制代码
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 复制代码
<?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>
相关推荐
移动开发者1号2 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号2 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
X_StarX2 小时前
【Unity笔记01】基于单例模式的简单UI框架
笔记·ui·unity·单例模式·游戏引擎·游戏开发·大学生
九班长2 小时前
Golang服务端处理Unity 3D游戏地图与碰撞的详细实现
3d·unity·golang
ysn111114 小时前
NGUI实现反向定位到层级面板结点
unity
ii_best6 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk7 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭11 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
Thomas_YXQ11 小时前
Unity3D DOTS场景流式加载技术
java·开发语言·unity
aqi0012 小时前
FFmpeg开发笔记(七十七)Android的开源音视频剪辑框架RxFFmpeg
android·ffmpeg·音视频·流媒体