Android targetSDK30如何实现自定义WiFi扫描连接页面
问题原因说明
你的代码无法正常运行是因为没有适配Android高版本(尤其是targetSdkVersion=30对应Android 11)的WiFi API权限和行为变更,核心问题点如下:
- 仅申请了
ACCESS_COARSE_LOCATION权限:Android 10(API29)及以上版本必须申请ACCESS_FINE_LOCATION权限,且需要手动开启设备的位置服务开关,才能获取WiFi扫描结果,仅授予权限无法生效 - 直接调用
wifiManager.setWifiEnabled(true)开关WiFi:该方法从Android 10(API29)开始对非系统预装应用完全失效,调用会直接返回false,无法通过代码直接打开/关闭WiFi,必须引导用户到系统设置页手动操作 - 未处理扫描限流逻辑:Android 9(API28)及以上版本对WiFi扫描频率做了限制,前台应用2分钟内最多允许扫描4次,超过阈值后扫描请求会直接被丢弃,广播返回的扫描结果为旧缓存数据
- 注册广播未做判空:
onPostResume中每次都新建广播接收器实例注册,异常场景下会出现重复注册导致内存泄漏,甚至收不到扫描结果广播 - 未适配高版本WiFi连接逻辑:Android 10(API29)及以上版本普通应用无法直接通过
addNetwork、enableNetwork方法静默连接WiFi,必须通过WifiNetworkSpecifier发起系统级连接请求,经用户确认后完成连接
实现步骤
1. 清单文件添加所需权限
在AndroidManifest.xml中添加以下权限声明,适配不同版本的权限要求:
<!-- 基础WiFi权限 --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <!-- Android 8.0-11 扫描WiFi需要的位置权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="32" /> <!-- Android 12+ 扫描WiFi不需要位置权限,用附近设备权限替代 --> <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" android:minSdkVersion="31" />
同时在AndroidManifest.xml的<application>标签外添加Android TV相关配置,确保应用在TV设备正常运行:
<uses-feature android:name="android.hardware.wifi" android:required="true" /> <uses-feature android:name="android.software.leanback" android:required="true" />
2. 核心功能实现代码
WiFi扫描广播接收器
实现广播接收器,用于接收扫描结果通知、处理扫描成功/失败状态:
public class WifiScanReceiver extends BroadcastReceiver { private final WifiManager wifiManager; private final WifiListAdapter listAdapter; private final List<ScanResult> scanResults = new ArrayList<>(); public WifiScanReceiver(WifiManager wifiManager, WifiListAdapter listAdapter) { this.wifiManager = wifiManager; this.listAdapter = listAdapter; } @Override public void onReceive(Context context, Intent intent) { if (!WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) return; // 判断扫描是否成功,失败则使用缓存数据 boolean scanSuccess = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); if (scanSuccess) { scanResults.clear(); // 过滤掉空SSID、重复的WiFi热点 List<ScanResult> rawResults = wifiManager.getScanResults(); HashSet<String> existedSsid = new HashSet<>(); for (ScanResult result : rawResults) { if (TextUtils.isEmpty(result.SSID) || existedSsid.contains(result.SSID)) continue; existedSsid.add(result.SSID); scanResults.add(result); } // 按信号强度从强到弱排序 scanResults.sort((a, b) -> b.level - a.level); listAdapter.updateData(scanResults); } else { Toast.makeText(context, "WiFi扫描受限,展示最近一次扫描结果", Toast.LENGTH_SHORT).show(); } } }
WiFi列表页主逻辑
处理权限申请、扫描触发、WiFi状态判断、连接请求发起:
public class WifiActivity extends AppCompatActivity { private static final int REQUEST_PERMISSION_CODE = 1001; private static final int REQUEST_ENABLE_WIFI = 1002; private static final int REQUEST_ENABLE_LOCATION = 1003; private ListView wifiListView; private Button scanBtn; private WifiManager wifiManager; private WifiScanReceiver wifiScanReceiver; private WifiListAdapter wifiListAdapter; private ConnectivityManager connectivityManager; private ConnectivityManager.NetworkCallback networkCallback; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_wifi); wifiListView = findViewById(R.id.wifiList); scanBtn = findViewById(R.id.scanBtn); wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); wifiListAdapter = new WifiListAdapter(this); wifiListView.setAdapter(wifiListAdapter); // 检查WiFi是否开启 if (!wifiManager.isWifiEnabled()) { Toast.makeText(this, "请先开启WiFi", Toast.LENGTH_SHORT).show(); // 跳转系统WiFi设置页引导用户开启,无法直接通过代码打开 startActivityForResult(new Intent(Settings.ACTION_WIFI_SETTINGS), REQUEST_ENABLE_WIFI); } scanBtn.setOnClickListener(v -> checkPermissionAndScan()); // 列表项点击触发连接流程 wifiListView.setOnItemClickListener((parent, view, position, id) -> { ScanResult selectedWifi = wifiListAdapter.getItem(position); showPasswordInputDialog(selectedWifi); }); } private void checkPermissionAndScan() { List<String> needPermissions = new ArrayList<>(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED) { needPermissions.add(Manifest.permission.NEARBY_WIFI_DEVICES); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { needPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); } // 检查位置服务是否开启 LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { Toast.makeText(this, "请先开启位置服务", Toast.LENGTH_SHORT).show(); startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), REQUEST_ENABLE_LOCATION); return; } } if (!needPermissions.isEmpty()) { ActivityCompat.requestPermissions(this, needPermissions.toArray(new String[0]), REQUEST_PERMISSION_CODE); return; } // 触发扫描 boolean scanStart = wifiManager.startScan(); if (!scanStart) { Toast.makeText(this, "扫描触发失败,请稍后重试", Toast.LENGTH_SHORT).show(); } } @Override protected void onPostResume() { super.onPostResume(); // 注册广播前先判空,避免重复注册 if (wifiScanReceiver == null) { wifiScanReceiver = new WifiScanReceiver(wifiManager, wifiListAdapter); IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(wifiScanReceiver, filter, Context.RECEIVER_EXPORTED); } else { registerReceiver(wifiScanReceiver, filter); } // 进入页面自动触发一次扫描 checkPermissionAndScan(); } } @Override protected void onPause() { super.onPause(); // 注销广播避免内存泄漏 if (wifiScanReceiver != null) { unregisterReceiver(wifiScanReceiver); wifiScanReceiver = null; } // 注销网络连接回调 if (connectivityManager != null && networkCallback != null) { connectivityManager.unregisterNetworkCallback(networkCallback); networkCallback = null; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSION_CODE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { checkPermissionAndScan(); } else { Toast.makeText(this, "相关权限被拒绝,无法扫描WiFi", Toast.LENGTH_SHORT).show(); } } } // WiFi连接逻辑 private void connectToWifi(ScanResult wifi, String password) { WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder() .setSsid(wifi.SSID); // 判断加密类型设置密码 String capabilities = wifi.capabilities; if (capabilities.contains("WPA3") || capabilities.contains("WPA2") || capabilities.contains("WPA")) { builder.setWpa2Passphrase(password); } else if (capabilities.contains("WEP")) { builder.setWepPassphrase(password); } // 开放网络不需要设置密码 NetworkRequest request = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setNetworkSpecifier(builder.build()) .build(); networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(@NonNull Network network) { super.onAvailable(network); // 连接成功,绑定网络到当前进程 connectivityManager.bindProcessToNetwork(network); runOnUiThread(() -> Toast.makeText(WifiActivity.this, "WiFi连接成功", Toast.LENGTH_SHORT).show()); } @Override public void onUnavailable() { super.onUnavailable(); runOnUiThread(() -> Toast.makeText(WifiActivity.this, "WiFi连接失败,请检查密码", Toast.LENGTH_SHORT).show()); } }; connectivityManager.requestNetwork(request, networkCallback); } // 弹出密码输入框 private void showPasswordInputDialog(ScanResult wifi) { // 开放网络直接连接 if (!wifi.capabilities.contains("WEP") && !wifi.capabilities.contains("WPA") && !wifi.capabilities.contains("WPA2") && !wifi.capabilities.contains("WPA3")) { connectToWifi(wifi, ""); return; } // 加密网络弹出密码输入框 EditText pwdInput = new EditText(this); pwdInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); new AlertDialog.Builder(this) .setTitle("连接WiFi:" + wifi.SSID) .setView(pwdInput) .setPositiveButton("连接", (dialog, which) -> { String pwd = pwdInput.getText().toString().trim(); if (pwd.length() >= 8) { connectToWifi(wifi, pwd); } else { Toast.makeText(this, "密码长度不能少于8位", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消", null) .show(); } }
WiFi列表适配器
实现列表数据绑定,展示WiFi名称、加密类型、信号强度:
public class WifiListAdapter extends BaseAdapter { private final LayoutInflater inflater; private List<ScanResult> dataList = new ArrayList<>(); public WifiListAdapter(Context context) { inflater = LayoutInflater.from(context); } public void updateData(List<ScanResult> newData) { dataList.clear(); dataList.addAll(newData); notifyDataSetChanged(); } @Override public int getCount() { return dataList.size(); } @Override public ScanResult getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false); } TextView tvName = convertView.findViewById(android.R.id.text1); TextView tvInfo = convertView.findViewById(android.R.id.text2); ScanResult result = dataList.get(position); tvName.setText(result.SSID); // 显示信号强度和加密类型 String levelStr = "信号强度:" + WifiManager.calculateSignalLevel(result.level, 5) + "/5"; String encryptType = result.capabilities.contains("WPA3") ? "WPA3加密" : result.capabilities.contains("WPA2") ? "WPA2加密" : result.capabilities.contains("WPA") ? "WPA加密" : result.capabilities.contains("WEP") ? "WEP加密" : "开放网络"; tvInfo.setText(encryptType + " | " + levelStr); return convertView; } }
注意事项
- 部分TV厂商定制系统可能放宽位置权限要求,不需要开启位置服务即可扫描WiFi,需要针对具体机型做兼容处理
- 连接加密WiFi时系统会弹出确认连接的系统弹窗,TV端可以直接通过遥控器操作确认,无需额外处理焦点
- 扫描操作不要过于频繁,建议两次扫描间隔至少30秒以上,避免触发系统限流导致扫描失败
内容的提问来源于stack exchange,提问作者Sana
相关产品推荐
相关产品推荐

