最近在作項目的過程當中須要惟一標識用戶的設備,後臺在作push notification的時候須要用到這個惟一的標識號。java
首先我會想到的是設備的device id,毫無疑問能夠惟一標識設備,第一個版本也正是這樣作的。國慶期間用戶的一封郵件讓哥很不淡定,由於須要拿到device id,因此必然要在AndroidManifest文件中添加權限android
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
添加完這條權限很天然的在用戶下載App的時候會提示如下權限接受的列表:安全
OK。問題來了,用戶以爲這個東西比較敏感,我選擇不安裝大家的這個App,心中一萬隻羊駝在奔跑。dom
想一想換換其餘方式來實現這一需求吧。今天和你們總結分享下每種方式的利弊。工具
第一種方式:設備的Device Id做爲惟一標識學習
在AndroidManifest配置文件中添加權限ui
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
具體獲取的方法以下,我是寫在工具類中的:spa
public static String getDeviceIdInfo(Context mContext) { //String imei = ((TelephonyManager) mContext.getSystemService(mContext.TELEPHONY_SERVICE)).getDeviceId(); String imei = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID); return imei; }
這種實現方式的缺點在於:code
1.遇到安全警覺性比較高的用戶,我不接受這樣的權限,不安裝你的App。blog
2.非手機設備,若是隻帶有Wifi的設備或者音樂播放器沒有通話的硬件功能的話就沒有這個DEVICE_ID。
3.做爲手機來說,IMEI是惟一的,它應該相似於359881030314356(除非你有一個沒有量產的手機(水貨)它可能有無效的IMEI,如:0000000000000)。說白了,若是隻爲了獲取它,沒有用到其餘的通話功能,那這個權限有點大才小用。
4.在少數的一些手機設備上,該實現有漏洞,會返回垃圾,如:zeros或者asterisks的產品。
第二種方式:獲取MAC ADDRESS
咱們也能夠經過手機的Wifi或者藍牙設備獲取MAC ADDRESS做爲DEVICE ID,可是並不建議這麼作,由於並非全部的設備都有Wifi,而且,若是Wifi沒有打開,那硬件設備沒法返回MAC ADDRESS.
The WLAN MAC Address string, 是另外一個惟一ID。毫無疑問你須要在AndroidManifest文件中添加以下權限:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
代碼中的實現:
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
String m_szWLANMAC = wm.getConnectionInfo().getMacAddress();
Returns: 00:11:22:33:44:55 (這不是一個真實的地址。並且這個地址能輕易地被僞造。).WLan沒必要打開,就可讀取些值。
第三種方式:BT MAC ADDRESS
只在有藍牙的設備上運行。而且要加入android.permission.BLUETOOTH 權限.
BluetoothAdapter m_BluetoothAdapter = null; // Local Bluetooth adapter
m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String m_szBTMAC = m_BluetoothAdapter.getAddress();
Returns: 43:25:78:50:93:38 . 藍牙沒有必要打開,也能讀取。
第四種方式:Serial Number
裝有SIM卡的設備能夠經過getSystemService(Context.TELEPHONY_SERVIEC).getSimSerialNumber();獲取到
sim serial number。 注意對CDMA設備,返回的是一個空值。
在Android 2.3能夠經過android.os.Build.SERIAL獲取,非手機設備能夠經過該接口獲取。
第五種方式:ANDROID_ID
ANDROID_ID是設備第一次啓動時產生和存儲的64bit的一個數,當設備被wipe後該數重置
ANDROID_ID彷佛是獲取Device ID的一個好選擇,但它也有缺陷:
它在Android <=2.1 or Android >=2.3的版本是可靠、穩定的,但在2.2的版本並非100%可靠的
在主流廠商生產的設備上,有一個很常常的bug,就是每一個設備都會產生相同的ANDROID_ID:9774d56d682e549c
具體獲取的方法:
String m_szAndroidID = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
第六種方式:Installtion ID : UUID
這種方式也正是我最後採用的一種方式。
以上幾種方式都有或多或少存在的必定的侷限性或者bug,在這裏,有另一種方式解決,就是使用UUID,該方法無需訪問設備的資源,也跟設備類型無關。
這種方式是經過在程序安裝後第一次運行後生成一個ID實現的,但該方式跟設備惟一標識不同,它會由於不一樣的應用程序而產生不一樣的ID,而不是設備惟一ID。
所以常常用來標識在某個應用中的惟一ID(即Installtion ID),或者跟蹤應用的安裝數量。
我在程序中新建了這麼一個工具類來獲取這個UUID
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.UUID; import android.content.Context; 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(); } }
綜上所述,我仍是比較推薦最後一種方式。若是還有別的實現方式你們能夠留言一塊兒學習討論。