Android Wifi模块梳理

本篇文章梳理三件事:
① wifi列表扫描与更新机制
② wifi连接实现与现状
③ wifi开关实现与现状

源码分析基于:android-13.0.0_r43

一、wifi列表扫描与更新机制

1.1 整体架构

应用层 (Application Layer)
    └── 用户应用
         |
框架层 (Framework Layer) wifi扫描结果集:ScanRequestProxy的Map<String, ScanResult> mLastScanResultsMap中
    └── WifiManager
         └── WifiServiceImpl (IWifiManager.Stub)
              └── WifiScanningServiceImpl (IWifiScanner.Stub)
                  └── WifiNative
                      |
硬件抽象层 (HAL)
    └── IWifiScannerImpl (WificondScannerImpl、HalWifiScannerImpl)
        |                                   
用户空间守护进程 (User Space Daemon)
    └── wificond
        | (netlink通信)
内核层 (Kernel Layer)
    └── WiFi 驱动(wpa_supplicant)

① 扫描流程
应用层通过IWifiManager#startScan binder call到system framework发起扫描请求,在IWifiScanner服务内完成整体数据封装及状态机扭转,根据状态的适合时机调用WifiNative与Hal通信,HAL 层接口实现将请求转发给 wificond(native 守护进程),由他收到请求并进行具体的操作,通过netlink与内核通信;

② 更新流程
内核将扫描结果返回给wificond,wificond从NL80211Packet套接字里取出数据,通过handle层层回调上传,最终更新framework层的ScanRequestProxy的Map<String, ScanResult> mLastScanResultsMap中。

1.2 wifi扫描触发机制

1)主动触发
app调用系统api,例如:IWifiManager#startScan、IWifiScanner#startScan 、ConnectivityManager#requestNetwork(发起网络连接弹窗前,会主动先触发一次wifi扫描,且不管成功失败都会循环扫描,直到用户点击确定连接)等。

2)周期触发
① 前置条件:WIFI开启 & 亮屏 & 未连接wifi & 自动开启WLAN(默认开启)
② 触发和重置事件:亮灭屏、网络状态切换(事件触发会检查前置条件)
③ 扫描频率:

  • 在wifi设置页:固定10S一次(WifiTracker#onStart -> resumeScanning -> Scanner.resume);
  • 在非wifi设置页:20-160S一次(WifiConnectivityManager#startConnectivityScan --> startPeriodicScan --> startPeriodicSingleScan);

1.3 wifi扫描更新详细流程梳理

以starScan为入口分析整体wifi列表扫描更新流程:

1.3.1 扫描流程

关键步骤总结:
① 应用层通过WifiManager#startScan发起扫描请求;
② 中间通过服务接口IWifiManager实现类WifiServiceImpl处理扫描请求;
③ 再交给服务接口IWifiScanner实现类WifiScanningServiceImpl 进一步处理请求,该服务更垂直于 扫描任务,内部通过状态机管理扫描任务,同时调用底层接口进行扫描;
④ WifiScanningServiceImpl通过WifiNative调用HAL层;
⑤ HAL 层接口实现将请求转发给 wificond(native 守护进程),由他收到请求并进行具体的操作,与内核通信;
⑥ 内核层 WiFi 驱动处理扫描并返回结果 HAL通过与驱动通信,最终触发硬件进行实际Wifi扫描。

1.3.2 更新流程

关键步骤总结:
① 内核驱动完成wifi扫描后,通过 Netlink 消息将结果返回给用户空间;
② 扫描结果通过handler层层向上传递,最终到WifiScanner,由他回调扫描监听,更新ScanRequestProxy中的扫描结果缓存Map

二、wifi连接实现与现状

方案1:WifiManager#addNetwork & enableNetwork
结论:< android Q版本可静默连接, >= android Q版本 被限制,额外需要android.permission.SYSTEM_ALERT_WINDOW。

1)权限:

android.permission.CHANGE_WIFI_STATE
android.permission.SYSTEM_ALERT_WINDOW

2)代码实现:

WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiConfiguration config = new WifiConfiguration();
        config.SSID = String.format("\"%s\"", WIFI_SSID);
        config.preSharedKey = String.format("\"%s\"", WIFI_PASSWORD); 
        
        try {
            int netId = wifiManager.addNetwork(config);
            boolean enableNetwork = wifiManager.enableNetwork(netId, true);
        } catch (Throwable e) {
            Log.i(TAG, "Failed to add network: " + e.getMessage());
        }

