You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 19:24:25