天天看點

android擷取裝置唯一标示

有時需要對使用者裝置進行辨別,是以希望能夠得到一個穩定可靠并且唯一的識别碼。雖然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支援。