3)源码分析
调用栈:

① 添加网络
android.net.wifi.WifiManager#addNetwork
com.android.server.wifi.WifiServiceImpl#addOrUpdateNetwork // 条件限制:版本 系统应用 or 悬浮窗权限
com.android.server.wifi.WifiServiceImpl#addOrUpdateNetworkInternal
com.android.server.wifi.WifiConfigManager#addOrUpdateNetwork
com.android.server.wifi.WifiConfigManager#addOrUpdateNetworkInternal
添加网络最终加到ConfigurationMap中,并经由com.android.server.wifi.NetworkListStoreData持久化缓存在:/data/misc/apexdata/com.android.wifi.WifiConfigStore.xml里

② 连接网络
android.net.wifi.WifiManager#enableNetwork
com.android.server.wifi.WifiServiceImpl#enableNetwork // 条件限制:版本 系统应用 or 悬浮窗权限
com.android.server.wifi.WifiServiceImpl#triggerConnectAndReturnStatus
com.android.server.wifi.ConnectHelper#connectToNetwork
com.android.server.wifi.ConcreteClientModeManager#connectNetwork
com.android.server.wifi.ClientModeImpl#connectNetwork
send Message: CMD_CONNECT_NETWORK
com.android.server.wifi.ClientModeImpl#connectToUserSelectNetwork
com.android.server.wifi.ClientModeImpl#startConnectToNetwork
com.android.server.wifi.ClientModeImpl#connectToNetwork(WifiConfiguration config)
com.android.server.wifi.WifiNative#connectToNetwork(String ifaceName, WifiConfiguration configuration)
com.android.server.wifi.SupplicantStaIfaceHal#connectToNetwork 
com.android.server.wifi.SupplicantStaNetworkHalAidlImpl#select
hardware/interfaces/wifi/supplicant/1.0/ISupplicantStaNetwork.hal#select
最终将提供的网络配置添加到wpa_supplicant并启动连接

① WifiManager#addNetwork 和 #enableNetwork 限制条件:小于Q版本、系统应用或特权应用、悬浮窗权限。


  • mWifiPermissionsUtil.isTargetSdkLessThan:applicationInfo targetSdkVersion

  • isPrivileged:系统权限: checkPermission(android.Manifest.permission.NETWORK_SETTINGS checkPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD checkPermission(android.Manifest.permission.NETWORK_STACK checkPermission(android.Manifest.permission.NETWORK_MANAGED_PROVISIONING mContext.getPackageManager().checkSignatures(i, 1000) == 0; 即:uid = 1000

  • isDeviceOrProfileOwner(callingUid, str): DeviceOwner: 设备所有者。android 5.0 引入,开放DevicePolicyManager更敏感的API能力。一个设备只能存在一个DeviceOwner,DeviceOwner应用不能被卸载,也无法在设置->安全中关闭权限, 生产环境没有好的静激活方式。 ProfileOwner:配置管理者,具有配置和管理设备用户配置文件(profile)的权限。实现需要系统权限:android.permission.MANAGE_USERS

  • mWifiPermissionsUtil.isSystem(str, callingUid): applicationInfo flag = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP

② addNetwork:添加网络 最终会内存缓存在ConfigurationMap中,并且持久化缓存在:/data/misc/apexdata/com.android.wifi.WifiConfigStore.xml里,过程中暂时没有额外可干预添加的点;

③ enableNetwork: 从map中获取对应网络config,最终将其添加到wpa_supplicant并启动连接,过程中暂时没有额外可干预添加的点。

方案2:ConnectivityManager#requestNetwork
结论:>= android Q版本可以使用, 没有额外限制,唯一问题就是会拉起Settings弹窗,用户交互点击连接,再实现连接,过程并不静默。

1)权限

android.permission.CHANGE_NETWORK_STATE
  1. 代码实现
@RequiresApi(api = Build.VERSION_CODES.Q)
    public void requestNetwork(Context context) {
        // 构建 NetworkRequest 对象
        NetworkRequest networkRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .setNetworkSpecifier(new WifiNetworkSpecifier.Builder().setSsid(WIFI_SSID).setWpa2Passphrase(WIFI_PASSWORD).build())
                .build();
        
        // 获取 ConnectivityManager 实例
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        
        ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
            }

            @Override
            public void onUnavailable() {
            }

            @Override
            public void onLost(Network network) {
            }
        };

        // 注册网络回调
        connectivityManager.requestNetwork(networkRequest, networkCallback);
    }

