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 系统更新,还需要不断的升级。

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