【轉帖】一探究竟:Android M 如何獲取 Wifi MAC地址(1)

#【轉帖】一探究竟:Android M 如何獲取 Wifi MAC地址(1) @(Android研究)[Android|MAC地址]java


[TOC]android


##引子shell

WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
String macAddress = wifiManager.getConnectionInfo().getMacAddress();

早在 Android M 預覽版發佈時就有人發現,經過WifiInfo.getMacAddress()獲取的MAC地址是一個「假」的固定值,其值爲 「02:00:00:00:00:00」。對於這個,官方的說法固然不外乎「保護用戶隱私數據」。網絡

大膽假設

不知是有意仍是一時不查,Google卻忘了Java獲取設備網絡設備信息的API——NetworkInterface.getNetworkInterfaces()——仍然能夠間接地獲取到MAC地址。app

Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
    NetworkInterface iF = interfaces.nextElement();
    byte[] addr = iF.getHardwareAddress();
    if (addr == null || addr.length == 0) {
        continue;
    }
    StringBuilder buf = new StringBuilder();
    for (byte b : addr) {
        buf.append(String.format("%02X:", b));
    }
    if (buf.length() > 0) {
        buf.deleteCharAt(buf.length() - 1);
    }
    String mac = buf.toString();
    Log.d("mac", "interfaceName="+iF.getName()+", mac="+mac);
}

輸出以下:ide

interfaceName=dummy0, mac=e6:f9:44:3c:ee:da
interfaceName=p2p0, mac=ae:22:0b:3e:d4
interfaceName=wlan0, mac=ac:22:0b:3e:d4
...

顧名思義,猜測wlan0對應的mac地址應該就是咱們要找的。ui

當心求證

既然NetworkInterface能夠正常獲取,那得好好看看它在 Android framework 中的實現源碼:code

public byte[] getHardwareAddress() throws SocketException {
    try {
        // Parse colon-separated bytes with a trailing newline: "aa:bb:cc:dd:ee:ff\n".
        String s = IoUtils.readFileAsString("/sys/class/net/" + name + "/address");
        byte[] result = new byte[s.length()/3];
        for (int i = 0; i < result.length; ++i) {
            result[i] = (byte) Integer.parseInt(s.substring(3*i, 3*i + 2), 16);
        }
        // We only want to return non-zero hardware addresses.
        for (int i = 0; i < result.length; ++i) {
            if (result[i] != 0) {
                return result;
            }
        }
        return null;
    } catch (Exception ex) {
        throw rethrowAsSocketException(ex);
    }
}

原來MAC地址是直接從"/sys/class/net/" + name + "/address"文件中讀取的!orm

這個name是什麼呢?server

繼續翻源碼:

private static final File SYS_CLASS_NET = new File("/sys/class/net");
...
public static Enumeration<NetworkInterface> getNetworkInterfaces() throws SocketException {
    return Collections.enumeration(getNetworkInterfacesList());
}
private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException {
    String[] interfaceNames = SYS_CLASS_NET.list();
    NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length];
    String[] ifInet6Lines = readIfInet6Lines();
    for (int i = 0; i < interfaceNames.length; ++i) {
        interfaces[i] = NetworkInterface.getByNameInternal(interfaceNames[i], ifInet6Lines);
        ...
    }
    List<NetworkInterface> result = new ArrayList<NetworkInterface>();
    for (int counter = 0; counter < interfaces.length; counter++) {
        ...
        result.add(interfaces[counter]);
    }
    return result;
}

能夠看出/sys/class/net目錄下的一個文件夾即對應一個NetworkInterfacename

user@android:/$ ls /sys/class/net/
dummy0
lo
p2p0
rev_rmnet0
rev_rmnet1
rev_rmnet2
rev_rmnet3
rmnet0
rmnet1
rmnet2
rmnet3
rmnet_smux0
sit0
wlan0

從路由器上在線設備的MAC地址列表,能夠印證我這臺設備Wifi的namewlan0

那麼讀取文件/sys/class/net/wlan0/address就輕鬆獲得了這臺設備的MAC地址

user@android:/$ cat /sys/class/net/wlan0/address
ac:22:0b:3e:d4

不出所料! 進而,問題又變成如何獲取設備的Wifi的interface name

探尋 Wifi interface name

回到開頭,咱們是經過context.getSystemService(Context.WIFI_SERVICE) 獲取的WifiManager

WifiManager確定是與遠程系統服務的IBinder在交互,而系統服務都是在SystemServer.run()中被啓動的。

SystemServer.java中搜索關鍵字」WIFI_SERVICE」,很容易便找到mSystemServiceManager.startService(WIFI_SERVICE_CLASS);

順藤摸瓜,又找到系統服務實現類com.android.server.wifi.WifiServiceWifiService中的邏輯很簡單,構造真正的實現類com.android.server.wifi.WifiServiceImpl對象並註冊到系統服務中:

public final class WifiService extends SystemService {
    private static final String TAG = "WifiService";
    final WifiServiceImpl mImpl;
    public WifiService(Context context) {
        super(context);
        mImpl = new WifiServiceImpl(context);
    }
    @Override
    public void onStart() {
        Log.i(TAG, "Registering " + Context.WIFI_SERVICE);
        publishBinderService(Context.WIFI_SERVICE, mImpl);
    }
    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            mImpl.checkAndStartWifi();
        }
    }
}

打開WifiServiceImpl.java,從構造方法處,一眼就看到了關鍵代碼:mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");

如此這般終於找到定義設備的Wifi的interface name的地方:SystemProperties

經過adb能夠很容易獲得這個屬性值:adb shell getprop wifi.interface

那麼在咱們應用裏能夠經過Java的反射獲取SystemProperties,進而調用靜態方法get便可拿到Wifi的interface name

結論

縱然Google掩耳盜鈴似的把API返回值篡改了,但終究是沒有改底層的實現,經過閱讀源碼的方式仍是很容易找到解決辦法的。

  • 經過反射SystemProperties獲取屬性wifi.interface的值
  • 讀取/sys/class/net/+interfaceName+/address文件的第一行便是Wifi MAC地址

請注意:

  • 個別設備可能會拒絕你訪問/sys目錄
  • /sys/class/net/+interfaceName+/address文件不必定存在,請在讀取以前確認設備的Wifi已經打開或已鏈接
  • 未免廠商實現不一的風險,請先嚐試用NetworkInterface.getByName(interfaceName)來間接獲取Wifi MAC地址

結束了?

這麼早下結論其實不是個人風格。

本文只是探討了如何繞過官方設置的障礙,從「黑科技」的角度獲取MAC地址。那麼官方推薦的「正道」該如何走呢?且看下回分解~

相關文章
相關標籤/搜索