3)源码分析
调用栈:

① system_server ConnectivityService#requestNetwork调用栈:
com.android.server.ConnectivityService#requestNetwork // type:REQUEST android.permission.CHANGE_NETWORK_STATE权限校验
com.android.server.ConnectivityService#getNriToRegister
com.android.server.ConnectivityService#handleRegisterNetworkRequest // 注册网络请求
com.android.server.ConnectivityService#rematchAllNetworksAndRequests // 评估所有网络请求与可用网络之间的匹配
com.android.server.ConnectivityService#issueNetworkNeeds()
com.android.server.ConnectivityService#informOffer
android.net.NetworkProvider.NetworkOfferCallbackProxy#onNetworkNeeded
com.android.wifi.x.android.net.NetworkFactoryImpl.handleAddRequest //添加网络请求
com.android.server.wifi.WifiNetworkFactory.needNetworkFor
com.android.server.wifi.WifiNetworkFactory#startUi // 拉起com.android.settings.wifi.NetworkRequestDialogActivity

② com.android.settings 弹窗交互:
onResume注册WifiManager.NetworkRequestMatchCallback 
呼起Dialog,onClickRescanButton 回调 com.android.server.wifi.WifiNetworkFactory.NetworkFactoryUserSelectionCallback#select

③ system_server WifiNetworkFactory#handleConnectToNetworkUserSelection调用栈:
com.android.server.wifi.WifiNetworkFactory#handleConnectToNetworkUserSelection // 针对用户选择的网络发起连接
com.android.server.wifi.WifiNetworkFactory#handleConnectToNetworkUserSelectionInternal
com.android.server.wifi.ActiveModeWarden#requestLocalOnlyClientModeManager
com.android.server.wifi.WifiNetworkFactory.ClientModeManagerRequestListener#onAnswer
com.android.server.wifi.WifiNetworkFactory#handleClientModeManagerRetrieval
com.android.server.wifi.WifiNetworkFactory#connectToNetwork{
...
com.android.server.wifi.WifiNetworkFactory#addNetworkToWifiConfigManager //添加WiFi
   - WifiConfigManager.addOrUpdateNetwork
...
com.android.server.wifi.ConnectHelper#connectToNetwork //连接WIFI
}

附:
                                                  ActiveModeWarden
                                                 /                \
                                                /                  \
                         ConcreteClientModeManager         DefaultClientModeManager
                       (Client Mode + Scan Only Mode)            (Wifi off)
                              /            \
                            /               \
                      ClientModeImpl       ScanOnlyModeImpl

方案3:WifiManager#addNetworkSuggestions
结论:只做建议添加,具体连接谁看系统。

1)权限

android.permission.CHANGE_WIFI_STATE

2)代码实现

 WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                .setSsid(WIFI_SSID)
                .setWpa2Passphrase(WIFI_PASSWORD)
                .setIsAppInteractionRequired(true)
                .build();
        List<WifiNetworkSuggestion> suggestionsList = new ArrayList<>();
        suggestionsList.add(suggestion);
        int status = wifiManager.addNetworkSuggestions(suggestionsList);

3)源码分析
调用栈

① 添加建议网络
com.android.server.wifi.WifiServiceImpl#addNetworkSuggestions
com.android.server.wifi.WifiNetworkSuggestionsManager#add

② 弹窗确认
com.android.server.wifi.WifiNetworkSuggestionsManager#sendUserApprovalDialog
com.android.server.wifi.WifiDialogManager#createSimpleDialog
com.android.server.wifi.WifiDialogManager.DialogHandle#launchDialog
com.android.settings.wifi.WifiDialogActivity#onCreate

③建立连接
com.android.settings.wifi.WifiDialog.WifiDialogListener#onSubmit

  @SystemApi
    @RequiresPermission(anyOf = {
            android.Manifest.permission.NETWORK_SETTINGS,
            android.Manifest.permission.NETWORK_SETUP_WIZARD,
            android.Manifest.permission.NETWORK_STACK
    })
android.net.wifi.WifiManager#connect(int, android.net.wifi.WifiManager.ActionListener)
// 弹窗点击取消或者确认后,弹窗不会再出现。

方案4:action: android.settings.WIFI_ADD_NETWORKS
拉起:com.android.settings.wifi.addappnetworks.AddAppNetworksActivity

1)内部实现分析:最终连接使用的系统api:

