实战项目:自定义 Wi-Fi 管理 App、Service 层扩展、系统级调试

终于到了这个系列的最后一战。说实话,前面讲了那么多 Wi-Fi 框架、扫描流程、连接机制,都是为这一刻做铺垫。今天我们要亲手打造一个完整的自定义 Wi-Fi 管理 App,并且深入到 Service 层做扩展,最后还要上系统级调试。

这个项目我去年在内部培训时带团队做过一次。当时有个同事问我:“为什么不用系统自带的 Wi-Fi 设置?” 我笑了笑说:“你想想看,如果我们要做企业级的 Wi-Fi 管控、黑白名单、信号热力图,系统自带的那个能行吗?” 答案显然是否定的。

项目整体架构

我们先画一张图,把整个项目的脉络理清楚。这张图我建议你保存下来,后面写代码时反复对照。

自定义 Wi-Fi 管理 App 架构图 自定义 Wi-Fi App (UI 层) Wi-Fi Service 扩展层 (自定义 Service) Android Wi-Fi Framework (WifiManager / WifiService) Wi-Fi HAL + 驱动层 (wpa_supplicant / vendor HAL) 系统级调试入口 adb shell dumpsys wifi logcat -s WifiService Systrace / Perfetto Service 扩展点 自定义扫描策略 黑白名单过滤 连接优先级管理 信号质量上报

从图上你能看到,我们的 App 在最上层,中间是我们自定义的 Service 扩展层,再往下是 Android 原生的 Wi-Fi Framework。为什么要中间插一层?说白了,就是为了解耦。我不希望 App 直接操作 WifiManager,那样后期维护会非常痛苦。

第一步:搭建自定义 Wi-Fi App 骨架

我个人习惯先搭一个干净的 App 骨架,把权限、依赖、基础 UI 都准备好。别急着写业务逻辑,先把地基打牢。

核心权限清单(AndroidManifest.xml 中必须声明):

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Android 12+ 需要额外声明 -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

注意:Android 12 以后,Wi-Fi 扫描权限和位置权限是绑定的。我曾经有个项目上线后才发现,部分手机在关闭定位时无法扫描到任何 AP。嗯,这个坑我替你们踩过了。

App 的主界面我建议用 RecyclerView 展示扫描结果,顶部放一个开关按钮控制 Wi-Fi 的开启和关闭。代码结构大概这样:

// MainActivity.java - 核心逻辑
public class MainActivity extends AppCompatActivity {
    private WifiManager wifiManager;
    private BroadcastReceiver scanReceiver;
    private RecyclerView apList;
    private WifiAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        wifiManager = (WifiManager) getApplicationContext()
            .getSystemService(Context.WIFI_SERVICE);
        
        initUI();
        registerScanReceiver();
    }

    private void startScan() {
        // 触发扫描
        boolean success = wifiManager.startScan();
        if (!success) {
            // 我遇到过扫描频率过快被系统拒绝的情况
            // 建议加一个 10 秒的冷却时间
            showToast("扫描太频繁,请稍后再试");
        }
    }
}

第二步:Service 层扩展——自定义 Wi-Fi Service

App 层搞定了,接下来是重头戏。我们要写一个自定义的 Wi-Fi Service,运行在后台,负责管理扫描策略、连接逻辑和黑白名单。

为什么要单独写一个 Service?你想想看,如果 App 被用户划掉了,扫描和连接逻辑就全断了。用 Service 可以保证后台持续运行。当然,Android 8 以后的后台限制越来越严,这个我们后面再说。

// CustomWifiService.java - 自定义 Wi-Fi 后台服务
public class CustomWifiService extends Service {
    private static final String TAG = "CustomWifiService";
    private WifiManager wifiManager;
    private Handler scanHandler;
    private static final long SCAN_INTERVAL = 30_000L; // 30秒扫描一次

    @Override
    public void onCreate() {
        super.onCreate();
        wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        scanHandler = new Handler(Looper.getMainLooper());
        
        // 启动周期性扫描
        startPeriodicScan();
    }

    private void startPeriodicScan() {
        scanHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (wifiManager.isWifiEnabled()) {
                    wifiManager.startScan();
                    // 扫描结果通过 BroadcastReceiver 回调给 App
                }
                // 继续下一轮
                scanHandler.postDelayed(this, SCAN_INTERVAL);
            }
        }, 0);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new CustomWifiBinder();
    }

    // Binder 提供给 App 调用
    public class CustomWifiBinder extends Binder {
        public CustomWifiService getService() {
            return CustomWifiService.this;
        }
    }
}

