Android 获取 唯一GUID ,替换 IMEI (兼容 Android 10+获取IMEI问题)

Posted Tobey_r1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 获取 唯一GUID ,替换 IMEI (兼容 Android 10+获取IMEI问题)相关的知识,希望对你有一定的参考价值。

背景

  谷歌在Android 10(API 级别 29)对不可重置的标识符(包括 IMEI 和序列号)添加了限制,导致我们无法获取到对应的IMEI值,不过给了我们其他方法就是获取GUID来作为唯一表示:
在这里插入图片描述

关于

  最近项目上需求需要在用户登录的时候获取手机设备唯一标识,原来的代码里面有获取的方法:
  手机调试版本 OPPO Android 11

 /**
     * 获取系统sn
     *
     * @return
     */
    public static String getSn(Context context) {
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            return "";
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return  Build.getSerial();
            //也有用到ANDROID_ID,但是因为会变所以不合适,如果不是特别要求的话你们可以使用下面这个,这样就不用继续往下看去适配了
            //Settings.System.getString( App.getInstance().getContentResolver(), Settings.Secure.ANDROID_ID);
        } else {
            String sn = null;
            try {
                Class<?> c = Class.forName("android.os.SystemProperties");
                Method get = c.getMethod("get", String.class);
                sn = (String) get.invoke(c, "ro.serialno");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sn;
        }
    }

  上面那段代码不会奔溃报错,但是返回的sn值在Android Q(10)+为null,即我们获取不到值,在谷歌开发者平台上给了我们适配方法,就是通过获取32位GUID来作为唯一表示,因为GUID在当前时间点是唯一的,所以可以作为标记设备唯一:
在这里插入图片描述
  就是通过String u = UUID.randomUUID().toString();来获取,当然了获取之后才是重点,因为这个UUID是根据一些时间等逻辑进行实时生成的,所以我们需要对它进行保存,以便作为唯一标识。

保存GUID方案

  • 第一种,使用SharedPreferemces存储(缺点会随着app卸载移除)
  • 第二种.使用android自带sqlite数据库存储(这里我用的郭老师的litepal存储,但是后面发现也会随着app卸载移除)
  • 第三种,以外部存储方式存储文件(不存在app私有目录下)(缺点文件删除,UUID随之移除)

第一种,SharedPreferemces存储

  存储uuid

// 通过判断isFirst的值来进行第一次uuid的存储,后续再打开app都不会刷新这个存储的uuid的值,app卸载或者重新安装会被重置
  boolean isFirst;
        SharedPreferences sharedPref = App.getInstance().getSharedPreferences(
                "myuuid", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        isFirst = sharedPref.getBoolean("isFirst",true);
        if (isFirst){
            String u = UUID.randomUUID().toString();
            //这里我将 5a5fbf50-b063-425b-88bb-fd04095d92f8 替换成 5a5fbf50b063425b88bbfd04095d92f8(32位)
            u = u.replace("-","");
            editor.putString("uuid",u).apply();
            editor.putBoolean("isFirst",false).apply();
        }

  取出uuid:

 /**
     * 获取系统sn
     *
     * @return
     */
    public static String getSn(Context context) {
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            return "";
        }
        if (Build.VERSION.SDK_INT >= 29) {
            String u = UUID.randomUUID().toString();
            u = u.replace("-","");
            SharedPreferences sharedPref = App.getInstance().getSharedPreferences(
                    "myuuid", Context.MODE_PRIVATE);
            String uuid = sharedPref.getString("uuid",u);
            return uuid;   
        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return  Build.getSerial();

        } else {
            String sn = null;
            try {
                Class<?> c = Class.forName("android.os.SystemProperties");
                Method get = c.getMethod("get", String.class);
                sn = (String) get.invoke(c, "ro.serialno");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sn;
        }
    }

第二种,sqlite数据库存储

  配置参考Android简单使用Litepal 数据库学习笔记(Android与数据库 (一)),我这边直接上步骤,不做细致解释了:
添加build引用:

implementation 'org.litepal.android:java:3.0.0'

  然后在个人的app下初始化:

LitePal.initialize(this);

  新增一个UuidString.java类:

public class UuidString extends LitePalSupport {
    private String uuid;

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }
}

  在src/main下新建一个assets文件夹,在文件夹下新建一个litepal.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
    <dbname value="uuiddb" ></dbname>
    <version value="1"></version>
    <list>

        <mapping class="com.xxxxxx.config.UuidString"></mapping>
    </list>
