初識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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...