实战项目:自定义 Wi-Fi 管理 App、Service 层扩展、系统级调试
终于到了这个系列的最后一战。说实话,前面讲了那么多 Wi-Fi 框架、扫描流程、连接机制,都是为这一刻做铺垫。今天我们要亲手打造一个完整的自定义 Wi-Fi 管理 App,并且深入到 Service 层做扩展,最后还要上系统级调试。
这个项目我去年在内部培训时带团队做过一次。当时有个同事问我:“为什么不用系统自带的 Wi-Fi 设置?” 我笑了笑说:“你想想看,如果我们要做企业级的 Wi-Fi 管控、黑白名单、信号热力图,系统自带的那个能行吗?” 答案显然是否定的。
项目整体架构
我们先画一张图,把整个项目的脉络理清楚。这张图我建议你保存下来,后面写代码时反复对照。
从图上你能看到,我们的 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 是不够的。我总结了一套调试三板斧:
- dumpsys 大法:
adb shell dumpsys wifi可以看到当前 Wi-Fi 状态、扫描缓存、连接记录。我经常用它来确认 Service 层的扫描策略是否生效。 - logcat 过滤:
adb logcat -s CustomWifiService WifiService WifiScanning只关注我们关心的 TAG,避免被海量日志淹没。 - 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做周期性扫描。 - 连接回调不触发:
WifiManager的addNetwork()和enableNetwork()在 Android 10 以后行为有变化。建议改用WifiNetworkSpecifier或ConnectivityManager的requestNetwork()。
最后说一句:系统级调试不是一次性的工作。我建议你在开发过程中就养成随时抓 log、随时看 dumpsys 的习惯。等到上线后再发现问题,那代价就大了。
好了,这个实战项目到这里就结束了。从 App 层到 Service 层,再到系统级调试,我们完整地走了一遍。希望你能把这些思路用到自己的项目中去。
公众号:蓝海资料掘金营,微信deep3321