我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android8.1获取Wifi,BT,Ethernet MAC地址问题分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android8.1获取Wifi,BT,Ethernet MAC地址问题分析

1.Wifi MAC地址

不说废话,直接上代码:

    public String getWifiMac() {
        String wifiMac = "";
        try {
            WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
            wifiMac = wifi.getConnectionInfo().getMacAddress();
            Log.d(TAG, "wifiMac: " + wifiMac);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return wifiMac;
    }

那么问题来了,这份代码在Android5.1上跑的好好的,在Android8.1上,拿到的却是一个固定地址02:00:00:00:00:00。
估计是Android新特性,不让第三方应用拿MAC地址了。我们阅读源码,证实一下。
frameworks/base/wifi/java/android/net/wifi/WifiManager.java

    public WifiInfo getConnectionInfo() {
        try {
            //这里没做啥事,只是封装了一下,调到服务端去了。
            return mService.getConnectionInfo(getContext().getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

    @Override
    public WifiInfo getConnectionInfo(String callingPackage) {
        enforceAccessPermission();//权限检查
        mLog.info("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush();
        
         //继续往下,调WifiStateMachine
        return mWifiStateMachine.syncRequestConnectionInfo(callingPackage);
    }
    //要声明android.permission.ACCESS_WIFI_STATE权限
    private void enforceAccessPermission() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
                "WifiService");
    }

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

    public WifiInfo syncRequestConnectionInfo(String callingPackage) {
        int uid = Binder.getCallingUid();
        WifiInfo result = new WifiInfo(mWifiInfo);// new了一个WifiInfo实例
        if (uid == Process.myUid()) return result;
        boolean hideBssidAndSsid = true;
        //这里把MacAddress设成了一个默认值,这个值的内容,就是02:00:00:00:00:00
        result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
        IPackageManager packageManager = AppGlobals.getPackageManager();
        try {
            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
                    uid) == PackageManager.PERMISSION_GRANTED) {
                    //这里又设置了MacAddress,原因就在这里,
                    //第三方应用过不了这个权限检查,代码走不到这里,所以拿到的是02:00:00:00:00:00
                result.setMacAddress(mWifiInfo.getMacAddress());
            }
            final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration();
            if (mWifiPermissionsUtil.canAccessFullConnectionInfo(
                    currentWifiConfiguration,
                    callingPackage,
                    uid,
                    Build.VERSION_CODES.O)) {
                hideBssidAndSsid = false;
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error checking receiver permission", e);
        } catch (SecurityException e) {
            Log.e(TAG, "Security exception checking receiver permission", e);
        }
        if (hideBssidAndSsid) {
            result.setBSSID(WifiInfo.DEFAULT_MAC_ADDRESS);
            result.setSSID(WifiSsid.createFromHex(null));
        }
        return result;
    }

看下这个WifiInfo.DEFAULT_MAC_ADDRESS是什么:
frameworks/base/wifi/java/android/net/wifi/WifiInfo.java

    
    public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";

源码的注释非常清晰,你没有声明"android.permission.LOCAL_MAC_ADDRESS"这个权限,不按规矩来,那就给个DEFAULT_MAC_ADDRESS 忽悠一下吧!

既然是没权限,那简单,加上就行了。
然而,我在测试应用的AndroidManifest.xml加了权限,再测试,还是不行,这就尴尬了。

继续分析,看下这个权限是个啥东西,为啥加了还不行?
frameworks/base/core/res/AndroidManifest.xml

    

这个权限的protectionLevel是"signature|privileged",这个目的就很明确了,就是不让第三方应用拿MAC地址,除非你用系统签名。

如果客户说,我同样的代码,在A平台可以,为啥B平台就不行了,都是你们的产品啊!
那就改吧,把权限检查去掉,就OK了。

--- a/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -1897,10 +1897,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
         IPackageManager packageManager = AppGlobals.getPackageManager();
         try {
-            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
-                    uid) == PackageManager.PERMISSION_GRANTED) {
+//            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
+//                    uid) == PackageManager.PERMISSION_GRANTED) {
                 result.setMacAddress(mWifiInfo.getMacAddress());
-            }
+//            }
             final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration();
             if (mWifiPermissionsUtil.canAccessFullConnectionInfo(
                     currentWifiConfiguration,

再看看Android5.1为啥没问题。调用流程都是一样的,不多说,直接看关键代码。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

    
    public WifiInfo syncRequestConnectionInfo() {
        return mWifiInfo;
    }

没有权限检查,直接把mWifiInfo丢回去,你再getMacAddress就OK了。

需要注意的是,如果WIFI没有打开过,可能拿不到MAC地址(取决于wifi模组),就是说要Supplicant跑起来,MAC才会传上来,这里就不深究了。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

    @Override
        public boolean processMessage(Message message) {
            logStateAndMessage(message, getClass().getSimpleName());
            switch(message.what) {
                case WifiMonitor.SUP_CONNECTION_EVENT:
                    if (DBG) log("Supplicant connection established");
                    setWifiState(WIFI_STATE_ENABLED);
                    //... ...省略部分代码
                    mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
2.BT MAC地址

应用代码:

    public String getBlueToothMac() {
        String btMac = "";
        try {
            BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            btMac = manager.getAdapter().getAddress();
            Log.d(TAG, "btMac: " + btMac);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return btMac;
    }

同Wifi一样,这个代码在Android8.1上运行,也是拿到固定地址02:00:00:00:00:00。
下面分析源码。
frameworks/base/core/java/android/bluetooth/BluetoothManager.java

    
    public BluetoothAdapter getAdapter() {
        return mAdapter;//返回BluetoothAdapter 实例
    }

frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

    
    @RequiresPermission(Manifest.permission.BLUETOOTH) //需要“android.permission.BLUETOOTH”权限
    public String getAddress() {
        try {
            return mManagerService.getAddress(); //调服务端的方法
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return null;
    }

frameworks/base/services/core/java/com/android/server/BluetoothManagerService.java

    public String getAddress() {
    //定义:private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
                "Need BLUETOOTH permission");
        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
                (!checkIfCallerIsForegroundUser())) {
            Slog.w(TAG,"getAddress(): not allowed for non-active and non system user");
            return null;
        }
        //这个权限检查跟wifi一样,如果没有“android.permission.LOCAL_MAC_ADDRESS”,就返回默认地址
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS)
                != PackageManager.PERMISSION_GRANTED) {
            return BluetoothAdapter.DEFAULT_MAC_ADDRESS;
        }
        try {
            mBluetoothLock.readLock().lock();
            if (mBluetooth != null) return mBluetooth.getAddress();
        } catch (RemoteException e) {
            Slog.e(TAG, "getAddress(): Unable to retrieve address remotely. Returning cached address", e);
        } finally {
            mBluetoothLock.readLock().unlock();
        }
        // mAddress is accessed from outside.
        // It is alright without a lock. Here, bluetooth is off, no other thread is
        // changing mAddress
        return mAddress;
    }

跟wifi一个套路,跟着代码流程走一遍就行了,

3.Ethernet MAC地址

Android没有提供获取以太网MAC地址的API,但有一个API可以间接实现。

    public String getEthernetMac() {
        String ethMac = "";
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
        if (info != null) {
            ethMac = info.getExtraInfo();//这个ExtraInfo就是以太网的mac地址
            Log.d(TAG, "ethernet mac = " + ethMac);
        } else {
            Log.e(TAG, "info is null !");
        }
        return ethMac;
    }

为什么“getExtraInfo”就能拿到以太网的MAC地址?我们继续往下看。
frameworks/base/core/java/android/net/NetworkInfo.java

    
    public String getExtraInfo() {
        synchronized (this) {
            return mExtraInfo;
        }
    }
    
    public void setExtraInfo(String extraInfo) {
        synchronized (this) {
            this.mExtraInfo = extraInfo;
        }
    }

这个“getExtraInfo”的本意是“Report the extra information about the network state”,具体是什么信息,取决于底层。
在这里插入图片描述
setExtraInfo添加了哪些附加信息?
如上图,
Ethernet是mHwAddr,
Wifi是mWifiInfo.getSSID(),
telephony是mApnSetting.apn
所以,对于以太网而言,getExtraInfo就能取出MAC地址。

4.修改应用

如果不改Android源码,而是由应用端来解决这个问题,有没有办法?
直接读节点,也是可行的。
wifi的节点:/sys/class/net/wlan0/address
ethernet的节点:/sys/class/net/eth0/address
Bt没有找到节点。
写一下获取wifi mac地址的代码,ethernet类似。

    public String getWifiMacFromNode() {
        String wifiMac = "";
        RandomAccessFile f = null;
        try {
            f = new RandomAccessFile("/sys/class/net/wlan0/address", "r");
            f.seek(0);
            wifiMac = f.readLine().trim();
            f.close();
            Log.d(TAG, "getWifiMacFromNode "+wifiMac);
            return wifiMac;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return wifiMac;
        } catch (IOException e) {
            e.printStackTrace();
            return wifiMac;
        } finally {
            if (f != null) {
                try {
                    f.close();
                    f = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

其他方法,反射调用Android的API,同时绕过权限检查,应该也是可行的。


作者:zhangchao2280


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android8.1获取Wifi,BT,Ethernet MAC地址问题分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android8.1获取Wifi,BT,Ethernet MAC地址问题分析

1.Wifi MAC地址 不说废话,直接上代码:public String getWifiMac() {String wifiMac = "";try {WifiManager wifi = (WifiManager) getSystemS
2022-06-06

怎么解决Android 6.0获取wifi Mac地址为02:00:00:00:00:00问题

这篇文章主要介绍怎么解决Android 6.0获取wifi Mac地址为02:00:00:00:00:00问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言:之前项目比较旧,手机版本还比较低,还使用eclipse
2023-05-30

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录