很多情况下,需要将APP和设备进行绑定,以保证用户不会无限制的注册或发送请求。然而,Android设备并没有绝对的唯一标识。
Android提供了多种方法来获取所谓的唯一标识。常用的有六种。
确实有一个叫做Android ID的东西,但通常被认为不可信,因为它有时会为NULL。而且,在ROOT后,该值可以被修改。
String sAndroidID= Secure.getString(getContentResolver(), Secure.ANDROID_ID);
首先,需要说明的是,获取该值需要添加权限。
android.permission.ACCESS_WIFI_STATE
其次,需要指出的是,获取该值无须打开WIFI。
最后,需要强调的是,该值不是真实地址,可以被伪造。
由于android系统对获取方法进行了升级。
android 6.0之前的版本,获取方法如下:
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
String m_szWLANMAC = wm.getConnectionInfo().getMacAddress();
若无法获取该值,或值为02:00:00:00:00:00,则说明系统为Android 6.0或以上,需要使用以下方法获取。
public static String getMacAddr() {
try {
List all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (!nif.getName().equalsIgnoreCase("wlan0")) continue;
byte[] macBytes = nif.getHardwareAddress();
if (macBytes == null) {
return "";
}
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02X:",b));
}
if (res1.length() > 0) {
res1.deleteCharAt(res1.length() - 1);
}
return res1.toString();
}
} catch (Exception ex) {
}
return "02:00:00:00:00:00";
}
注:该方法也无须打开WIFI。
与WLAN MAC类似,获取该值需要添加权限。
android.permission.BLUETOOTH
同样,获取方法也以Android 6.0作为分界点。
Android 6.0以下,使用如下方法获取
BluetoothAdapter m_BluetoothAdapter = null; // Local Bluetooth adapter
m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String m_szBTMAC = m_BluetoothAdapter.getAddress();
Android 6.0及以上,获取方法可参见Stackoverflow,该方法需要到入mirror包。(为取个唯一标识,至于吗?这里不再详述)
同样, 蓝牙没有必要打开,也能读取该值。
注:此前,某些帖子说添加ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION 权限后,可以获取蓝牙或WIFI的MAC,也是瞎扯。仅添加两项权限,不能获取MAC值。若不添加两项权限,也可以获取MAC值。因此,该两项不是必要条件。
IMEI,即International Mobile Equipment Identity,是国际移动设备身份码的缩写,国际移动装备辨识码,是由15位数字组成的”电子串号”。
获取该值,需要添加权限
android.permission.READ_PHONE_STATE
获取方法如下:
TelephonyManager TelephonyMgr = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
String szImei = TelephonyMgr.getDeviceId();
该值与SIM卡有关,但不是所有Android设备都有SIM卡。
且需要考虑双卡双待情况。
方法2至方法4,都尝试以某种硬件的信息作为Android的唯一标识,但无法保证该值得唯一性和有效性。很自然可以想到,使用多硬件信息组合作为唯一标识。例如取出ROM版本、CPU型号、以及其他硬件信息进行组合。获取这些信息,需要用到Build类。大多数的Build成员都是字符串形式的,但网上流传比较广的使用方法,是使用各成员的长度,进行字符串组合。
Build.BOARD.length()%10 +
Build.BRAND.length()%10 +
Build.CPU_ABI.length()%10 +
Build.DEVICE.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 +
Build.USER.length()%10 ;
当然,我们也可以只使用其中某几个成员,获取其字符串值进行组合,再辅助于某种运算(SHA, MD5,HASH等)来生成自定义的唯一标识。
还有一种方法,使用了Android UUID类,实现了APP与设备的绑定。
在通过APP第一次获取设备唯一标识时,创建文件,并使用UUID类向文件中写入一个随机字符串,作为唯一标识。下次获取时,若文件存在,则直接读取随机字符串。当卸载APP时,删除该文件。因此,一次安装APP,对应一个唯一标识。
public synchronized static String getUUID(Context context, String fileName) {
if (sID == null) {
File UUIDFile= new File(context.getFilesDir(), fileName);
try {
if (!UUIDFile.exists())
writeInstallationFile(UUIDFile);
sID = readInstallationFile(UUIDFile);
} catch (Exception e){
throw new RuntimeException(e);
}
}
return sID;
}
private static String readInstallationFile(File file) throws IOException {
RandomAccessFile f = new RandomAccessFile(file, "r");
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}
private static void writeInstallationFile(File file) throws IOException {
FileOutputStream out = new FileOutputStream(file);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
APP UUID值例如:
7a2c6658-1d61-42f5-99b1-c7d309c60d50
综上,设备的唯一标识,不是绝对的。我们可以选择一种或多种进行组合,最大可能的实现其唯一性。