获取Android设备的唯一标识

0 阅读1分钟

一、最终兼容版工具类(Android 5 ~ 14 全支持)

image.png

特点

  • ✅ 公共存储,卸载 APP 不删除
  • ✅ 严格遵循你的流程图
  • ✅ 安卓 13/14 完美兼容
  • ✅ 双备份:内部 SP + 外部公共文件
  • ✅ 外部存储状态判断保留(防崩溃)

java

运行

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

/**
 * 设备唯一 UUID 工具类(全安卓版本兼容:5.0 ~ 14.0)
 * 存储到 公共外部存储,卸载APP不删除,重装可恢复
 */
public class DeviceUUIDUtil {
    private static final String TAG = "DeviceUUIDUtil";
    private static final String SP_NAME = "device_uuid_sp";
    private static final String KEY_UUID = "device_unique_id";
    private static final String FILE_NAME = "device_uuid.txt";

    private static DeviceUUIDUtil instance;
    private final Context appContext;

    private DeviceUUIDUtil(Context context) {
        this.appContext = context.getApplicationContext();
    }

    public static DeviceUUIDUtil getInstance(Context context) {
        if (instance == null) {
            instance = new DeviceUUIDUtil(context);
        }
        return instance;
    }

    // ====================== 核心流程(严格按流程图)======================
    public String getDeviceUUID() {
        // 1. 首次启动
        if (isFirstLaunch()) {
            String uuid = generateUUID();
            saveToInternal(uuid);
            saveToPublicStorage(uuid);
            markFirstLaunchDone();
            return uuid;
        }

        // 2. 非首次 → 同步
        syncLogic();

        String internal = getFromInternal();
        String external = getFromPublicStorage();

        // 3. 都为空 → 重新生成
        if (internal == null && external == null) {
            String uuid = generateUUID();
            saveToInternal(uuid);
            saveToPublicStorage(uuid);
            return uuid;
        }

        // 4. 一致 → 直接返回
        if (internal != null && internal.equals(external)) {
            return internal;
        }

        // 5. 不一致 → 外部优先(保证设备唯一)
        if (external != null) {
            saveToInternal(external);
            return external;
        } else {
            saveToPublicStorage(internal);
            return internal;
        }
    }

    // 可扩展:云端同步
    private void syncLogic() {
        Log.d(TAG, "执行UUID同步...");
    }

    // 生成唯一ID
    private String generateUUID() {
        return UUID.randomUUID().toString();
    }

    // ====================== 内部存储(SP)======================
    private String getFromInternal() {
        return appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .getString(KEY_UUID, null);
    }

    private void saveToInternal(String uuid) {
        appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .edit()
                .putString(KEY_UUID, uuid)
                .apply();
    }

    // ====================== 公共存储(兼容 Android 13/14)======================
    /**
     * 读取公共存储UUID(卸载不删除)
     */
    private String getFromPublicStorage() {
        if (!isExternalReadable()) return null;

        // 兼容全版本:公共下载目录(安卓13/14可正常读取)
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), FILE_NAME);

        if (!file.exists()) return null;

        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[(int) file.length()];
            fis.read(buffer);
            return new String(buffer).trim();
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 写入公共存储(兼容 Android13/14)
     */
    private void saveToPublicStorage(String uuid) {
        if (uuid == null || !isExternalWritable()) return;

        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), FILE_NAME);

        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(uuid.getBytes());
            Log.d(TAG, "UUID 已保存到公共存储: " + uuid);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // ====================== 外部存储状态判断(必须保留)======================
    private boolean isExternalWritable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state);
    }

    private boolean isExternalReadable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
    }

    // ====================== 首次启动标记 ======================
    private boolean isFirstLaunch() {
        return appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .getBoolean("first_launch", true);
    }

    private void markFirstLaunchDone() {
        appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .edit()
                .putBoolean("first_launch", false)
                .apply();
    }
}

二、AndroidManifest.xml 权限配置(全版本兼容)

xml

<!-- 基础存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Android 13+ 新权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Android 10 兼容 -->
<application
    android:requestLegacyExternalStorage="true"
    ...>
</application>

三、动态权限申请代码(Activity 中使用)

1. 权限申请代码

java

运行

import android.Manifest;
import android.os.Build;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

public class BaseActivity extends AppCompatActivity {
    private static final int REQ_CODE = 100;

    // 在onCreate调用
    private void requestStoragePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_MEDIA_IMAGES},
                    REQ_CODE);
        } else {
            // Android 12 及以下
            ActivityCompat.requestPermissions(this,
                    new String[]{
                            Manifest.permission.READ_EXTERNAL_STORAGE,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    REQ_CODE);
        }
    }
}

2. 使用工具类

java

运行

// 权限申请成功后调用
String deviceId = DeviceUUIDUtil.getInstance(this).getDeviceUUID();

四、为什么这样改能兼容 Android13/14?

1. 路径改为:

java

运行

Environment.DIRECTORY_DOWNLOADS // 公共下载目录
  • 全版本可写
  • 卸载 APP不删除
  • 重装 APP可读取
  • 完美满足设备唯一标识需求

2. 权限适配

  • Android 13-:用旧存储权限
  • Android 13+:用 READ_MEDIA_IMAGES
  • 高版本安卓不再报错、不再写入失败

3. 状态判断保留

  • 防止 SD 卡未挂载、只读、损坏导致崩溃
  • 保证工具类健壮性

五、最终总结(最关键)

公共存储 → 卸载不删除

Android 13/14 完美兼容

流程图逻辑 100% 不变

设备唯一标识永久保存

无需私有目录、不随 APP 删除