1. 程式人生 > >android獲取裝置唯一標識完美解決方案的思考以及實現方式

android獲取裝置唯一標識完美解決方案的思考以及實現方式

前言

由於在開發中需要開發遊客模式,在使用者沒有登入的情況下必須確保裝置的唯一性,於是慣性思維想到的肯定是使用DevicesId 來作為裝置的唯一標識,用以代替使用者登入以後的唯一識別符號。

但是由於國內複雜的rom定製情況,以及使用者許可權禁止的情況。DevicesId 在使用中並不能百分百的貨到到。所以本篇文章就是描述一下,我在開發中如何處理裝置唯一識別符號的。

一、一些常用的獲取裝置唯一識別符號的方法

  • IMEI
  • Mac 地址
  • ANDROID_ID
  • Serial Number, SN(裝置序列號)
  • UniquePsuedoID

1.1 關於IMEI

IMEI 國際移動裝置身份碼 目前GSM/WCDMA/LTE手機終端需要使用IMEI號碼,在單卡工程中一個手機號對應一個IMEI號,雙卡手機則會對應兩個IMEI號,一張是手機卡對應一個。

1.1.1 關於獲取IMEI過程

需要的許可權

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

獲取IMEI 呼叫的方法

TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(context.TELEPHONY_SERVICE);  
String imei = telephonyManager.getDeviceId();
1.1.2 使用IMEI 存在的弊端

由以上可以看出使用IMEI來作為Android的裝置唯一識別符號存在一定的弊端, 如果使用者禁用掉相關許可權,那麼對於以上獲取引數的程式碼。則會直接報錯,不會得到我們想要的內容。

1.2Mac地址

Mac 指的就是我們裝置網絡卡的唯一設別碼,該碼全球唯一,一般稱為實體地址,硬體地址用來定義裝置的位置

1.2.1 獲取裝置的Mac地址

需要的許可權

<!--訪問WIFI的許可權-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> 

獲取mac地址的方法

獲取mac地址有一點需要注意的就是android 6.0版本後,以下注釋方法不再適用,不管任何手機都會返回”02:00:00:00:00:00”這個預設的mac地址,這是googel官方為了加強許可權管理而禁用了getSYstemService(Context.WIFI_SERVICE)方法來獲得mac地址。

        /*
        獲取mac地址有一點需要注意的就是android 6.0版本後,以下注釋方法不再適用,
 不管任何手機都會返回"02:00:00:00:00:00"這個預設的mac地址,
 這是googel官方為了加強許可權管理而禁用了getSYstemService(Context.WIFI_SERVICE)方法來獲得mac地址。
         */
        // String macAddress= "";
        // WifiManager wifiManager = (WifiManager) MyApp.getContext().getSystemService(Context.WIFI_SERVICE);
        // WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        // macAddress = wifiInfo.getMacAddress();
        // return macAddress;

        String macAddress = null;
        StringBuffer buf = new StringBuffer();
        NetworkInterface networkInterface = null;
        try {
            networkInterface = NetworkInterface.getByName("eth1");
            if (networkInterface == null) {
                networkInterface = NetworkInterface.getByName("wlan0");
            }
            if (networkInterface == null) {
                return "02:00:00:00:00:02";
            }
            byte[] addr = networkInterface.getHardwareAddress();
            for (byte b : addr) {
                buf.append(String.format("%02X:", b));
            }
            if (buf.length() > 0) {
                buf.deleteCharAt(buf.length() - 1);
            }
            macAddress = buf.toString();
        } catch (SocketException e) {
            e.printStackTrace();
            return "02:00:00:00:00:02";
        }
        return macAddress;
    }
1.2.2 使用Mac地址存在的弊端
  1. 如果使用Mac地址最重要的一點就是手機必須具有上網功能,
  2. 在Android6.0以後 google 為了執行時許可權對geMacAddress();作出修改通過該方法得到的mac地址永遠是一樣的, 但是可以其他途徑獲取。

