Android 唯一标识码

本文详细介绍了在Android中获取设备唯一标识的各种方法,包括MAC地址、DEVICE_ID、IMEI/IMSI、Pseudo-Unique ID以及Combined Device ID。讨论了各自的优缺点,如MAC地址的唯一性但获取限制,IMEI涉及隐私问题,以及如何在不同API级别下实现设备唯一标识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MAC

优点

好处就是唯一,不会有重复,而且除非硬改,否则是不会改变的。

缺点

硬件限制:并不是所有的设备都有Wifi和蓝牙硬件,硬件不存在自然也就得不到这一信息。
  获取的限制:如果Wifi没有打开过,是无法获取其Mac地址的;而蓝牙是只有在打开的时候才能获取到其Mac地址。

添加权限

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

使用WifiManager获取MAC地址

可以使用手机Wifi或蓝牙的MAC地址作为设备标识,但是并不推荐这么做,原因有以下两点:

/**
 * Created by Notzuonotdied on 5/13/17.
 * 直接使用WifiManager获取MAC地址
 */
public class Method1 {

    public static void getLocalMAC(Context context) {
        @SuppressLint("WifiManagerLeak")
        WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo info = wifi.getConnectionInfo();
        Toast.makeText(context, info.getMacAddress(), Toast.LENGTH_SHORT).show();
    }
}

根据IP获取本地Mac


/**
 * Created by Notzuonotdied on 5/13/17.
 * 根据IP获取本地Mac
 * 使用Java获取设备网络设备信息的
 * API——NetworkInterface.getNetworkInterfaces() 
 * ——仍然可以间接地获取到安卓6.0的MAC地址。 
 */

public class Method2 {

