1. 程式人生 > >Android中獲取手機IMEI,IMSI, MAC(Android 6.0、支援4G環境獲取)工具類(標識使用者唯一)

Android中獲取手機IMEI,IMSI, MAC(Android 6.0、支援4G環境獲取)工具類(標識使用者唯一)

最近專案中用到獲取手機IMEI,IMSI以及MAC,在此記錄一下,方便你我他。。。

那麼什麼時候會用到這些東西呢?

LZ 個人認為,如果專案當中需要對當前使用使用者裝置做唯一標識時(證明這個是你用的)可以使用這種方式。當然了這也是LZ個人的一點理解,歡迎大家打臉~

那麼首先簡單普及一下這三個分別都是什麼鬼。。。

1. IMEI

IMEI(International Mobile Equipment Identity)是國際移動裝置身份碼的縮寫,國際移動裝備辨識碼,是由15位數字組成的”電子串號”,它與每臺行動電話機一一對應,而且該碼是全世界唯一的。每一隻行動電話機在組裝完成後都將被賦予一個全球唯一的一組號碼,這個號碼從生產到交付使用都將被製造生產的廠商所記錄。
PS:通俗來講就是標識你當前裝置(手機)全世界唯一,類似於個人身份證,這個肯定唯一啦~

2. IMSI

國際移動使用者識別碼(IMSI:International Mobile Subscriber Identification
Number)是區別移動使用者的標誌,儲存在SIM卡中,可用於區別移動使用者的有效資訊。其總長度不超過15位,同樣使用0~9的數字。其中MCC是移動使用者所屬國家代號,佔3位數字,中國的MCC規定為460;MNC是移動網號碼,由兩位或者三位數字組成,中國移動的行動網路編碼(MNC)為00;用於識別移動使用者所歸屬的行動通訊網;MSIN是移動使用者識別碼,用以識別某一行動通訊網中的移動使用者
PS:通俗來講就是標識你當前SIM卡(手機卡)唯一,同樣類似於個人身份證,肯定唯一啦~

3. MAC

MAC(Media Access Control或者Medium Access Control)地址,意譯為媒體訪問控制,或稱為實體地址、硬體地址,用來定義網路裝置的位置。在OSI模型中,第三層網路層負責 IP地址,第二層資料鏈路層則負責 MAC地址。因此一個主機會有一個MAC地址,而每個網路位置會有一個專屬於它的IP地址
PS:通俗來講就是標識你當前使用我這個軟體(功能)時的地址,方便在你幹壞事的時候警察叔叔抓你~
最主要的是:在平板裝置上,無法通過imei標示裝置,我們會將mac地址作為使用者的唯一標識

好啦,下面貼出獲取這三項的程式碼。。。


import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.telephony.TelephonyManager;

/**
 * 獲取手機資訊工具類
 * 
 * @author HLQ
 * @createtime 2016-12-7下午2:06:03
 * @remarks
 */
public class MobileInfoUtil {

