在Android 16(API 36)设备上开发WiFi相关功能时,很多开发者会遇到一个典型问题:通过WifiManager.getConnectionInfo()获取当前连接的WiFi信息时,返回的SSID显示为"
我最近在一个智能家居项目中就遇到了这个棘手问题。当时需要根据用户连接的WiFi网络自动切换设备配置,但在Android 16设备上始终无法正确获取SSID。经过两天排查和测试,终于找到了完整的解决方案。下面我将分享这个问题的技术细节和实战经验。
Android系统的权限管理经历了多次重大变革:
获取WiFi信息需要同时考虑网络权限和位置权限,这是因为SSID和BSSID可以被用来推断设备的位置信息。
在AndroidManifest.xml中需要声明以下权限组:
xml复制<!-- 基础网络权限 -->
<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_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Android 12+ 特殊权限 -->
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<!-- Android 13+ 新增权限 -->
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
重要提示:从Android 10开始,即使已经声明了ACCESS_FINE_LOCATION权限,如果应用在后台运行,仍然无法获取WiFi信息。这是Google加强隐私保护的措施。
对于Android 13及以上版本,需要特别处理NEARBY_WIFI_DEVICES权限。这个权限的设计初衷是让应用可以发现附近的WiFi设备而不需要获取位置信息。
java复制private void handleAndroid13Permissions() {
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.NEARBY_WIFI_DEVICES);
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.NEARBY_WIFI_DEVICES)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
permissions.toArray(new String[0]),
REQUEST_CODE_WIFI_PERMISSIONS);
} else {
// 权限已授予,执行WiFi操作
fetchWifiInfo();
}
}
Android 12引入了BLUETOOTH_SCAN权限,用于限制对蓝牙设备的扫描,这也间接影响了WiFi扫描功能。
java复制private void handleAndroid12Permissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.BLUETOOTH_SCAN},
REQUEST_CODE_WIFI_PERMISSIONS);
} else {
fetchWifiInfo();
}
}
对于较旧的Android版本,主要需要处理位置权限:
java复制private void handleLegacyPermissions() {
String[] permissions = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
};
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
permissions,
REQUEST_CODE_WIFI_PERMISSIONS);
} else {
fetchWifiInfo();
}
}
获取WiFi连接信息时,应该采用多层验证机制:
java复制public WifiNetworkInfo getCurrentWifiInfo() {
WifiNetworkInfo info = new WifiNetworkInfo();
// 1. 检查权限
if (!checkWifiPermissions()) {
info.setError("Missing required permissions");
return info;
}
// 2. 获取WifiManager服务
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
if (wifiManager == null) {
info.setError("WifiManager not available");
return info;
}
// 3. 获取连接信息
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo == null) {
info.setError("No active WiFi connection");
return info;
}
// 4. 验证连接状态
if (wifiInfo.getSupplicantState() != SupplicantState.COMPLETED) {
info.setError("WiFi not fully connected");
return info;
}
// 5. 处理SSID
String ssid = wifiInfo.getSSID();
if (ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
// 6. 最终验证
if (ssid == null || ssid.isEmpty() || ssid.equals("<unknown ssid>")) {
if (isActuallyConnectedViaWifi()) {
ssid = "Hidden Network";
} else {
info.setError("No valid SSID available");
return info;
}
}
info.setSsid(ssid);
info.setBssid(wifiInfo.getBSSID());
info.setRssi(wifiInfo.getRssi());
info.setIpAddress(wifiInfo.getIpAddress());
return info;
}
即使获取到了SSID,也应该验证设备是否真的通过WiFi连接到互联网:
java复制private boolean isActuallyConnectedViaWifi() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) return false;
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null
&& networkInfo.isConnected()
&& networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
当用户拒绝权限请求时,应该优雅地处理这种情况:
java复制@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_WIFI_PERMISSIONS) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
fetchWifiInfo();
} else {
// 解释为什么需要这些权限
showPermissionRationale();
}
}
}
从Android 10开始,应用在后台运行时获取WiFi信息会受到限制。解决方法:
java复制// 在Activity的onResume中获取WiFi信息
@Override
protected void onResume() {
super.onResume();
if (checkWifiPermissions()) {
fetchWifiInfo();
}
}
某些厂商的定制ROM可能会有额外的限制:
应对策略:
java复制public void checkManufacturerSpecificSettings() {
String manufacturer = Build.MANUFACTURER.toLowerCase();
if (manufacturer.contains("xiaomi")) {
// 引导用户到小米的特殊设置页面
showXiaomiGuide();
} else if (manufacturer.contains("huawei")) {
// 华为电池优化指引
showHuaweiGuide();
}
// 其他厂商处理...
}
频繁获取WiFi信息会影响性能,建议适当缓存:
java复制private WifiNetworkInfo mCachedWifiInfo;
private long mLastUpdateTime;
public WifiNetworkInfo getCachedWifiInfo() {
// 5秒缓存有效期
if (mCachedWifiInfo == null ||
System.currentTimeMillis() - mLastUpdateTime > 5000) {
mCachedWifiInfo = getCurrentWifiInfo();
mLastUpdateTime = System.currentTimeMillis();
}
return mCachedWifiInfo;
}
与其轮询检查,不如注册广播接收器监听网络变化:
java复制private final BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (info != null && info.isConnected()) {
// 网络状态变化,更新WiFi信息
fetchWifiInfo();
}
}
}
};
// 注册接收器
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
registerReceiver(mWifiReceiver, filter);
有些特殊场景需要特别注意:
增强型SSID处理方法:
java复制public String sanitizeSsid(String ssid) {
if (ssid == null) return "";
// 处理引号包裹的情况
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
// 处理特殊转义字符
ssid = ssid.replace("\\", "");
// 处理不可见字符
ssid = ssid.replaceAll("[^\\x20-\\x7e]", "");
return ssid.trim();
}
为确保在各种设备上都能正常工作,建议测试以下场景:
测试用例示例:
java复制@Test
public void testWifiInfoOnVariousDevices() {
// 模拟不同Android版本
for (int apiLevel = Build.VERSION_CODES.O; apiLevel <= Build.VERSION.SDK_INT; apiLevel++) {
ShadowBuild.setSdkInt(apiLevel);
WifiNetworkInfo info = wifiHelper.getCurrentWifiInfo();
assertFalse(info.getSsid().equals("<unknown ssid>"));
}
}
如果经过上述所有方法仍然无法获取SSID,可以考虑以下替代方案:
Android 7.0引入了NetworkCapabilities API,可以提供另一种获取网络信息的方式:
java复制public String getNetworkSsidViaCapabilities() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) return null;
Network network = cm.getActiveNetwork();
if (network == null) return null;
NetworkCapabilities nc = cm.getNetworkCapabilities(network);
if (nc == null || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return null;
}
// 注意:这个方法不一定能获取到SSID
String ssid = (String) nc.getTransportInfo();
return ssid instanceof String ? (String) ssid : null;
}
极端情况下,可以通过创建原始socket来获取网络接口信息:
java复制public String getSsidViaNetworkInterface() {
try {
for (NetworkInterface intf : Collections.list(NetworkInterface.getNetworkInterfaces())) {
if (!intf.getName().startsWith("wlan")) continue;
byte[] macBytes = intf.getHardwareAddress();
if (macBytes == null) continue;
// 这里可以根据接口信息推断网络状态
return intf.getDisplayName(); // 可能包含SSID信息
}
} catch (Exception e) {
Log.e(TAG, "Error getting network interfaces", e);
}
return null;
}
注意:这种方法可靠性不高,且需要额外的权限,仅作为最后手段考虑。
在实现WiFi信息获取功能时,必须注意以下几点安全和隐私问题:
建议在代码中添加隐私检查:
java复制public boolean isPrivacyCompliant() {
// 检查是否在欧盟地区需要遵守GDPR
if (isInGdprRegion()) {
return hasUserConsentForWifiData();
}
return true;
}
在多个商业项目中实现WiFi功能后,我总结了以下宝贵经验:
示例用户引导实现:
java复制private void showPermissionGuide() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("需要权限");
builder.setMessage("为了提供WiFi相关功能,需要授予位置权限。我们不会收集您的位置数据,这个权限仅用于获取WiFi信息。");
builder.setPositiveButton("去设置", (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
});
builder.setNegativeButton("取消", null);
builder.show();
}
随着Android系统的持续更新,WiFi相关的API和权限要求可能会继续变化。建议:
未来兼容性代码示例:
java复制@RequiresApi(Build.VERSION_CODES.R)
private void prepareForFutureAndroidVersions() {
// Android 11+ 的新API示例
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
cm.registerNetworkCallback(
new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build(),
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 处理网络可用事件
}
});
}
当WiFi信息获取出现问题时,以下调试方法很有帮助:
adb shell dumpsys wifi获取详细的WiFi状态信息调试代码示例:
java复制public void dumpWifiDebugInfo() {
Log.d(TAG, "===== WiFi Debug Info =====");
Log.d(TAG, "Permissions:");
Log.d(TAG, "ACCESS_WIFI_STATE: " + checkPermission(Manifest.permission.ACCESS_WIFI_STATE));
Log.d(TAG, "ACCESS_FINE_LOCATION: " + checkPermission(Manifest.permission.ACCESS_FINE_LOCATION));
WifiManager wifiManager = getSystemService(WifiManager.class);
Log.d(TAG, "WiFi state: " + wifiManager.getWifiState());
Log.d(TAG, "Connection info: " + wifiManager.getConnectionInfo());
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
Log.d(TAG, "Active network: " + networkInfo);
}