Android 獲取安卓設備的唯一標識碼 ID

現存問題:

 Android 獲取設備唯一值,由於 國內rom 不同,一直沒有一個 一個穩定的唯一標示。

權限

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

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

常用的信息

  • imei              Sim Serial Number

  • Android_id    Android ID 

  • mac 地址      mac address 

  •  CPUSerial           cpu sn

  • SerialNumber

  • Installtion ID

IMEI

獲取權限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
獲取方法

   public static String getIMEI(Context context) {
        TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling

            return "";
        }
        String szImei = TelephonyMgr.getDeviceId();
        return szImei;
    }

如果 6.0 以上需要動態權限,發現在 定製設備上,這個爲空。手機上一般存在。這個值可能有手機卡的 Android 設備纔會有;如果沒有手機卡,支持電話可能會沒有;目前看唯一;

ANDROID_ID

在設備首次啓動時,系統會隨機生成一個64位的數字,並把這個數字以16進制字符串的形式保存下來,這個16進制的字符串就是ANDROID_ID,當設備被wipe後該值會被重置。可以通過下面的方法獲取:

 String m_szAndroidID = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.ANDROID_ID);

Android_id是不需要權限,但它跟手機rom和手機廠商有關(Android_id是設備首次運行隨機生成的64位數字)有點手機是獲取不到,恢復出廠設置時也會改變,可靠性也較差

在Android 8.0 中針對Android ID有一個比較大的變化,8.0開始不同簽名的App 會產生不同的Android ID,這對於使用AndroidID來追蹤設備的用戶來說影響比較大

MAC ADDRESS

可以使用手機Wifi或藍牙的MAC地址作爲設備標識,但是並不推薦這麼做,原因有以下兩點:

  • 硬件限制:並不是所有的設備都有Wifi和藍牙硬件,硬件不存在自然也就得不到這一信息。

  • 獲取的限制:如果Wifi沒有打開過,是無法獲取其Mac地址的;而藍牙是隻有在打開的時候才能獲取到其Mac地址。

獲取Wifi Mac地址:


import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;

import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;

 
public class MacUtils {

    /**
     * 獲取失敗默認返回值
     */
    public static final String ERROR_MAC_STR = "02:00:00:00:00:00";

    // Wifi 管理器
    private static WifiManager mWifiManager;

    /**
     * 實例化WifiManager對象
     *
     * @param context 當前上下文對象
     * @return
     */
    private static WifiManager getInstant(Context context) {
        if (mWifiManager == null) {
            mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        }
        return mWifiManager;
    }

    /**
     * 開啓wifi
     */
    public static void getStartWifiEnabled() {
        // 判斷當前wifi狀態是否爲開啓狀態
        if (!mWifiManager.isWifiEnabled()) {
            // 打開wifi 有些設備需要授權
            mWifiManager.setWifiEnabled(true);
        }
    }

    /**
     * 獲取手機設備MAC地址
     * MAC地址:物理地址、硬件地址,用來定義網絡設備的位置
     * modify by heliquan at 2018年1月17日
     *
     * @param context
     * @return
     */
    public static String getMobileMAC(Context context) {
        mWifiManager = getInstant(context);
        // 如果當前設備系統大於等於6.0 使用下面的方法
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return getAndroidHighVersionMac();
        } else { // 當前設備在6.0以下
            return getAndroidLowVersionMac(mWifiManager);
        }
    }

    /**
     * Android 6.0 設備兼容獲取mac
     * 兼容原因:從Android 6.0之後,Android 移除了通過WiFi和藍牙API來在應用程序中可編程的訪問本地硬件標示符。
     * 現在WifiInfo.getMacAddress()和BluetoothAdapter.getAddress()方法都將返回:02:00:00:00:00:00
     *
     * @return
     */
    public static String getAndroidHighVersionMac() {
        String str = "";
        String macSerial = "";
        try {
            // 由於Android底層基於Linux系統 可以根據shell獲取
            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 (Exception ex) {
            ex.printStackTrace();
        }
        if (macSerial == null || "".equals(macSerial)) {
            try {
                return loadFileAsString("/sys/class/net/eth0/address")
                        .toUpperCase().substring(0, 17);
            } catch (Exception e) {
                e.printStackTrace();
                macSerial = getAndroidVersion7MAC();
            }
        }
        return macSerial;
    }

    /**
     * Android 6.0 以下設備獲取mac地址 獲取失敗默認返回:02:00:00:00:00:00
     *
     * @param wifiManager
     * @return
     */
    @NonNull
    private static String getAndroidLowVersionMac(WifiManager wifiManager) {
        try {
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            String mac = wifiInfo.getMacAddress();
            if (TextUtils.isEmpty(mac)) {
                return ERROR_MAC_STR;
            } else {
                return mac;
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("mac", "get android low version mac error:" + e.getMessage());
            return ERROR_MAC_STR;
        }
    }

    /**
     * 兼容7.0獲取不到的問題
     *
     * @return
     */
    public static String getAndroidVersion7MAC() {
        try {
            List<NetworkInterface> 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 e) {
            Log.e("mac", "get android version 7.0 mac error:" + e.getMessage());
        }
        return ERROR_MAC_STR;
    }

    public static String loadFileAsString(String fileName) throws Exception {
        FileReader reader = new FileReader(fileName);
        String text = loadReaderAsString(reader);
        reader.close();
        return text;
    }

    public static String loadReaderAsString(Reader reader) throws Exception {
        StringBuilder builder = new StringBuilder();
        char[] buffer = new char[4096];
        int readLength = reader.read(buffer);
        while (readLength >= 0) {
            builder.append(buffer, 0, readLength);
            readLength = reader.read(buffer);
        }
        return builder.toString();
    }

}

 CPUSerial           cpu sn

這個好像只有在root 過的設備纔可以取到 ,沒有root的設備,可能會空

 public static String getCPUSerial() {
        String str = "", strCPU = "", cpuAddress = "";
        try {
            // 讀取CPU信息
            Process pp = Runtime. getRuntime().exec("cat /proc/cpuinfo");
            InputStreamReader ir = new InputStreamReader(pp.getInputStream());
            LineNumberReader input = new LineNumberReader(ir);
            // 查找CPU序列號
            for ( int i = 1; i < 100; i++) {
                str = input.readLine();
                if (str != null) {
                    // 查找到序列號所在行
                    if (str.indexOf( "Serial") > -1) {
                        // 提取序列號
                        strCPU = str.substring(str.indexOf(":" ) + 1, str.length());
                        // 去空格
                        cpuAddress = strCPU.trim();
                        break;
                    }
                } else {
                    // 文件結尾
                    break;
                }
            }
        } catch (IOException ex) {
            // 賦予默認值
            ex.printStackTrace();
        }
        return cpuAddress;
    }

SerialNumber 設備SN

具體測試,這個好像也只有root過的設備可以獲取到,手機不能獲取

String SerialNumber = android.os.Build.SERIAL;

Installtion ID

如果並不是確實需要對硬件本身進行綁定,使用自己生成的UUID也是一個不錯的選擇,因爲該方法無需訪問設備的資源,也跟設備類型無關。

這種方式的原理是在程序安裝後第一次運行時生成一個ID,該方式和設備唯一標識不一樣,不同的應用程序會產生不同的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();  
    }  
} 