</litepal>

  在登录界面添加如下:

  String u = UUID.randomUUID().toString();
  u = u.replace("-","");
  LitePal.getDatabase();
  //查询对应表第一条数据
 UuidString uuid = LitePal.findFirst(UuidString.class);
 if (uuid == null){
      UuidString uuidString = new UuidString();
       uuidString.setUuid(u);
       uuidString.save();
  }

  然后再获取sn的方法里面添加如下:

 UuidString uui2 = LitePal.findFirst(UuidString.class);
 return uui2.getUuid();

第三种,新增文件方式存储(还是逃不过啊)

  我这里没有将uuid写到text文本里,直接将他赋值命名这个text文本了,这就省去了读取流的操作,当然了需要读写权限,权限申请这里就不贴代码了:
  思路首先我们新建一个文件夹在/storage/emulated/0/下面,免得App卸载也会删除对应文件夹,然后我们还要对这个文件夹/storage/emulated/0/netLog/下面进行是否存在文件的判断,有文件说明我们第一次运行程序的时候已经生成了唯一UUID的text文本,我们只需要取出来使用即可,没有那就新增一个UUID的文本即可,下面是详细步骤:
  首先新增(为了代码看起来简洁)一个静态方法帮助类StaticMethodUtil.java

public class StaticMethodUtil {
 
  /**
     * 获取文件
     *
     * @return 文件
     */
    public static String getLogFile(String uuid) {
        File file;
       //因为适用版本就是Android10+的,所以不必担心这里报错
        file = new File(Environment.getExternalStorageDirectory()+"/netLog");
        // 若目录不存在则创建目录
        if (!file.exists()) {
            file.mkdir();
        }
        if (!hasFile(file.getPath())){
            File logFile = new File(file.getPath()+"/" +uuid+ ".txt");
            if (!logFile.exists()) {
                try {
                    logFile.createNewFile();
                } catch (Exception e) {
                    Log.e("文件帮助类!", "Create log file failure !!! " + e.toString());
                }
            }
            return logFile.getName();
        }else {
            return getFileName(file.getPath(), ".txt").get(0);
        }


    }
    //判断文件夹下是否含有文件
    public static boolean hasFile(String fileAbsolutePaht) {
        File file = new File(fileAbsolutePaht);
        return file.list().length==0?false:true;
    }
 
}

  修改获取sn的方法代码(一定要注意读写权限):

/**
     * 获取系统sn
     *
     * @return
     */
    public static String getSn(Context context) {
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            return "";
        }
        if (Build.VERSION.SDK_INT >= 29) {
            String u = UUID.randomUUID().toString();
            u = u.replace("-","");
            return StaticMethodUtil.getLogFile(u).replace(".txt","");
        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return  Build.getSerial();
        } else {
            String sn = null;
            try {
                Class<?> c = Class.forName("android.os.SystemProperties");
                Method get = c.getMethod("get", String.class);
                sn = (String) get.invoke(c, "ro.serialno");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sn;
        }
    }

  看下效果图(文件夹):
在这里插入图片描述
  到此本篇文章结束,有问题欢迎批评指正,觉得不错的也请点个赞哦

以上是关于Android 获取 唯一GUID ,替换 IMEI (兼容 Android 10+获取IMEI问题)的主要内容,如果未能解决你的问题,请参考以下文章

GUID获取16位19位22位的唯一字符串

GUID转换成16位19位22位唯一字符串

QT软件开发-得到唯一文件名-当前时间与GUID

QT软件开发-得到唯一文件名-当前时间与GUID

QT软件开发-得到唯一文件名-当前时间与GUID

C# GUID转换成16位字符串或19位数字并确保唯一