正常情況下,我們按照下麵的步驟操作即可進入Android的開發者模式(大部分安卓手機進入的方式都類似): 打開手機的設置,點擊最下麵的關於手機。 點擊這裡的“HarmonyOS版本”。連續點擊多次(我的手機是7次),然後會彈出需要輸入密碼解屏。解鎖之後屏幕上會提示“您正處於開發者模式!”。 返回到上 ...
最近負責的一個簡單定製化的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));
}
}
};