例:

生成文件保存在多媒體目錄下

private static final String saveFileName = "ADMObileDeviceIdFile";


    /**
     * 在媒體文件中 生成fileName文件
     * 向Mediastore添加內容
     */
    private void creatUUIDFile() {

        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");

        ContentResolver contentResolver = this.getContentResolver();

        Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

        try {
            FileOutputStream outputStream = null;
            //訪問 對於單個媒體文件,請使用 openFileDescriptor()。
            if (deviceVersion >= 10) {
                ParcelFileDescriptor fielDescriptor = contentResolver.openFileDescriptor(uri, "w", null);
                outputStream = new FileOutputStream(fielDescriptor.getFileDescriptor());
            } else {
                File file = new File(filePath);
                outputStream = new FileOutputStream(file);
            }
            try {
                //講UUID寫入到文件中
                String uuidStr = UUID.randomUUID().toString();
                outputStream.write(uuidStr.getBytes());
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            contentResolver.update(uri, values, null, null);
            values.clear();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

      /**
     * 將內容選擇編碼格式輸出
     *
     * @param inputStream
     * @return
     */
    private static String inputStreamToString(InputStream inputStream) {
        InputStreamReader inputStreamReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, "utf-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        BufferedReader reader = new BufferedReader(inputStreamReader);
        StringBuffer sb = new StringBuffer("");
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
      /**
     * 檢查文件是否存在
     *
     * @return
     */
    private String checkUUIDFileByUri() {

        String[] projection = {
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media._ID
        };
        //查詢
        ContentResolver contentResolver = this.getContentResolver();

        // 添加篩選條件,根據SQLite表裏的 display_name是否與saveFileName一直
        String selection = MediaStore.Images.Media.DISPLAY_NAME + "=" + "'" + saveFileName + "'";

        //EXTERNAL_CONTENT_URI 爲查詢外置內存卡
        Cursor mCursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, null, null);

        String getSaveContent = "";
        if (mCursor != null) {
            while (mCursor.moveToNext()) {

                int fileIdIndex = mCursor.getColumnIndex(MediaStore.Images.Media._ID);
                String thumbPath = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
                        .appendPath(String.valueOf(mCursor.getInt(fileIdIndex))).build().toString();
                Uri fileUri = Uri.parse(thumbPath);

                FileInputStream inputStream = null;
                try {
                    if (deviceVersion >= 10) {
                        ParcelFileDescriptor fielDescriptor = contentResolver.openFileDescriptor(fileUri, "r", null);
                        inputStream = new FileInputStream(fielDescriptor.getFileDescriptor());
                    } else {
                        File file = new File(filePath);
                        inputStream = new FileInputStream(file);
                    }
                    getSaveContent = inputStreamToString(inputStream);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                //只有在得到的唯一標識符不爲空的情況下才結束循環
                if (!TextUtils.isEmpty(getSaveContent)) {
                    break;
                }
            }
            mCursor.close();
        }
        return getSaveContent;
    }

總結:

    一般的情況會前面所有的,加上最install id(或者類似的id)結合使用,並且隨着Android 系統更新,還需要不斷的升級。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章