    /**
     * 獲取手機IMEI
     * 
     * @param context
     * @return
     */
    public static final String getIMEI(Context context) {
        try {
            //例項化TelephonyManager物件
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            //獲取IMEI號
            String imei = telephonyManager.getDeviceId();
            //在次做個驗證,也不是什麼時候都能獲取到的啊
            if (imei == null) {
                imei = "";
            }
            return imei;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

    }

    /**
     * 獲取手機IMSI
     */
    public static String getIMSI(Context context){
        try {
            TelephonyManager telephonyManager=(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
            //獲取IMSI號
            String imsi=telephonyManager.getSubscriberId();
            if(null==imsi){
                imsi="";
            }
            return imsi;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

}

2017年6月10日00:26:48 刪除之前獲取MAC地址方法,重新更新一下:

哎,有時候這活幹的真是心累,沒轍~按著自己的方向繼續前行吧~

今天,突然給我發個連結,說什麼Android 6.0獲取MAC地址,返回的都是02:00:00:00:00:00

突然一蒙圈,然後回過頭看看以前提交的資料,日了狗,還真是!!!

經過排查,發現了下面一些結論,瞭解瞭解:

從Android 6.0之後,android 移除了通過 WiFi 和藍芽 API 來在應用程式中可程式設計的訪問本地硬體標示符。現在 WifiInfo.getMacAddress() 和 BluetoothAdapter.getAddress() 方法都將返回 02:00:00:00:00:00 。

2018年1月17日14:04:09更新:

產品,後臺反饋,說部分手機或存在獲取不到Mac的情況。再仔細詢問後,發現獲取不到的有如下特徵:

  • 當前網路環境為4G

然而經過LZ在公司打死搜尋Android機型測試後發現如下幾個奇葩無奈的事兒:

  • 部分裝置在WIFI、4G以及無網路的情況可以正常獲取Mac;

  • 較多裝置僅僅支援在WIFI情況下可以獲取。

針對以上問題,LZ有如下猜(xiang)測(fa):

既然在WIFI狀態下正常獲取,那麼如果我4G情況下開啟WIFI,可以獲取到麼?

針對以上想法,LZ專門做了一個測試,測試結果,很nice~

果然,4G情況下開啟WIFI即可獲取到正常的MAC!

下面附上本次更新程式碼:

首先,需要如下許可權:

    <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" />

其次LZ簡單搞了一個工具類:

package com.hlq.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;

/**
 * author : HLQ
 * e-mail : [email protected]
 * time   : 2018/01/17
 * desc   : 獲取mac 相容6.0獲取 以及4g環境下獲取失敗
 * version: 1.0
 */
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();
    }

}

這裡需要注意的是,最好在程式啟動的時候進行首次獲取,當然不是非必需,看各位具體使用場景吧。LZ這裡放置在BaseApplication之中,如果首次獲取到的是預設提供的02:00:00:00:00:00,則需要開啟WIFI,當然這裡有很多優化的地方,時間緊迫,先這麼來~

public class BaseApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        String strMa = MacUtils.getMobileMAC(getApplicationContext());
        if (MacUtils.ERROR_MAC_STR.equals(strMa)) {
            Toast.makeText(this, "請授予開啟wifi許可權 以保證正常獲取mac", Toast.LENGTH_SHORT).show();
            MacUtils.getStartWifiEnabled();
        }
    }
}

之後直接獲取即可:

    public void getMACInfoLG(View view) {
        ((Button) view).setText("獲取到MAC地址為:" + MacUtils.getMobileMAC(this));
    }

Q:為什麼要首次執行來一次獲取呢?

A:因為在目前的測試得出的結論來看,在WIFI下幾乎手機都能獲取到,如果首次獲取不到,也就是預設為02:00:00:00:00:00的時候,開啟WIFI(因為不是一開啟就能立刻獲取到),稍等片刻即可獲取。

下面附上效果圖:

這裡寫圖片描述

補充

還有老鐵給LZ提供了另外一種獲取MAC地址的方式,但是在實際測試中,有的裝置還是需要開啟WIFI,下面附上原始碼:

    /**
     * 根據IP地址獲取MAC地址
     *
     * @return
     */
    private static String getLocalMacAddressFromIp() {
        String strMacAddr = null;
        try {
            //獲得IpD地址
            InetAddress ip = getLocalInetAddress();
            byte[] b = NetworkInterface.getByInetAddress(ip).getHardwareAddress();
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < b.length; i++) {
                if (i != 0) {
                    buffer.append(':');
                }
                String str = Integer.toHexString(b[i] & 0xFF);
                buffer.append(str.length() == 1 ? 0 + str : str);
            }
            strMacAddr = buffer.toString().toUpperCase();
        } catch (Exception e) {
        }
        return strMacAddr;
    }

    /**
     * 獲取移動裝置本地IP
     *
     * @return
     */
    private static InetAddress getLocalInetAddress() {
        InetAddress ip = null;
        try {
            //列舉
            Enumeration<NetworkInterface> en_netInterface = NetworkInterface.getNetworkInterfaces();
            while (en_netInterface.hasMoreElements()) {//是否還有元素
                NetworkInterface ni = (NetworkInterface) en_netInterface.nextElement();//得到下一個元素
                Enumeration<InetAddress> en_ip = ni.getInetAddresses();//得到一個ip地址的列舉
                while (en_ip.hasMoreElements()) {
                    ip = en_ip.nextElement();
                    if (!ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1)
                        break;
                    else
                        ip = null;
                }
                if (ip != null) {
                    break;
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return ip;
    }

結束

感謝各位老鐵相助~

釋出文章有缺陷發現後一定及時更新,以免誤導~

感謝小夥伴~、

2018-06-20 補充

剛剛有位夥計通過GItChat提問,這裡再次補充下:

當時LZ測試後,發現部分手機支援不開啟Wifi照樣可以獲取到MAC地址,而有些手機只能通過開啟Wifi去獲取MAC。

如果業務邏輯符合的話,可以通過校驗MAC格式,通過判斷MAC是否符合標準,之後去判斷是否需要開啟WIFI。

不過現在一般手機都需要彈框授權,可以來個友好提示。

其他的方式目前瞭解甚少,希望還有其他方式可以一塊交流~