个人经验:扫描间隔不要低于 15 秒。我曾经设了 10 秒,结果在部分高通平台上直接触发扫描节流(throttling),扫描结果全是空的。后来改成 30 秒,稳如老狗。

第三步:黑白名单与连接优先级

这是企业级 Wi-Fi 管理的核心需求。比如公司内部网络,只允许连接特定的 SSID,或者某些信号弱的 AP 要自动忽略。

我实现了一个简单的黑白名单管理器,用 SharedPreferences 存储名单列表:

// WifiFilterManager.java - 黑白名单管理
public class WifiFilterManager {
    private static final String PREF_WHITELIST = "wifi_whitelist";
    private static final String PREF_BLACKLIST = "wifi_blacklist";
    private SharedPreferences prefs;

    public WifiFilterManager(Context context) {
        prefs = context.getSharedPreferences("wifi_filter", Context.MODE_PRIVATE);
    }

    public boolean isAllowed(String ssid) {
        Set<String> whitelist = prefs.getStringSet(PREF_WHITELIST, new HashSet<>());
        Set<String> blacklist = prefs.getStringSet(PREF_BLACKLIST, new HashSet<>());
        
        // 如果白名单不为空,只允许白名单中的 SSID
        if (!whitelist.isEmpty() && !whitelist.contains(ssid)) {
            return false;
        }
        // 黑名单优先级更高
        if (blacklist.contains(ssid)) {
            return false;
        }
        return true;
    }

    public void addToWhitelist(String ssid) {
        Set<String> whitelist = new HashSet<>(
            prefs.getStringSet(PREF_WHITELIST, new HashSet<>()));
        whitelist.add(ssid);
        prefs.edit().putStringSet(PREF_WHITELIST, whitelist).apply();
    }
}

连接优先级这块,我建议用信号强度 + 历史连接成功率做加权排序。举个例子:

SSID 信号强度 (dBm) 历史成功率 加权得分
Office-5G -55 95% 92.5
Office-2.4G -40 80% 80.0
Guest-Net -70 60% 55.0

加权公式我一般用:得分 = 信号强度归一化值 × 0.6 + 成功率 × 0.4。这样既照顾了信号好的 AP,也考虑了稳定性。

第四步:系统级调试——真刀真枪干

代码写完了,怎么验证?光靠 Log 是不够的。我总结了一套调试三板斧:

  1. dumpsys 大法adb shell dumpsys wifi 可以看到当前 Wi-Fi 状态、扫描缓存、连接记录。我经常用它来确认 Service 层的扫描策略是否生效。
  2. logcat 过滤adb logcat -s CustomWifiService WifiService WifiScanning 只关注我们关心的 TAG,避免被海量日志淹没。
  3. Systrace / Perfetto:当怀疑性能问题时,抓一份 systrace 看看扫描线程是否卡顿,Binder 调用是否频繁。

调试小技巧:我曾经遇到一个诡异的问题——App 能扫描到 AP 但就是连不上。用 dumpsys 一看,发现目标 AP 的 BSSID 被系统标记为 "disabled"。原因是之前连接失败次数太多,系统自动把它拉黑了。清除 Wi-Fi 设置里的“已保存网络”即可恢复。

还有一个调试利器是 adb shell cmd wifi 命令。Android 10 以后,这个命令可以模拟各种 Wi-Fi 场景:

# 强制触发扫描
adb shell cmd wifi start-scan

# 设置 Wi-Fi 为禁用状态(模拟飞行模式)
adb shell cmd wifi set-wifi-enabled disabled

# 查看当前连接的详细状态
adb shell cmd wifi status

避坑指南——我踩过的几个坑

做这个项目时,我遇到了不少问题。挑几个典型的分享给你:

  • 扫描结果为空:检查是否在 Android 12+ 上申请了 NEARBY_WIFI_DEVICES 权限,同时位置服务必须开启。
  • Service 被系统杀死:Android 8 以后,后台 Service 限制很严。建议使用 startForegroundService() 配合前台通知,或者改用 WorkManager 做周期性扫描。
  • 连接回调不触发WifiManageraddNetwork()enableNetwork() 在 Android 10 以后行为有变化。建议改用 WifiNetworkSpecifierConnectivityManagerrequestNetwork()

最后说一句:系统级调试不是一次性的工作。我建议你在开发过程中就养成随时抓 log、随时看 dumpsys 的习惯。等到上线后再发现问题,那代价就大了。

好了,这个实战项目到这里就结束了。从 App 层到 Service 层,再到系统级调试,我们完整地走了一遍。希望你能把这些思路用到自己的项目中去。


公众号:蓝海资料掘金营,微信deep3321