1.3關於ANDROID_ID

在裝置首次執行的時候,系統會隨機生成一64位的數字,並把這個數值以16進位制儲存下來,這個16進位制的數字就是ANDROID_ID,但是如果手機恢復出廠設定這個值會發生改變。

1.3.1獲取ANDROID_ID
String ANDROID_ID = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID); 
1.3.2使用ANDROID_ID存在的弊端
  1. 手機恢復出廠設定以後該值會發生變化
  2. 在國內Android定製的大環境下,有些裝置是不會返回ANDROID_ID的

1.4 Serial Number, SN(裝置序列號)

1.4.1 獲取序列號

java
String SerialNumber = android.os.Build.SERIAL;

獲取序列號不需要許可權,但是有一定的侷限性,在有些手機上會出現垃圾資料,比如紅米手機返回的就是連續的非隨機數

1.5 UniquePsuedoID

具體稱呼不明, 但是從以下的情況看出是一些列硬體資訊拼裝獲取到的內容。

1.5.1 具體的獲取方式
public static String getUniquePsuedoID()  
{ 
    String m_szDevIDShort = "35" + (Build.BOARD.length() % 10) + (Build.BRAND.length() % 10) + (Build.CPU_ABI.length() % 10) + (Build.DEVICE.length() % 10) + (Build.MANUFACTURER.length() % 10) + (Build.MODEL.length() % 10) + (Build.PRODUCT.length() % 10);  

    // Thanks to @Roman SL!  
    // http://stackoverflow.com/a/4789483/950427  
    // Only devices with API >= 9 have android.os.Build.SERIAL  
    // http://developer.android.com/reference/android/os/Build.html#SERIAL  
    // If a user upgrades software or roots their phone, there will be a duplicate entry  
    String serial = null;  
    try  
    {  
        serial = android.os.Build.class.getField("SERIAL").get(null).toString();  

        // Go ahead and return the serial for api => 9  
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();  
    }  
    catch (Exception e)  
    {  
        // String needs to be initialized  
        serial = "serial"; // some value  
    }  

    // Thanks @Joe!  
    // http://stackoverflow.com/a/2853253/950427  
    // Finally, combine the values we have found by using the UUID class to create a unique identifier  
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();  
}  
1.5.2 關於使用UniquePsuedoID的弊端
  1. 由於是與裝置資訊直接相關,如果是同一批次出廠的的裝置有可能出現生成的內容可能是一樣的。(通過模擬器實驗過,開啟兩個完全一樣的模擬器,生成的內容是完全一下),所以如果單獨使用該方法也是不能用於生成唯一識別符號的。

1.6 以上方法比較

比較以上5種方法。如果只是考慮單獨使用,那麼在不同程度上在使用者使用的情況下都會出現無法生成或者生成無效裝置唯一識別符號的情況。所以我在開發中採用混合使用的方式,同時結合SD卡 以及 sharepreference進行本地持久化處理。

二、我所使用的獲取裝置唯一標識實現方式

2.1 實現簡要描述

在開發中通過結合 device_id 、 MacAddress 以及 隨機生成的 UUID 進行生成裝置唯一識別符號(優先順序依次由高到低)。 然後通過把生成的唯一識別符號寫到SD卡中一個隱藏目錄中(為什麼是寫到影藏檔案中呢,主要是避避免使用者看見然後手動刪除。)。 同時會在相應App sharepreference 進行儲存一份,該內容只要內容只會在App 第一次啟動或者App清除資料以後會進行重寫操作,在其他時候不會進行寫操作,只會進行讀取操作。

2.2 如何生成唯一識別符號

2.2.1 先上程式碼
package com.wdsunday.utils;

import android.content.Context;
import android.os.Environment;
import android.telephony.TelephonyManager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.MessageDigest;
import java.util.UUID;

