有時需要對使用者裝置進行辨別,是以希望能夠得到一個穩定可靠并且唯一的識别碼。雖然android系統中提供了這樣裝置識别碼,但是由于android系統版本、廠商定制系統中的bug等限制,穩定性和唯一性并不理想。而通過其他硬體資訊辨別也因為系統版本、手機硬體等限制存在不同程度的問題。
下面收集了一些“有能力”或“有一定能力”作為裝置辨別的串碼。
這是android系統為開發者提供的用于辨別手機裝置的串号,也是各種方法中普适性較高的,可以說幾乎所有的裝置都可以傳回這個串号,并且唯一性良好。
這個device_id可以同通過下面的方法擷取:
它會根據不同的手機裝置傳回imei,meid或者esn碼,但在使用的過程中有以下問題:
非手機裝置:最開始搭載android系統都手機裝置,而現在也出現了非手機裝置:如平闆電腦、電子書、電視、音樂播放器等。這些裝置沒有通話的硬體功能,系統中也就沒有telephony_service,自然也就無法通過上面的方法獲得device_id。
權限問題:擷取device_id需要read_phone_state權限,如果隻是為了擷取device_id而沒有用到其他的通話功能,申請這個權限一來大才小用,二來部分使用者會懷疑軟體的安全性。
廠商定制系統中的bug:少數手機裝置上,由于該實作有漏洞,會傳回垃圾,如:zeros或者asterisks
可以使用手機wifi或藍牙的mac位址作為裝置辨別,但是并不推薦這麼做,原因有以下兩點:
硬體限制:并不是所有的裝置都有wifi和藍牙硬體,硬體不存在自然也就得不到這一資訊。
擷取的限制:如果wifi沒有打開過,是無法擷取其mac位址的;而藍牙是隻有在打開的時候才能擷取到其mac位址。
擷取wifi mac位址:
擷取藍牙 mac位址:
裝有sim卡的裝置,可以通過下面的方法擷取到sim serial number:
注意:對于cdma裝置,傳回的是一個空值!
在裝置首次啟動時,系統會随機生成一個64位的數字,并把這個數字以16進制字元串的形式儲存下來,這個16進制的字元串就是android_id,當裝置被wipe後該值會被重置。可以通過下面的方法擷取:
android_id可以作為裝置辨別,但需要注意:
廠商定制系統的bug:不同的裝置可能會産生相同的android_id:9774d56d682e549c。
廠商定制系統的bug:有些裝置傳回的值為null。
裝置差異:對于cdma裝置,android_id和telephonymanager.getdeviceid() 傳回相同的值。
android系統2.3版本以上可以通過下面的方法得到serial number,且非手機裝置也可以通過該接口擷取。
以上幾種方式都或多或少存在一定的局限性或者bug,如果并不是确實需要對硬體本身進行綁定,使用自己生成的uuid也是一個不錯的選擇,因為該方法無需通路裝置的資源,也跟裝置類型無關。
這種方式的原理是在程式安裝後第一次運作時生成一個id,該方式和裝置唯一辨別不一樣,不同的應用程式會産生不同的id,同一個程式重新安裝也會不同。是以這不是裝置的唯一id,但是可以保證每個使用者的id是不同的。可以說是用來辨別每一份應用程式的唯一id(即installtion id),可以用來跟蹤應用的安裝數量等。
google developer blog提供了這樣的一個架構:
上文可以看出,android系統中并沒有可以可靠擷取所有廠商裝置唯一id的方法,各個方法都有自己的使用範圍和局限性,這也是目前流行的android系統版本過多,裝置也是來自不同廠商,且沒有統一标準等原因造成的。
從目前發展來看,android系統多版本共存還會持續較長的時間,而android系統也不會被某個裝置生産廠商壟斷,長遠看android基礎系統将會趨于穩定,裝置辨別也将會作為系統基礎部分而标準化,屆時這一問題才有望徹底解決。
目前的解決辦法,比較可行的是一一适配,在保證大多數裝置友善的前提下,如果擷取不到,使用其他備選資訊作為辨別,即自己再封裝一個裝置id出來,通過内部算法保證盡量和裝置硬體資訊相關,以及辨別的唯一性。
1 cpu号:
檔案在: /proc/cpuinfo
通過adb shell 檢視:
adb shell cat /proc/cpuinfo
2 mac 位址
檔案路徑 /sys/class/net/wlan0/address
adb shell cat /sys/class/net/wlan0/address
xx:xx:xx:xx:xx:aa
這樣可以擷取兩者的序列号,
方法确定,剩下的就是寫代碼了
以mac位址為例:
string getmac() {
string macserial = null;
string str = "";
try {
process pp = runtime.getruntime().exec(
"cat /sys/class/net/wlan0/address ");
inputstreamreader ir = new inputstreamreader(pp.getinputstream());
linenumberreader input = new linenumberreader(ir);
for (; null != str;) {
str = input.readline();
if (str != null) {
macserial = str.trim();// 去空格
break;
}
}
} catch (ioexception ex) {
// 賦予預設值
ex.printstacktrace();
}
return macserial;
}
唯一辨別碼這東西在網絡應用中非常有用,例如檢測是否重複注冊之類的。
import android.provider.settings.secure;
private string android_id = secure.getstring(getcontext().getcontentresolver(), secure.android_id);
我們在項目過程中或多或少會使用到裝置的唯一識别碼,我們希望能夠得到一個穩定、可靠的裝置唯一識别碼。今天我們将介紹幾種方式。
1. device_id
假設我們确實需要用到真實裝置的辨別,可能就需要用到device_id。在以前,我們的android裝置是手機,這個device_id可以同通過telephonymanager.getdeviceid()擷取,它根據不同的手機裝置傳回imei,meid或者esn碼,但它在使用的過程中會遇到很多問題:
非手機裝置: 如果隻帶有wifi的裝置或者音樂播放器沒有通話的硬體功能的話就沒有這個device_id
權限: 擷取device_id需要read_phone_state權限,但如果我們隻為了擷取它,沒有用到其他的通話功能,那這個權限有點大才小用
bug:在少數的一些手機裝置上,該實作有漏洞,會傳回垃圾,如:zeros或者asterisks的産品
2. mac address
我們也可以通過手機的wifi或者藍牙裝置擷取mac address作為device id,但是并不建議這麼做,因為并不是所有的裝置都有wifi,并且,如果wifi沒有打開,那硬體裝置無法傳回mac address.
3. serial number
在android 2.3可以通過android.os.build.serial擷取,非手機裝置可以通過該接口擷取。
4. android_id
android_id是裝置第一次啟動時産生和存儲的64bit的一個數,當裝置被wipe後該數重置
android_id似乎是擷取device id的一個好選擇,但它也有缺陷:
它在android <=2.1 or android >=2.3的版本是可靠、穩定的,但在2.2的版本并不是100%可靠的
在主流廠商生産的裝置上,有一個很經常的bug,就是每個裝置都會産生相同的android_id:9774d56d682e549c
5. installtion id : uuid
以上四種方式都有或多或少存在的一定的局限性或者bug,在這裡,有另外一種方式解決,就是使用uuid,該方法無需通路裝置的資源,也跟裝置類型無關。
這種方式是通過在程式安裝後第一次運作後生成一個id實作的,但該方式跟裝置唯一辨別不一樣,它會因為不同的應用程式而産生不同的id,而不是裝置唯一id。是以經常用來辨別在某個應用中的唯一id(即installtion id),或者跟蹤應用的安裝數量。很幸運的,google developer blog提供了這樣的一個架構:
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_id為基礎,在擷取失敗時以telephonymanager.getdeviceid()為備選方法,如果再失敗,使用uuid的生成政策。
重申下,以下方法是生成device id,在大多數情況下installtion id能夠滿足我們的需求,但是如果确實需要用到device id,那可以通過以下方式實作:
import android.content.context;
import android.content.sharedpreferences;
import android.telephony.telephonymanager;
import java.io.unsupportedencodingexception;
import java.util.uuid;
public class deviceuuidfactory {
protected static final string prefs_file = "device_id.xml";
protected static final string prefs_device_id = "device_id";
protected static uuid uuid;
public deviceuuidfactory(context context) {
if( uuid ==null ) {
synchronized (deviceuuidfactory.class) {
if( uuid == null) {
final sharedpreferences prefs = context.getsharedpreferences( prefs_file, 0);
final string id = prefs.getstring(prefs_device_id, null );
if (id != null) {
// use the ids previously computed and stored in the prefs file
uuid = uuid.fromstring(id);
} else {
final string androidid = secure.getstring(context.getcontentresolver(), secure.android_id);
// use the android id unless it's broken, in which case fallback on deviceid,
// unless it's not available, then fallback on a random number which we store
// to a prefs file
try {
if (!"9774d56d682e549c".equals(androidid)) {
uuid = uuid.nameuuidfrombytes(androidid.getbytes("utf8"));
} else {
final string deviceid = ((telephonymanager) context.getsystemservice( context.telephony_service )).getdeviceid();
uuid = deviceid!=null ? uuid.nameuuidfrombytes(deviceid.getbytes("utf8")) : uuid.randomuuid();
}
} catch (unsupportedencodingexception e) {
throw new runtimeexception(e);
// write the value out to the prefs file
prefs.edit().putstring(prefs_device_id, uuid.tostring() ).commit();
}
/**
* returns a unique uuid for the current android device. as with all uuids, this unique id is "very highly likely"
* to be unique across all android devices. much more so than android_id is.
*
* the uuid is generated by using android_id as the base key if appropriate, falling back on
* telephonymanager.getdeviceid() if android_id is known to be incorrect, and finally falling back
* on a random uuid that's persisted to sharedpreferences if getdeviceid() does not return a
* usable value.
* in some rare circumstances, this id may change. in particular, if the device is factory reset a new device id
* may be generated. in addition, if a user upgrades their phone from certain buggy implementations of android 2.2
* to a newer, non-buggy version of android, the device id may change. or, if a user uninstalls your app on
* a device that has neither a proper android id nor a device id, this id may change on reinstallation.
* note that if the code falls back on using telephonymanager.getdeviceid(), the resulting id will not
* change after a factory reset. something to be aware of.
* works around a bug in android 2.2 for many devices when using android_id directly.
* @return a uuid that may be used to uniquely identify your device for most purposes.
*/
public uuid getdeviceuuid() {
return uuid;
如何擷取android手機的唯一辨別?
代碼: 這裡是你在android裡讀出 唯一的 imsi-id / imei-id 的方法。
java:
string myimsi = android.os.systemproperties.get(android.telephony.telephonyproperties.property_imsi);
// within my emulator it returns: 310995000000000
string myimei = android.os.systemproperties.get(android.telephony.telephonyproperties.property_imei);
// within my emulator it returns: 000000000000000
注:android.os.systemproperties的标簽被打上@hide了,是以sdk中并不會存在。如果需要使用,需要有android的source code支援。