2017年6月28日星期三

Android唯一标识

Android设备用来唯一标示用户或设备的方法很多,没有统一的完美的方案。
有以下常用方案:
  1. IMEI/MEID
  2. ANDROID_ID
  3. Mac address
  4. Serial number
  5. Installation / GUID

IMEI/MEID

介绍

下面是维基百科上IMEI的介绍:
国际移动设备识别码(International Mobile Equipment Identity,IMEI),即通常所说的手机序列号、手机“串号”,用于在移动电话网络中识别每一部独立的手机等行动通讯装置,相当于移动电话的身份证。序列号共有15位数字,前6位(TAC)是型号核准号码,代表手机类型。接着2位(FAC)是最后装配号,代表产地。后6位(SNR)是串号,代表生产顺序号。最后1位(SP)一般为0,是检验码,备用。国际移动设备识别码一般贴于机身背面与外包装上,同时也存在于手机内存中,通过输入*#06#即可查询。
下面是百度百科里MEID的介绍:
MEID 移动设备识别码(Mobile Equipment Identifier)是CDMA手机的身份识别码,也是每台CDMA手机或通讯平板唯一的识别码。通过这个识别码,网络端可以对该手机进行跟踪和监管。用于CDMA制式的手机。MEID的数字范围是十六进制的,和IMEI的格式类似。
MEID's are also manged by the TIA. These are 56 bits long, and like ESN's, identify the manufacturer of a mobile device as well as the serial number assigned to the device by that manufacturer.
The MEID consists of 32 bits to specify the manufacturer and 24 bits to specify the serial number: The first 4 bits of the manufacturer code are "reserved," and restricted to just a few values. There can also be a 4 bit "check digit" appended to the end, but this is not transmitted between the mobile device and the CDMA system (according to 3GPP2 S.R0048-A) and is not common to see.

获取

Android中获取IMEI的方法,首先需要在AndroidManifest.xml文件中增加android.permission.READ_PHONE_STATE权限:
<manifest ...>
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application ...>
    ...
    </application>
</manifest>
然后获取IMEI
final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String imei = tm.getDeviceId();
我们看下TelephonyManager中的getDeviceId方法:
/**
 * Returns the unique device ID, for example, the IMEI for GSM and the MEID
 * or ESN for CDMA phones. Return null if device ID is not available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 */
public String getDeviceId();
下面是API level 23新增的方法:
/**
 * Returns the unique device ID of a subscription, for example, the IMEI for
 * GSM and the MEID for CDMA phones. Return null if device ID is not available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 *
 * @param slotId of which deviceID is returned
 */
public String getDeviceId(int slotId);
这两个接口可以返回GSM手机的IMEI或者CDMA手机的MEIDslotId是卡槽编号。
这两个接口在API level O中都标记为@Deprecated,取而代之的是如下接口:
/**
 * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
 * available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 */
public String getImei();

/**
 * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
 * available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 *
 * @param slotIndex of which IMEI is returned
 */
public String getImei(int slotIndex);

/**
 * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 */
public String getMeid();

/**
 * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 *
 * @param slotIndex of which MEID is returned
 */
public String getMeid(int slotIndex);

存在的问题

  1. 需要android.permission.READ_PHONE_STATE权限,用户可以禁止授权给应用
  2. 非手机设备没有IMEI/MEID
  3. IMEI/MEID理论上应该没有重复,实际却不是这样的,有些厂商生产的设备会有重复

ANDROID_ID

下面是ANDROID_ID的解释:
A 64-bit number (as a hex string) that is randomly generated when the user first sets up the device and should remain constant for the lifetime of the user's device. The value may change if a factory reset is performed on the device.

获取

import android.provider.Settings.Secure;

final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);

存在的问题

  1. 设备重置后会重新生成ANDROID_ID
  2. 部分厂商的设备存在ANDROID_ID重复的情况,同样不是绝对可靠
  3. Android O 版本中,对于设备上的每个应用和用户,都有不同的ANDROID_ID

Mac address

mac地址或蓝牙地址也可以用作设备标识

获取

获取mac地址需要android.permission.ACCESS_WIFI_STATE权限:
<manifest ...>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <application ...>
        ...
    </application>
</manifest>
获取mac代码:
String mac = null;
final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if (wifiManager != null) {
    final WifiInfo info = wifiManager.getConnectionInfo();
    if (info != null) {
        mac = info.getMacAddress();
    }
}

存在的问题

下面是 Android 官网的说明
MAC 地址具有全局唯一性,无法由用户重置,在恢复出厂设置后也不会发生变化。一般不建议使用 MAC 地址进行任何形式的用户标识。因此,从 Android M 开始,无法再通过第三方 API 获得本地设备 MAC 地址(例如,WLAN 和蓝牙)。WifiInfo.getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法都会返回 02:00:00:00:00:00。
  1. Android M开始无法获取 Mac 地址
  2. 没有 WIFI 的设备无法获取
  3. Wifi 没有打开时也无法获取 Mac 地址

Serial number

获取

final String sn =  android.os.Build.SERIAL;
这是API level 9添加的,在 Android O 中这个字段被置为"unknown",需要使用android.os.Build.getSerial()方法来获取:
public static getSerial();

存在的问题

  1. Android O 中,android.os.Build.SERIAL标记为@deprecated,使用android.os.Build.getSerial()需要android.permission.READ_PHONE_STATE权限

Installation ID / GUID

Installation IDAndroid Developers Blog提供的方案,原理是应用自己生成一个UUID字符串存储在本地缓存里来作为标识。

获取

public class Installation {
    private static String sID = null;
    private static final String INSTALLATION = "INSTALLATION";

    public synchronized static String id(Context context) {
        if (sID == null) {  
            File installation = new File(context.getFilesDir(), INSTALLATION);
            try {
                if (!installation.exists())
                    writeInstallationFile(installation);
                sID = readInstallationFile(installation);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return sID;
    }

    private static String readInstallationFile(File installation) throws IOException {
        RandomAccessFile f = new RandomAccessFile(installation, "r");
        byte[] bytes = new byte[(int) f.length()];
        f.readFully(bytes);
        f.close();
        return new String(bytes);
    }

    private static void writeInstallationFile(File installation) throws IOException {
        FileOutputStream out = new FileOutputStream(installation);
        String id = UUID.randomUUID().toString();
        out.write(id.getBytes());
        out.close();
    }
}

存在的问题

应用卸载重装,或者清理缓存,会清除掉这个标识,下次只能重新生成一个新的UUID字符串标识

参看

没有评论:

发表评论

Android logcat

Log等级 Android log 等级在 android/log.h 中定义如下: typedef   enum   android_LogPriority {    /** For internal use only. */ ANDROID_LOG_UNKNOWN =...