/**
 * @author liangjun on 2018/1/21.
 */

public class GetDeviceId {

    //儲存檔案的路徑
    private static final String CACHE_IMAGE_DIR = "aray/cache/devices";
    //儲存的檔案 採用隱藏檔案的形式進行儲存
    private static final String DEVICES_FILE_NAME = ".DEVICES";

    /**
     * 獲取裝置唯一識別符號
     *
     * @param context
     * @return
     */
    public static String getDeviceId(Context context) {
        //讀取儲存的在sd卡中的唯一識別符號
        String deviceId = readDeviceID(context);
        //用於生成最終的唯一識別符號
        StringBuffer s = new StringBuffer();
        //判斷是否已經生成過,
        if (deviceId != null && !"".equals(deviceId)) {
            return deviceId;
        }
        try {
            //獲取IMES(也就是常說的DeviceId)
            deviceId = getIMIEStatus(context);
            s.append(deviceId);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            //獲取裝置的MACAddress地址 去掉中間相隔的冒號
            deviceId = getLocalMac(context).replace(":", "");
            s.append(deviceId);
        } catch (Exception e) {
            e.printStackTrace();
        }
//        }

        //如果以上搜沒有獲取相應的則自己生成相應的UUID作為相應裝置唯一識別符號
        if (s == null || s.length() <= 0) {
            UUID uuid = UUID.randomUUID();
            deviceId = uuid.toString().replace("-", "");
            s.append(deviceId);
        }
        //為了統一格式對裝置的唯一標識進行md5加密 最終生成32位字串
        String md5 = getMD5(s.toString(), false);
        if (s.length() > 0) {
            //持久化操作, 進行儲存到SD卡中
            saveDeviceID(md5, context);
        }
        return md5;
    }


