初識Setting 應用WIFI設置

来源:https://www.cnblogs.com/mhzf/archive/2023/11/03/17807550.html
-Advertisement-
Play Games

正常情況下,我們按照下麵的步驟操作即可進入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));
            }
        }
    };

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 包括線程概念簡介;線程創建函數pthread_create以及退出、回收等;線程同步互斥鎖pthread_mutex_t,讀寫鎖pthread_rwlock_t,條件變數pthread_cond_t以及信號量semaphore ...
  • 本文分享自華為雲社區《GaussDB資料庫SQL系列-數據去重》,作者: Gauss松鼠會小助手2 。 一、前言 數據去重在資料庫中是比較常見的操作。複雜的業務場景、多業務線的數據來源等等,都會帶來重覆數據的存儲。本文以GaussDB資料庫為實驗平臺,將為大家詳細講解如何去重。 二、數據去重應用場景 ...
  • 本篇文章記錄最近ES做節點替換,從shard遷移過程中被鎖定導致無法分配,主shard正常,希望可以幫助其它人 failed to create shard,failed to obtain in-memory shard lock,ShardLockObtainFailedException 一、 ...
  • 設置進程記憶體(Process Memory) Apache Flink通過嚴格控制其各種組件的記憶體使用,在JVM之上提供高效的工作負載。 配置總記憶體(Total Memory) Flink JVM進程的總進程記憶體(total process memory)由Flink應用程式消耗的記憶體(總Flink ...
  • 本文是對這篇文章How the MySQL Optimizer Calculates the Cost of a Query (Doc ID 1327497.1)[1]的翻譯,翻譯如有不當的地方,敬請諒解,請尊重原創和翻譯勞動成果,轉載的時候請註明出處。謝謝! 適用於: MySQL 4.0 及後續更 ...
  • 本文分享自天翼雲開發者社區《一種Mysql和Mongodb數據同步到Elasticsearch的實現辦法和系統》,作者:l****n 核心流程如下: 核心邏輯說明: MySQL Binlog解析: 首先,從MySQL的二進位日誌(Binlog)中解析出表名。這一步驟非常關鍵,因為我們只關註特定表的數 ...
  • 在Oracle資料庫中,有時候需要根據實際情況調整redo log的大小,增加redo log的日誌組的數量,或者增加日誌組成員,以及redo log的size大小不合適時,需要調整redo log的大小(刪除redo log,重新添加redo log),一般情況下,都是手工寫腳本,如果多台資料庫實 ...
  • 本章將深入探討 HarmonyOS 應用開發的關鍵方面,包括應用的生命周期、數據存儲和網路訪問。瞭解這些內容對於創建功能豐富、高效的 HarmonyOS 應用至關重要。 ...
一周排行
    -Advertisement-
    Play Games
  • 當使用Autofac處理一個介面有多個實現的情況時,通常會使用鍵(key)進行區分或者通過IIndex索引註入,也可以通過IEnumerable集合獲取所有實例,以下是一個具體的例子,演示如何在Autofac中註冊多個實現,並通過構造函數註入獲取指定實現。 首先,確保你已經安裝了Autofac Nu ...
  • 本篇將分享Prometheus+Grafana的監控平臺搭建,並監控之前文章所搭建的主機&服務,分享日常使用的一些使用經驗本篇將配置常用服務的監控與面板配置:包括 MySQL,MongoDB,CLickHouse,Redis,RabbitMQ,Linux,Windows,Nginx,站點訪問監控,已... ...
  • 使用Aspirate可以將Aspire程式部署到Kubernetes 集群 工具安裝 dotnet tool install -g aspirate --prerelease 註意:Aspirate 正在開發中,該軟體包將作為預覽版進行版本控制,--prelease 選項將獲得最新的預覽版。 容器註 ...
  • 前言 本文要說的這種開發模式,這種模式並不是只有blazor支持,js中有一樣的方案next.js nuxt.js;blazor還有很多其它內容,本文近關註漸進式開發模式。 是的,前後端是主流,不過以下情況也許前後端分離並不是最好的選擇: 小公司,人員不多,利潤不高,創業階段能省則省 個人開發者,接 ...
  • 在.NET中,Microsoft.Extensions.Logging是一個靈活的日誌庫,它允許你將日誌信息記錄到各種不同的目標,包括資料庫。在這個示例中,我將詳細介紹如何使用Microsoft.Extensions.Logging將日誌保存到MySQL資料庫。我們將使用Entity Framewo ...
  • chatgpt介面開發筆記3: 語音識別介面 1.文本轉語音 1、瞭解介面參數 介面地址: POST https://api.openai.com/v1/audio/speech 下麵是介面文檔描述內容: 參數: { "model": "tts-1", "input": "你好,我是饒坤,我是ter ...
  • 前面兩篇文章主要是介紹瞭如何解決高併發情況下資源爭奪的問題。但是現實的應用場景中除了要解決資源爭奪問題,高併發的情況還需要解決更多問題,比如快速處理業務數據等, 本篇文章簡要羅列一下與之相關的更多技術細節。 1、非同步編程:使用async和await關鍵字進行非同步編程,這可以避免阻塞線程,提高程式的響 ...
  • 大家好,我是棧長。 Nacos 2.3.0 前幾天正式發佈了,新增了不少實用性的新功能,真是史上最強版本。 Nacos 2.3.0 還真是一個比較重要的大版本,因為它涉及了太多重大更新,今天棧長給大家來解讀下。 Nacos 先掃個盲: Nacos 一個用於構建雲原生應用的動態服務發現、配置管理和服務 ...
  • IDEA的遠程開發功能,可以將本地的編譯、構建、調試、運行等工作都放在遠程伺服器上執行,而本地僅運行客戶端軟體進行常規的開發操作即可,舊版本IDEA目前不支持該功能.,本例使用的是IDEA2023.2.5版本 下麵介紹如何在IDEA中設置遠程連接伺服器開發環境並結合Cpolar內網穿透工具實現無公網 ...
  • 本文解釋為啥會有響應式編程,為什麼它在開發者中不太受歡迎,以及引入 Java 虛擬線程後它可能最終會消失。 命令式風格編程一直深受開發者喜愛,如 if-then-else、while 迴圈、函數和代碼塊等結構使代碼易理解、調試,異常易追蹤。然而,像所有好的東西一樣,通常也有問題。這種編程風格導致線程 ...