android.net.wifi.WifiManager#connect(WifiConfiguration, android.net.wifi.WifiManager.ActionListener)
 
 @SystemApi
    @RequiresPermission(anyOf = { 
            android.Manifest.permission.NETWORK_SETTINGS, // signature
            android.Manifest.permission.NETWORK_SETUP_WIZARD, // signature|setup
            android.Manifest.permission.NETWORK_STACK //signature
    })
    public void connect(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
        if (config == null) throw new IllegalArgumentException("config cannot be null");
        connectInternal(config, WifiConfiguration.INVALID_NETWORK_ID, listener);
    }

2)WifiManager#connect的底层调用栈:

android.net.wifi.WifiManager#connect
android.net.wifi.WifiManager#connectInternal
com.android.server.wifi.WifiServiceImpl#connect {
  ...
com.android.server.wifi.WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)  //添加
  ...
com.android.server.wifi.ConnectHelper#connectToNetwork(ClientModeManager, NetworkUpdateResult, ActionListenerWrapper, int, String)  //连接
}

方案5:action:android.settings.panel.action.WIFI

com.android.settings/com.android.settings.panel.SettingsPanelActivity 仅仅拉起wifi面板,需要用户主动去连接wifi

汇集目前所有能实现wifi连接的方案,总结:

wifi连接整体分两步:1)添加wifi;2)连接wifi。

添加wifi所有方案都收敛于:WifiConfigManager#addOrUpdateNetwork(WifiConfiguration config, int uid);
连接wifi所有方案都收敛于:ConnectHelper#connectToNetwork(NetworkUpdateResult result,ActionListenerWrapper wrapper, int callingUid,String packageName)

三、wifi开关实现与现状

Android 10之前 :
权限:android.permission.CHANGE_WIFI_STATE、android.permission.ACCESS_WIFI_STATE

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
boolean b = wifiManager.setWifiEnabled(true);

Android 10之后:该接口被限制,官方推荐方案(拉起设置面板来操作)

Intent panelIntent = new Intent(Settings.Panel.ACTION_WIFI);
startActivity(panelIntent);

限制代码分析:



总体来说就是:三方应用targetSdkVersion大于30就不能用了。

除了官方推荐的拉起系统面板来操作,还有没有更好的办法?
基于com.android.settings 利用activity弹dialog实现分析:

<activity android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" android:name="com.android.settings.wifi.RequestToggleWiFiActivity" android:permission="android.permission.CHANGE_WIFI_STATE" android:exported="true" android:excludeFromRecents="true">
     <intent-filter>
            <action android:name="android.net.wifi.action.REQUEST_ENABLE"/>
            <action android:name="android.net.wifi.action.REQUEST_DISABLE"/>
            <category android:name="android.intent.category.DEFAULT"/>
     </intent-filter>
</activity>
 
 com.android.settings.wifi.RequestToggleWiFiActivity
         
  protected void onCreate(Bundle bundle) { 
        super.onCreate(bundle);
        getWindow().addSystemFlags(524288);
        this.mWiFiManager = (WifiManager) getSystemService(WifiManager.class);
        setResult(0);
        String stringExtra = getIntent().getStringExtra("android.intent.extra.PACKAGE_NAME"); // 需要传packageName
        if (TextUtils.isEmpty(stringExtra)) {
            finish();
            return;
        }
        try {
            this.mAppLabel = getPackageManager().getApplicationInfo(stringExtra, 0).loadSafeLabel(getPackageManager(), 1000.0f, 5);
            String action = getIntent().getAction();
            action.hashCode();
            // 接收action,更新mState。
            if (action.equals("android.net.wifi.action.REQUEST_ENABLE")) {
                this.mState = 1;
            } else if (action.equals("android.net.wifi.action.REQUEST_DISABLE")) {
                this.mState = 3;
            } else {
                finish();
            }
        } catch (PackageManager.NameNotFoundException unused) {
            Log.e("RequestToggleWiFiActivity", "Couldn't find app with package name " + stringExtra);
            finish();
        }
    }

    protected void onStart() {
        ...
        updateUi(); //拉起Dialog,根据mState触发mWiFiManager.setWifiEnabled,
    }

代码实现:

WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, "com.android.settings"); // 必要条件
String action = wifiManager.isWifiEnabled() ? "android.net.wifi.action.REQUEST_DISABLE" : "android.net.wifi.action.REQUEST_ENABLE";
intent.setAction(action);
context.startActivity(intent);

效果:


©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容