IMEI+mac+uuid实现单点登录

背景

前段时间正在做单点登录相关的需求,提到单点登录不得不提到android设备的唯一标识,因为只有获取到设备的唯一标识并且传给服务器,服务器才能知道是不是有两台手机同时登录了一个账号。
#### 获取唯一标识的几种方法
1 IMEI

public static String getIMEI(Context context) {
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(context.TELEPHONY_SERVICE);
        String imei = telephonyManager.getDeviceId();

        return imei;
    }

缺点:
- 6.0以上需要android.permission.READ_PHONE_STATE权限
- android 碎片化严重,获取不到deviceid
- 只能获取到具备打电话功能的设备,如果是pad 则不起作用
- 如果用户手机root,则可以通过第三方软件修改imei

2 AndroidId

String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID);    

优点:获取方式简单,不需要获取权限

缺点:手机恢复出厂设置该值就会发生变化,碎片化严重有的设备获取不到Android_id

3 Serial Number

String SerialNumber = android.os.Build.SERIAL; 

Android系统2.3版本以上可以通过下面的方法得到Serial Number,且非手机设备也可以通过该接口获取。不需要权限,通用性也较高,但我测试发现红米手机返回的是 0123456789ABCDEF 明显是一个顺序的非随机字符串。也不一定靠谱。

4 Mac地址 – 属于Android系统的保护信息,高版本系统获取的话容易失败,返回0000000;

5 SimSerialNum
看名字也知道需要插入sim的设备,而且他也需要获取手机的android.permission.READ_PHONE_STATE权限

结论

由于历史原因,我公司用的是IMEI+MAC+uuid的方式获取唯一id
代码如下:

public static String getImei(final Context context) {
        if (!TextUtils.isEmpty(mDeviceImei)) {
            return mDeviceImei;
        }

        TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        String imei;

        try {
            imei = tm.getDeviceId();// imei
        } catch (Exception e) {
            LOGGER.e(TAG, "imei obtained exception", e);
            imei = null;
        }

        if (StringUtils.isEmpty(imei) || "0".equals(imei)) {
            // 如果imei号为空或0,取mac地址作为imei号传递
            try {
                WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                WifiInfo info = wifi.getConnectionInfo();
                imei = info.getMacAddress();
            } catch (Exception e) {
                imei = null;
            }

            // 如果mac地址为空或0,则通过uuid生成的imei号来传递
            if (StringUtils.isEmpty(imei) || "0".equals(imei)) {
                imei = SharePersistentUtils.getString(context, LibConstant.IMEI);
                LOGGER.d(TAG, "imei = " + imei);

                if (StringUtils.isEmpty(imei)) {
                    imei = UUIDUtils.getUUID(15);
                    if (StringUtils.isEmpty(imei)) {
                        return "0";
                    }
                    LOGGER.d(TAG, "imei new = " + imei);
                    SharePersistentUtils.saveString(context, LibConstant.IMEI, imei);
                    /*
                     * SharePersistentUtils.savePerference(context,
                     * UtilLibConstant.IMEI, imei);
                     */
                }
            }
        }

        // TODO (ly) 转为小写
        if (imei != null) {
            imei = imei.toLowerCase(Locale.US);
        }

        setmDeviceImei(imei);

        return imei;
    }

代码逻辑:
- 先获取imei(这里并没有判断版本,后面会说会引起的问题)
- 如果imei为空,再获取mac地址
- 如果mac 地址也为空,则利用uuid随机生成一串编码,并且保存到文件中。

存在问题:

  • imei在6.0之前不需要权限可以直接获取,6.0以后需要read_phone_state
    权限。此处并没有申请权限,会导致6.0以上手机一直获取的是mac地址或者是uuid 生成的编码
  • 考虑这样一种情况,即使此处申请了 权限,但是用户拒绝了(此时唯一编码肯定是mac地址或者是uuid),用户操作app后续界面又弹出了相同权限(read_phone_state)此时用户同意了,下次进入app后获取的一定是imei,这将会导致第二次进入app会被登出(两次打开app设备唯一id上传不一致)
  • 由于我们公司需要在所有接口上传基础信息(包括设备唯一id),所以此段代码的调用放在了application中,这也就是为什么此处没有申请权限的原因(application没法调用 requestPermission,这个会考虑将代码调用后移,最好放在一个activity中,例如WelcomeActivity)

如果你的需求是在activity中,那么你只需要将上面代码imei获取处增加版本判断申请权限,那么一般来说上面的逻辑还是比较完善的。

下面说说我开发中遇到的坑爹问题,以及解决思路(都是泪T.T)

先说问题,每次安装好发现首次打开app登录成功后杀掉进程,第二次再打开的时候就会提示我重新登录,后面就不会出现这个问题了,这个问题困扰了我好几个小时。

解决:

  • 由于手机本身问题跳过了前两步,每次获取的都是uuid生成的编码,但是很奇怪的是第二次打开app会发现存储的uuid和上一次不一致,
  • 我首先联想到的是是不是多线程调用。然后我习惯性的看了方法的调用处,果然有很多处。我一一排查并没有多线程的问题
  • 就在我要崩溃的时候,我突然发现项目中有个pushlib的进程也在打印 getImei()方法的信息,我立即联想到多进程导致的问题。
  • 我找到了pushlib进程的一个service,在里面并没有发现有调用getImei()的地方
  • 就在我又快崩溃的时候,我想到既然pushlib进程并没有主动调用getImei()方法,会不会是“被调用”,最后我想到了多进程会导致Application被初始化多次,果然在Application中发现了调用getImei()的痕迹,此时问题迎刃而解,app启动的时候getImei()会被两个进程调用两次,导致uuid被获取两次(类似于线程并发问题),所以第一次和第二次启动app会不一致

找到问题根源,说下我的解决方案:

// 避免其他进程重复初始化application,导致此方法被调用多次。
    public String getImei(){
        String processName = getProcessName(this, Process.myPid());
        if (null == processName || processName.endsWith(":pushservice")) {
            return;
        }
        ....
        //否则执行后续代码
   }

猜你喜欢

转载自blog.csdn.net/u011508817/article/details/82253705
今日推荐