    /**
     * 讀取固定的檔案中的內容,這裡就是讀取sd卡中儲存的裝置唯一識別符號
     *
     * @param context
     * @return
     */
    public static String readDeviceID(Context context) {
        File file = getDevicesDir(context);
        StringBuffer buffer = new StringBuffer();
        try {
            FileInputStream fis = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
            Reader in = new BufferedReader(isr);
            int i;
            while ((i = in.read()) > -1) {
                buffer.append((char) i);
            }
            in.close();
            return buffer.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取裝置的DeviceId(IMES) 這裡需要相應的許可權<br/>
     * 需要 READ_PHONE_STATE 許可權
     *
     * @param context
     * @return
     */
    private static String getIMIEStatus(Context context) {
        TelephonyManager tm = (TelephonyManager) context
                .getSystemService(Context.TELEPHONY_SERVICE);
        String deviceId = tm.getDeviceId();
        return deviceId;
    }


    /**
     * 獲取裝置MAC 地址 由於 6.0 以後 WifiManager 得到的 MacAddress得到都是 相同的沒有意義的內容
     * 所以採用以下方法獲取Mac地址
     * @param context
     * @return
     */
    private static String getLocalMac(Context context) {
//        WifiManager wifi = (WifiManager) context
//                .getSystemService(Context.WIFI_SERVICE);
//        WifiInfo info = wifi.getConnectionInfo();
//        return info.getMacAddress();


        String macAddress = null;
        StringBuffer buf = new StringBuffer();
        NetworkInterface networkInterface = null;
        try {
            networkInterface = NetworkInterface.getByName("eth1");
            if (networkInterface == null) {
                networkInterface = NetworkInterface.getByName("wlan0");
            }
            if (networkInterface == null) {
                return "";
            }
            byte[] addr = networkInterface.getHardwareAddress();


            for (byte b : addr) {
                buf.append(String.format("%02X:", b));
            }
            if (buf.length() > 0) {
                buf.deleteCharAt(buf.length() - 1);
            }
            macAddress = buf.toString();
        } catch (SocketException e) {
            e.printStackTrace();
            return "";
        }
        return macAddress;


    }

    /**
     * 儲存 內容到 SD卡中,  這裡儲存的就是 裝置唯一識別符號
     * @param str
     * @param context
     */
    public static void saveDeviceID(String str, Context context) {
        File file = getDevicesDir(context);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            Writer out = new OutputStreamWriter(fos, "UTF-8");
            out.write(str);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 對挺特定的 內容進行 md5 加密
     * @param message  加密明文
     * @param upperCase  加密以後的字串是是大寫還是小寫  true 大寫  false 小寫
     * @return
     */
    public static String getMD5(String message, boolean upperCase) {
        String md5str = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");

            byte[] input = message.getBytes();

            byte[] buff = md.digest(input);

            md5str = bytesToHex(buff, upperCase);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return md5str;
    }


    public static String bytesToHex(byte[] bytes, boolean upperCase) {
        StringBuffer md5str = new StringBuffer();
        int digital;
        for (int i = 0; i < bytes.length; i++) {
            digital = bytes[i];

            if (digital < 0) {
                digital += 256;
            }
            if (digital < 16) {
                md5str.append("0");
            }
            md5str.append(Integer.toHexString(digital));
        }
        if (upperCase) {
            return md5str.toString().toUpperCase();
        }
        return md5str.toString().toLowerCase();
    }

    /**
     * 統一處理裝置唯一標識 儲存的檔案的地址
     * @param context
     * @return
     */
    private static File getDevicesDir(Context context) {
        File mCropFile = null;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File cropdir = new File(Environment.getExternalStorageDirectory(), CACHE_IMAGE_DIR);
            if (!cropdir.exists()) {
                cropdir.mkdirs();
            }
            mCropFile = new File(cropdir, DEVICES_FILE_NAME); // 用當前時間給取得的圖片命名
        } else {
            File cropdir = new File(context.getFilesDir(), CACHE_IMAGE_DIR);
            if (!cropdir.exists()) {
                cropdir.mkdirs();
            }
            mCropFile = new File(cropdir, DEVICES_FILE_NAME);
        }
        return mCropFile;
    }
}

以上程式碼就是生成 裝置唯一標識的具體實現方式。同時包括讀取裝置唯一標識的方法 。 關鍵點的說明已經在註釋中進行描述。這裡不再重複講述。

2.2.2具體的使用

在app 的啟動頁中增加以下程式碼

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //獲取儲存在sd中的 裝置唯一識別符號
                    String readDeviceID = Utils.readDeviceID(WelcomeActivity.this);
                    //獲取快取在  sharepreference 裡面的 裝置唯一標識
                    String string = SimplePreference.getPreference(WelcomeActivity.this).getString(SpConstant.SP_DEVICES_ID, readDeviceID);
                    //判斷 app 內部是否已經快取,  若已經快取則使用app 快取的 裝置id
                    if (string != null) {
                        //app 快取的和SD卡中儲存的不相同 以app 儲存的為準, 同時更新SD卡中儲存的 唯一識別符號
                        if (StringUtil.isBlank(readDeviceID) && !string.equals(readDeviceID)) {
                            // 取有效地 app快取 進行更新操作
                            if (StringUtil.isBlank(readDeviceID) && !StringUtil.isBlank(string)) {
                                readDeviceID = string;
                                Utils.saveDeviceID(readDeviceID, WelcomeActivity.this);
                            }
                        }
                    }
                    // app 沒有快取 (這種情況只會發生在第一次啟動的時候)
                    if (StringUtil.isBlank(readDeviceID)) {
                        //儲存裝置id
                        readDeviceID = Utils.getDeviceId(WelcomeActivity.this);
                    }
                    //左後再次更新app 的快取 
                    SimplePreference.getPreference(WelcomeActivity.this).saveString(SpConstant.SP_DEVICES_ID, readDeviceID);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

以上程式碼只是生成裝置唯一識別符號,同時保證app 在一次啟動以後能夠保持使用是統一個裝置唯一識別符號。

2.2.3 注意

在 app 使用的時候只取 sharepreference 儲存的內容,不要取sd 卡中儲存的內容

2.3 使用總結

以上方法只是能夠最大限度的保持app 能夠使用同一個裝置唯一識別符號。 在特殊情況下裝置唯一識別符號還是會發生變化的。 例如 使用者沒有給SD卡的讀取許可權, 那麼app 在清楚資料以後再次生成的裝置唯一識別符號是有課能會發生變化的。

三、寫在最後

以上是如何最大限度生成一個唯一識別符號的思考,如有不正確之處請忽略,

相關推薦

android獲取裝置唯一標識完美解決方案

這是Android系統為開發者提供的用於標識手機裝置的串號,也是各種方法中普適性較高的,可以說幾乎所有的裝置都可以返回這個串號,並且唯一性良好。 這個DEVICE_ID可以同通過下面的方法獲取: TelephonyManager tm = (TelephonyManager)getSystemServi

android獲取裝置唯一標識完美解決方案思考以及實現方式

前言 由於在開發中需要開發遊客模式,在使用者沒有登入的情況下必須確保裝置的唯一性,於是慣性思維想到的肯定是使用DevicesId 來作為裝置的唯一標識,用以代替使用者登入以後的唯一識別符號。 但是由於國內複雜的rom定製情況,以及使用者許可權禁止的情況。De

Android獲取裝置唯一標識解決方案

關於Android裝置唯一識別符號號 前言 由於在開發中需要開發遊客模式,在使用者沒有登入的情況下必須確保裝置的唯一性,於是慣性思維想到的肯定是使用DevicesId 來作為裝置的唯一標識,用以代替使用者登入以後的唯一識別符號。 但是由於國內複雜的rom定製情況,以及使

安卓獲取渠道名渠道id Android獲取裝置唯一標識的終極解決方法,防止安卓7.0時崩潰問題

一,先說獲取渠道名(這裡以友盟為例) /* * 4.5.1新加渠道名欄位,用來傳給後臺去統計各個渠道下載量 * */ public static String getSource() { //獲取渠道名 Strin

android獲取裝置唯一標識

IMEI 百度百科解釋:IMEI(International Mobile Equipment Identity)是國際移動裝置身份碼的縮寫,國際移動裝備辨識碼,是由15位數字組成的“電子串號”,它與每臺行動電話機一一對應,而且該碼是全世界唯一的。每一隻行動電話機在組裝完成後都將被賦予一個全

Android獲取裝置唯一標識

話不多說,先貼程式碼 /**獲取裝置唯一標識碼
 * @param context 上下文
 * @return 裝置唯一標識碼
 */
 public static String getUDID

Android獲取裝置唯一標識(UDID, DeviceID)

android下獲取裝置唯一標識原本非常簡單(至少不會像iOS一樣禁用這個,禁用那個),但是由於裝置的多樣性需要考慮的東西也對應複雜起來。 先附上完整程式碼protected static final String PREFS_FILE = "gank_device_id.

Android 沉浸式狀態列完美解決方案

現在搜尋Android 沉浸式狀態列,真的是一堆一堆,寫的特別多,但是真正用的舒服的真沒有,在這裡自己整理一下開發記錄   注意,在使用這個步驟過程之前,請把之前設定的程式碼註釋一下 把佈局帶有android:fitsSystemWindows註釋掉 style檔案中凡是在style.xml

Android獲取手機唯一標識

//獲取手機唯一標識 private String getId() { StringBuilder deviceId = new StringBuilder(); // 渠道標誌 deviceId.append("a");

Unity Android APP il2cpp熱更完美解決方案

1. 簡介 這是Unity Android APP il2cpp熱更完美解決方案的Demo(Git地址)的說明。 和現有的熱更解決方案不同的是,他不會引入多餘的語言(只是UnityScript,c#…),對Unity程式設計和編碼沒有任何限制。你可以在預置和場景裡的GameObjec

android 獲取視訊縮圖終極解決方案(ffmpeg)

前些天有個師弟(在做一個仿LinkInEyes行車記錄儀的app)問我怎麼獲取視訊縮圖,起初以為很簡單,就找了個常用的解決方案(使用者獲取正常的視訊檔案的縮圖): 方案1: private void initView() { imgPic = (

Android獲取裝置唯一ID

Android的開發者在一些特定情況下都需要知道手機中的唯一裝置ID。例如,跟蹤應用程式的安裝,生成用於複製保護的DRM時需要使用裝置的唯一ID。在本文件結尾處提供了作為參考的示例程式碼片段。 範圍 本文提供有關如何讀取各種Android裝置的 ID的介紹,用以使用標

iOS得到裝置唯一標識的各種方案及推薦使用方案的介紹

由於我們後臺判斷App登入時根據唯一標識來判斷的在不同終端登入的,如果唯一標識不一樣,說明是換了裝置,但是我們的使用者名稱密碼是我們同一系列的app公用的,所以得保證同一公司旗下的app在同一裝置下的唯一標識是同一個,避免出現同一裝置登陸同一系列其他app時提示換了裝置,說個簡單的例子如:我的使用者名稱是aa

各種獲取裝置唯一標識的方法介紹

//現在蘋果對隱私方面很嚴  很難獲取一種較好的裝置唯一識別符號方法,下面幾篇博文僅做參考: 使用UUID作為手機唯一識別符號在app刪除並重新安裝後會重新生成。當我們把第一次生成的UUID儲存到KeyChain中就能解決這個問題。  推薦大家使用SFHFKeychai

android獲取裝置唯一識別符號

1、獲取IMEI: adb命令:adb shell dumpsys iphonesubinfo 手機裝置檢視:<1>*#06#(通話快捷查詢)<2>設定--->關於手機--->狀態訊息--->IMEI <3>直

Android 獲取裝置唯一ID

只能在安卓手機上,需要支援Sim晶片,需要在AndroidManifest.xml中加入一個許可:android.permission.READ_PHONE_STATE,並且使用者應當允許安裝此應用。作為手機來講,IMEI是唯一的,它應該類似於 35988103

android 獲取手機唯一標識(遇到的問題及處理)

我們專案中有一個需求,那就是獲取同一使用者進行簽到時候只能使用一個裝置,在17年之前還可以正常使用,最近新增一個手機唯一標識許可權開啟管理,從而影響了最早之前的邏輯處理在這裡專門做一個記錄,來警醒自己。最早之前部落格 http://blog.csdn.net/u0140075

iOS獲取裝置唯一標識的八種方法

UDID UDID(Unique Device Identifier),iOS 裝置的唯一識別碼,是一個40位十六進位制序列(越獄的裝置通過某些工具可以改變裝置的 UDID),行動網路可以利用 UDID 來識別移動裝置。 許多開發者把 UDID 跟使用者的真

[Android]Delphi/C++ Builder 開發 Android 程式啟動畫面簡單完美解決方案

前面和音兒一起研究 Android 下啟動畫面的問題,雖然問題得到了解決,但是,總是感覺太麻煩,主要的問題: 1、需要手工去修改XML檔案; 2、而且需要對系統本身做出修改; 3、方案還不夠完善,需要較多的步驟; 現在,經常不斷的折騰,終於找到了一個

獲取裝置唯一標識(Unique Identifier):Windows系統

之前有做過防止使用者非法使用公司軟體的元件(軟體授權),其中基於windows系統獲取裝置唯一標誌,上任開發者獲取的唯一標識之一CPU ID已經不再唯一產生的Bug,便是參考此文修復。現記錄之。          唯一的標識一個裝置是一個基本功能,可以擁有很多應