    public static String getMac(Context context) {
        String mac_s = "";
        try {
            byte[] mac;
            NetworkInterface ne = NetworkInterface.getByInetAddress(InetAddress.getByName(getLocalIpAddress()));
            mac = ne.getHardwareAddress();
            mac_s = byte2hex(mac);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return mac_s;
    }

    private static String byte2hex(byte[] b) {
        StringBuffer hs = new StringBuffer(b.length);
        String stmp;
        int len = b.length;
        for (int n = 0; n < len; n++) {
            stmp = Integer.toHexString(b[n] & 0xFF);
            if (stmp.length() == 1)
                hs = hs.append("0").append(stmp);
            else {
                hs = hs.append(stmp);
            }
            if (n + 1 != len) {
                hs = hs.append(":");
            }
        }
        return String.valueOf(hs);
    }

    /**
     * 获取本地IP
     * */
    private static String getLocalIpAddress() {
        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
                NetworkInterface intf = en.nextElement();
                for (Enumeration<InetAddress> enumIpAddr = intf
                        .getInetAddresses();
                         enumIpAddr.hasMoreElements();) {
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
                        return inetAddress.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用busybox获取本地Mac


/**
 * Created by Notzuonotdied on 5/13/17.
 * 根据busybox获取本地Mac
 */

public class Method3 {

    public static String getMac() {
        String result;
        String Mac;
        result = callCmd("Method3 ifconfig", "HWaddr");
        //如果返回的result == null,则说明网络不可取
        if (result == null) {
            return "网络出错,请检查网络";
        }
        //对该行数据进行解析
        //例如:eth0      Link encap:Ethernet  HWaddr 00:16:E8:3E:DF:67
        if (result.length() > 0 && result.contains("HWaddr") == true) {
            Mac = result.substring(result.indexOf("HWaddr") + 6, result.length() - 1);
            result = Mac;
        }
        return result;
    }

	private String callCmd(String cmd, String filter) {
        String result = "";
        String line;
        try {
            Process proc = Runtime.getRuntime().exec(cmd);
            InputStreamReader is = new InputStreamReader(proc.getInputStream());
            BufferedReader br = new BufferedReader(is);
            //执行命令cmd,只取结果中含有filter的这一行
            while ((line = br.readLine()) != null) {
                if (line.contains(filter)) {
                    result = line;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

源码

附录

DEVICE_ID

这是Android系统为开发者提供的用于标识手机设备的串号,也是各种方法中普适性较高的,可以说几乎所有的设备都可以返回这个串号,并且唯一性良好。

这个DEVICE_ID可以同通过下面的方法获取:

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); String DEVICE_ID = tm.getDeviceId(); 

它会根据不同的手机设备返回IMEI,MEID或者ESN码,但在使用的过程中有以下问题:

  • 非手机设备:最开始搭载Android系统都手机设备,而现在也出现了非手机设备:如平板电脑、电子书、电视、音乐播放器等。这些设备没有通话的硬件功能,系统中也就没有TELEPHONY_SERVICE,自然也就无法通过上面的方法获得DEVICE_ID。
  • 权限问题:获取DEVICE_ID需要READ_PHONE_STATE权限,如果只是为了获取DEVICE_ID而没有用到其他的通话功能,申请这个权限一来大才小用,二来部分用户会怀疑软件的安全性。
  • 厂商定制系统中的Bug:少数手机设备上,由于该实现有漏洞,会返回垃圾,如:zeros或者asterisks

IMEI和IMSI

这个信息只有可以插SIM卡的设备才有。通常用户会因为你向他们要这个权限而给你一个差评,因为他们觉得你就是在窃取他们的隐私,很明显,你就是在搜集一些数据。
  IMEI在理论上来讲,应该是唯一的(除非你有一个没有量产的手机(水货),那么它就可能是无效的IMEI,如:0000000000000)。

简介

百度百科 - IMEI

IMEI(International Mobile Equipment Identity)是国际移动设备身份码的缩写,国际移动装备辨识码,是由15位数字组成的"电子串号",它与每台移动电话机一一对应,而且该码是全世界唯一的。每一只移动电话机在组装完成后都将被赋予一个全球唯一的一组号码,这个号码从生产到交付使用都将被制造生产的厂商所记录。
  双卡双待手机会有两个IMEI,IMEI和IMSI存在一一对应的关系。因为双卡双待手机两个卡槽都能插SIM卡,在GSM蜂窝通信网络中被看作是集成在一起的两个设备,所以会被分配有2个IMEI码。

百度百科 - IMSI

国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。


添加权限

在获取之前我们需要在AndroidManifest.xml中添加权限:

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

获取方法

在Android中获取IMEI号码的方式为:

/**
 * IMEI
 */
public static String getIMEI(Context context) {
	TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(context.TELEPHONY_SERVICE);
	return telephonyManager.getDeviceId();
}

在Android中获取IMSI号码的方式为:

/**
 * IMSI
 */
public static String getIMSI(Context context){
	TelephonyManager mTelephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
	return mTelephonyMgr.getSubscriberId();
}

Pseudo-Unique ID

简介

这种方法是依靠使用硬件信息,即通过读取设备的ROM版本号、厂商名、CPU型号和其他硬件信息来构建15位的唯一标识码。
  **注意:**没有具体测试过重复率,但是在要求数据要求不高的情况下可以使用。
  优点就是不需要加入READ_PHONE_STATE许可权限,缺点就是有一定的重复率(因为如果两个手机应用了同样的硬件以及Rom 镜像)。

不需要任何权限获得Android设备的唯一ID,权限android设备id

API >=9:通过“Build.SERIAL”这个属性来保证ID的独一无二。API 9 以上的Android设备目前市场占有率在99.5%
  记住:你只在技术上忽略了0.5%的用户,你可以专注于99.5%的用户。
  API < 9:我们可以通过读取设备的ROM版本号、厂商名、CPU型号和其他硬件信息来组合出一串15位的号码,这15位号码有可能重复,但是几率太小了,小到可以忽略,况且就算重复了,我们损失的用户最多也只不过是0.5%而已。

实现

 public String getUniquePseudoID() {
        String serial;

        String m_szDevIDShort = "233" +
                Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
                Build.DEVICE.length() % 10 + Build.USER.length() % 10 +
                Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
                Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
                Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
                Build.TAGS.length() % 10 + Build.TYPE.length() % 10; //12 位
        try {
            serial = android.os.Build.class.getField("SERIAL").get(null).toString();
            //API>=9 使用serial号
            return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
        } catch (Exception exception) {
            //serial需要一个初始化
            serial = "serial"; // 随便一个初始化
        }
        //使用硬件信息拼凑出来的15位号码
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    }

Combined Device ID

IDFV

和上述的Combined Device ID的思路差不多,可以使用Android IdIMEI码、Mac地址、手机型号android.os.Build.MODEL以及手机厂商android.os.Build.MANUFACTURER进行MD5,将结果作为唯一标识码。

附录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值