在 Android API 级别 28 及以上连接时如何获取 SSID、频段等 Wifi AP 详细信息?

How to get Wifi AP details like SSID, Band, etc when connected in Android API level 28 and above?

我正在尝试获取 Wifi AP 详细信息。我正在使用以下代码

    private val TAG = "[MainActivity]"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

         val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                if(WifiManager.NETWORK_STATE_CHANGED_ACTION == intent?.action){
                    val networkInfo = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO)
                    if(ConnectivityManager.TYPE_WIFI == networkInfo.type){
                        val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
                        val info = wifiManager.connectionInfo
                        Log.d(TAG, "info = $info")
                    }
                }
            }
        }

        val intentFilter = IntentFilter()
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
        applicationContext.registerReceiver(receiver, intentFilter)
    }

NetworkInfo 和 ConnectivityManager.TYPE_WIFI 显示为已弃用。另外,我得到的 SSID 为空。

info = SSID: , BSSID: 02:00:00:00:00:00, MAC: 02:00:00:00:00:00, Supplicant state: COMPLETED

我使用的权限:

    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

完成上述任务的最佳方法是什么?

您必须拥有位置权限才能获取 WiFi AC 详细信息(参见 Location Requirements for WiFi APIs)。

在您的清单中声明 ACCESS_FINE_LOCATION 不足以获取 WiFi 信息 - 用户必须授予您该权限(通过在运行时向用户请求权限或由用户手动打开权限在应用信息中)。

此外,在 Android 10 中,google 添加了 enforcement that prevents you from getting the permission in the background,因此请记住,只有当您的请求 ACCESS_FINE_LOCATION 才允许您获取 WiFi AC 详细信息应用程序在前台。

要在后台获取 WiFi AC 详细信息,您必须声明(并被授予)ACCESS_BACKGROUND_LOCATION 以及常规位置权限。

@Shlomi 回答的补充,

下面是运行时获取用户权限的示例

Perform a check before performing operation

if (!checkPermissions()) {
    // App does not have permissions, ask for permissions.
    requestPermissions()
} else {
    // App has permissions
    // Perform your operations here......
}

Code to check and ask permissions

Kotlin

/**
 * Return the current state of the permissions needed.
 */
private fun checkPermissions(): Boolean {
   
    val permissionState = ActivityCompat.checkSelfPermission(
            requireContext(),
            Manifest.permission.ACCESS_FINE_LOCATION
    )
    return permissionState == PackageManager.PERMISSION_GRANTED
}

private fun requestPermissions() {
    val shouldProvideRationale = ActivityCompat.shouldShowRequestPermissionRationale(
            requireActivity(),
            Manifest.permission.ACCESS_FINE_LOCATION
    )

    // Provide an additional rationale to the user. This would happen if the user denied the
    // request previously, but didn't check the "Don't ask again" checkbox.
    if (shouldProvideRationale) {
        Log.i(
                TAG,
                "Displaying permission rationale to provide additional context."
        )
        Snackbar.make(
                binding.wifiConnectionFragment,
                "Location permission is needed for core functionality",
                Snackbar.LENGTH_INDEFINITE
        )
            .setAction("OK") { // Request permission
                ActivityCompat.requestPermissions(
                        requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                        REQUEST_PERMISSIONS_REQUEST_CODE
                )
            }
            .show()
    } else {
        Log.i(TAG, "Requesting permission")
        // Request permission. It's possible this can be auto answered if device policy
        // sets the permission in a given state or the user denied the permission
        // previously and checked "Never ask again".
        ActivityCompat.requestPermissions(
                requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_PERMISSIONS_REQUEST_CODE
        )
    }
}

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
        if (grantResults.isEmpty()) {

        } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission Granted
            // Perform your operations
        } else {
            // Permission denied.

            // Notify the user via a SnackBar that they have rejected a core permission for the
            // app, which makes the Activity useless. In a real app, core permissions would
            // typically be best requested during a welcome-screen flow.

            // Additionally, it is important to remember that a permission might have been
            // rejected without asking the user for permission (device policy or "Never ask
            // again" prompts). Therefore, a user interface affordance is typically implemented
            // when permissions are denied. Otherwise, your app could appear unresponsive to
            // touches or interactions which have required permissions.
            Snackbar.make(
                    binding.wifiConnectionFragment,
                    "Permission was denied, but is needed for core functionality.",
                    Snackbar.LENGTH_INDEFINITE
            )
                .setAction(
                        "Settings",
                        View.OnClickListener { // Build intent that displays the App settings screen.
                            val intent = Intent()
                            intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                            val uri = Uri.fromParts(
                                    "package",
                                    BuildConfig.APPLICATION_ID, null
                            )
                            intent.data = uri
                            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                            startActivity(intent)
                        })
                .show()
        }
    }
}