初识Setting 应用WIFI设置
最近负责的一个简单定制化的setting,需要学习Wifi这一块方面的内容。通过这篇文章来了解一下原生的Setting 处理Wifi 的方式。有错误也希望大家提出来,我改进!
使用步骤
- 申请权限、获取系统服务 WifiManager。
- 通过 wifiManager.startScan(); 扫描WiFi 列表 。注意这个动作是耗时的
- 注册广播获取wifi扫描结果
简单用法
示例代码
下面是Android Studio Bito 生成的一个简单的示例代码,展示如何搜索Wi-Fi并使用列表展示。
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int PERMISSIONS_REQUEST_CODE = 100;
private WifiManager wifiManager;
private List<ScanResult> wifiList;
private ListView listView;
private Button scanButton;
private WifiScanReceiver wifiScanReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.listView);
scanButton = findViewById(R.id.scanButton);
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
scanButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scanWifi();
}
});
wifiScanReceiver = new WifiScanReceiver();
}
private void scanWifi() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_CODE);
} else {
performWifiScan();
}
}
private void performWifiScan() {
registerReceiver(wifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
wifiManager.startScan();
}
private class WifiScanReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
wifiList = wifiManager.getScanResults();
if (wifiList != null) {
List<String> wifiNames = new ArrayList<>();
for (ScanResult scanResult : wifiList) {
wifiNames.add(scanResult.SSID);
}
WifiListAdapter adapter = new WifiListAdapter(MainActivity.this, wifiNames);
listView.setAdapter(adapter);
} else {
Toast.makeText(MainActivity.this, "No Wi-Fi networks found", Toast.LENGTH_SHORT).show();
}
}
unregisterReceiver(this);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
performWifiScan();
} else {
Toast.makeText(this, "Permission denied. Unable to scan Wi-Fi networks.", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (wifiScanReceiver != null) {
unregisterReceiver(wifiScanReceiver);
}
}
}
ScanResult
是Android中的一个类,用于表示Wi-Fi扫描的结果。当您使用Wi-Fi功能进行扫描时,将返回一个ScanResult对象的列表,每个对象表示一个扫描到的Wi-Fi网络。ScanResult类提供了一些有用的信息,可以帮助您获取和分析附近的Wi-Fi网络。以下是一些ScanResult类提供的常用信息:
- SSID:表示Wi-Fi网络的名称。
- BSSID:表示Wi-Fi网络的MAC地址。
- level:表示Wi-Fi信号的强度,以dBm为单位。
- frequency:表示Wi-Fi信号的频率。
- capabilities:表示Wi-Fi网络的安全和认证功能。
- timestamp:表示扫描结果的时间戳。
- channelWidth:表示Wi-Fi信号的通道宽度。
- centerFreq0和centerFreq1:表示Wi-Fi信号的中心频率。
- is80211mcResponder:表示Wi-Fi网络是否支持802.11mc(Wi-Fi Round-Trip Time)协议。
等等。
Setting 源码分析
平常可以在这里看Android 源码 http://aospxref.com/ (偶尔可能访问不了)
这里是Android 13 http://aospxref.com/android-13.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/wifi/WifiSettings.java 不同的系统版本可能不一样,应该是大差不差的。
红色圈住的部分比较重要。WifiPickerTracker 是什么,接着走进去发现又继承了BaseWifiTracker。可以直接看这个基类。
BaseWifiTracker
构造方法
传参注册生命周期
/**
* Constructor for BaseWifiTracker.
* @param wifiTrackerInjector injector for commonly referenced objects.
* @param lifecycle Lifecycle this is tied to for lifecycle callbacks.
* @param context Context for registering broadcast receiver and for resource strings.
* @param wifiManager Provides all Wi-Fi info.
* @param connectivityManager Provides network info.
* @param mainHandler Handler for processing listener callbacks.
* @param workerHandler Handler for processing all broadcasts and running the Scanner.
* @param clock Clock used for evaluating the age of scans
* @param maxScanAgeMillis Max age for tracked WifiEntries.
* @param scanIntervalMillis Interval between initiating scans.
*/
BaseWifiTracker(
@NonNull WifiTrackerInjector injector,
@NonNull Lifecycle lifecycle, @NonNull Context context,
@NonNull WifiManager wifiManager,
@NonNull ConnectivityManager connectivityManager,
@NonNull Handler mainHandler,
@NonNull Handler workerHandler,
@NonNull Clock clock,
long maxScanAgeMillis,
long scanIntervalMillis,
BaseWifiTrackerCallback listener,
StLifecyclering tag) {
mInjector = injector;
lifecycle.addObserver(this); //这里注册了生命周期Lifecycle
mContext = context;
mWifiManager = wifiManager;
mConnectivityManager = connectivityManager;
mMainHandler = mainHandler; //主线程处理监听器回调。
mWorkerHandler = workerHandler; //处理所有广播和运行扫描器的处理程序。
mMaxScanAgeMillis = maxScanAgeMillis; //设定ScanResult的最大存活时间
mScanIntervalMillis = scanIntervalMillis; //启动WifiPickerTracker扫描的间隔时间
mListener = listener;
mTag = tag;
mScanResultUpdater = new ScanResultUpdater(clock,
maxScanAgeMillis + scanIntervalMillis);
mScanner = new BaseWifiTracker.Scanner(workerHandler.getLooper());
sVerboseLogging = mWifiManager.isVerboseLoggingEnabled();
updateDefaultRouteInfo();
}
Scanner
扫描WiFi动作
@WorkerThread
private class Scanner extends Handler {
private static final int SCAN_RETRY_TIMES = 3; //扫描WiFi 最大失败次数
private int mRetry = 0;
private boolean mIsActive;
private Scanner(Looper looper) {
super(looper);
}
private void start() {
Log.d("TAG", "start: 疑问不知道哪里发的广播????");
if (!mIsActive) {
mIsActive = true;
if (isVerboseLoggingEnabled()) {
Log.v(mTag, "Scanner start");
}
postScan();
}
}
private void stop() {
mIsActive = false;
if (isVerboseLoggingEnabled()) {
Log.v(mTag, "Scanner stop");
}
mRetry = 0;
removeCallbacksAndMessages(null);
}
//一直重复扫描WiFi设备。
private void postScan() {
if (mWifiManager.startScan()) {
mRetry = 0;
} else if (++mRetry >= SCAN_RETRY_TIMES) { //失败3次跳出循环
// TODO(b/70983952): See if toast is needed here
if (isVerboseLoggingEnabled()) {
Log.v(mTag, "Scanner failed to start scan " + mRetry + " times!");
}
mRetry = 0;
return;
}
postDelayed(this::postScan, mScanIntervalMillis);
}
}
ScanResultUpdater
主要更新扫描结果
package com.android.wifitrackerlib;
import android.net.wifi.ScanResult;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.time.Clock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Utility class to keep a running list of scan results merged by SSID+BSSID pair.
*
* Thread-safe.
*/
public class ScanResultUpdater {
private Map<Pair<String, String>, ScanResult> mScanResultsBySsidAndBssid = new HashMap<>();
private final long mMaxScanAgeMillis;
private final Object mLock = new Object();
private final Clock mClock;
/**
* Creates a ScanResultUpdater with no max scan age.
*
* @param clock Elapsed real time Clock to compare with ScanResult timestamps.
*/
public ScanResultUpdater(Clock clock) {
this(clock, Long.MAX_VALUE);
}
/**
* Creates a ScanResultUpdater with a max scan age in milliseconds. Scans older than this limit
* will be pruned upon update/retrieval to keep the size of the scan list down.
*/
public ScanResultUpdater(Clock clock, long maxScanAgeMillis) {
mMaxScanAgeMillis = maxScanAgeMillis;
mClock = clock;
}
/**
* Updates scan result list and replaces older scans of the same SSID+BSSID pair.
* 更新扫描结果列表并替换相同SSID+BSSID对的旧扫描。
*/
public void update(@NonNull List<ScanResult> newResults) {
synchronized (mLock) {
evictOldScans();
for (ScanResult result : newResults) {
final Pair<String, String> key = new Pair(result.SSID, result.BSSID);
ScanResult prevResult = mScanResultsBySsidAndBssid.get(key);
if (prevResult == null || (prevResult.timestamp < result.timestamp)) {
mScanResultsBySsidAndBssid.put(key, result);
}
}
}
}
/**
* Returns all seen scan results merged by SSID+BSSID pair.
*/
@NonNull
public List<ScanResult> getScanResults() {
return getScanResults(mMaxScanAgeMillis);
}
/**
* Returns all seen scan results merged by SSID+BSSID pair and newer than maxScanAgeMillis.
* maxScanAgeMillis must be less than or equal to the mMaxScanAgeMillis field if it was set.
*/
@NonNull
public List<ScanResult> getScanResults(long maxScanAgeMillis) throws IllegalArgumentException {
if (maxScanAgeMillis > mMaxScanAgeMillis) {
throw new IllegalArgumentException(
"maxScanAgeMillis argument cannot be greater than mMaxScanAgeMillis!");
}
synchronized (mLock) {
List<ScanResult> ageFilteredResults = new ArrayList<>();
for (ScanResult result : mScanResultsBySsidAndBssid.values()) {
if (mClock.millis() - result.timestamp / 1000 <= maxScanAgeMillis) {
ageFilteredResults.add(result);
}
}
return ageFilteredResults;
}
}
//清除旧的扫描结果
//即扫描结果的时间戳距离当前时间超过了设定的最大扫描时间,那么该扫描结果会被移除
private void evictOldScans() {
synchronized (mLock) {
mScanResultsBySsidAndBssid.entrySet().removeIf((entry) ->
mClock.millis() - entry.getValue().timestamp / 1000 > mMaxScanAgeMillis);
}
}
}
生命周期
- Lifecycle.Event.ON_START 在onStart 中注册广播接收器处理网络回调
- Lifecycle.Event.ON_STOP 取消广播接收器的注册,网络回调,并暂停扫描机制。
/**
* Registers the broadcast receiver and network callbacks and starts the scanning mechanism.
* 注册广播接收器和网络回调,并启动扫描机制。
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
@MainThread
public void onStart() {
mWorkerHandler.post(() -> {
updateDefaultRouteInfo();
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter,
/* broadcastPermission */ null, mWorkerHandler);
mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
mWorkerHandler);
NonSdkApiWrapper.registerSystemDefaultNetworkCallback(
mConnectivityManager, mDefaultNetworkCallback, mWorkerHandler);
handleOnStart();
mIsInitialized = true;
});
}
/**
* Unregisters the broadcast receiver, network callbacks, and pauses the scanning mechanism.
* 取消广播接收器的注册,网络回调,并暂停扫描机制。
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@MainThread
public void onStop() {
mWorkerHandler.post(() -> {
mScanner.stop();
mContext.unregisterReceiver(mBroadcastReceiver);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
});
}
/**
*广播接收器处理网络回调
1、WiFi状态改变 WIFI_STATE_CHANGED_ACTION
2、扫描结果 SCAN_RESULTS_AVAILABLE_ACTION
3、Wi-Fi 信号强度变化 RSSI_CHANGED_ACTION
4、配置的网络发生变化 CONFIGURED_NETWORKS_CHANGED_ACTION
5、网络状态改变 NETWORK_STATE_CHANGED_ACTION
*/
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@WorkerThread
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (isVerboseLoggingEnabled()) {
Log.v(mTag, "Received broadcast: " + action);
}
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
mWifiState = intent.getIntExtra(
WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
if (mWifiState == WifiManager.WIFI_STATE_ENABLED) {
mScanner.start();
} else {
mScanner.stop();
}
notifyOnWifiStateChanged();
handleWifiStateChangedAction();
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
handleScanResultsAvailableAction(intent);
} else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)) {
handleConfiguredNetworksChangedAction(intent);
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
handleNetworkStateChangedAction(intent);
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
handleRssiChangedAction();
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
handleDefaultSubscriptionChanged(intent.getIntExtra(
"subscription", SubscriptionManager.INVALID_SUBSCRIPTION_ID));
}
}
};