一、Android Wifi常用廣播
網絡開發中主體會使用到的action:
ConnectivityManager.CONNECTIVITY_ACTION
WifiManager.WIFI_STATE_CHANGED_ACTION
WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
WifiManager.NETWORK_IDS_CHANGED_ACTION
WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION
WifiManager.NETWORK_STATE_CHANGED_ACTION
WifiManager.RSSI_CHANGED_ACTION
1、ConnectivityManager.CONNECTIVITY_ACTION:
網絡連接發生了變化的廣播, 通常是默認的連接類型已經建立連接或者已經失去連接會觸發的廣播; 監聽到這個廣播之后, 可以從intent中獲取字段ConnectivityManager.EXTRA_NO_CONNECTIVITY, 如果返回true, 代表當前連接斷開. 否則,連接成功.同時可以從intent中取出字段 ConnectivityManager.EXTRA_NETWORK_INFO, 返回NetWorkInfo, 通過此對象, 你會獲取當前連接的一些更為具體的信息. 部分代碼如下:
// 是否無連接.
public static boolean isNoConnectivity(Intent intent) {
return intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
}
// 獲取當前網絡信息.
public static NetworkInfo getExtraNetworkInfo(Intent intent) {
return intent.getPacelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
}
// 獲取當前網絡狀態.
public static NetworkInfo.State getNetState(NetworkInfo info) {
return info == null ? null : info.getState();
}
// 獲取當前網絡類型.
public static int getNetState(NetworkInfo info) {
return info == null ? -1 : info.getType();
}
2、WifiManager.WIFI_STATE_CHANGED_ACTION:
WiFi模塊硬件狀態改變的廣播, 對於肉眼而言, 看到的直觀表征有, WiFi開啟, WiFi關閉; 而在實際的過程中, WIFI 從開啟到關閉, 或是從關閉到開啟, 需要經歷三個狀態, 以開啟WIFI為例, 其要經過的狀態分別為: 已關閉, 開啟中, 已開啟. 關閉WIFI則相反, 分為為: 已開啟, 關閉中, 關閉. 接收到這個廣播后, 你可以從intent中取出當前WiFi硬件的變化狀態, 可以使用 int 值來區別; 這個key是: EXTRA_WIFI_STATE, 可能得到的值為:0, 1, 2, 3, 4; 當然除了這種獲取方式, 也可以通過WiFiManager對象getWifiState() 獲取這個值. 也可以從 intent 中取出另外一個值, 表示之前WiFi模塊的狀態, 是不是很爽? 那么, 對應的key, 就是: EXTRA_PREVIOUS_WIFI_STATE;
// 通過 intent 獲取當前WIFI狀態.
public static int getWifiStateByIntent(Intent intent) {
return intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
}
// 通過 WifiManager 獲取當前WIFI狀態.
public static int getWifiStateByWifiManager(WifiManager manager) {
return manager == null ? WifiManager.WIFI_STATE_UNKNOWN : manager.getState();
}
// 獲取WIFI前一時刻狀態.
public static int getWifiPreviousState(Intent intent) {
return intent.getIntExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
}
其中:
0 --> WiFiManager.WIFI_STATE_DISABLING, 表示 WiFi 正關閉的瞬間狀態;
1 --> WifiManager.WIFI_STATE_DISABLED, 表示 WiFi 模塊已經完全關閉的狀態;
2 --> WifiManager.WIFI_STATE_ENABLING, 表示 WiFi 模塊正在打開中瞬間的狀態;
3 --> WiFiManager.WIFI_STATE_ENABLED, 表示 WiFi 模塊已經完全開啟的狀態;
4 --> WiFiManager.WIFI_STATE_UNKNOWN, 表示 WiFi 處於一種未知狀態; 通常是在開啟或關閉WiFi的過程中出現不可預知的錯誤, 通常是底層狀態機可能跑的出現故障了, 會到這種情況, 與底層控制相關;
3、WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
掃描到一個熱點, 並且此熱點達可用狀態 會觸發此廣播; 此時, 你可以通過wifiManager.getScanResult() 來取出當前所掃描到的ScanResult; 同時, 你可以從intent中取出一個boolean值; 如果此值為true, 代表着掃描熱點已完全成功; 為false, 代表此次掃描不成功, ScanResult距離上次掃描並未得到更新;
// 獲取 ScanResult 列表:
public static List<ScanResult> getScanResultForWifi(WifiManager manager) {
return manager == null ? null : manager.getScanResult();
}
// result 是否更新:
public static boolean isResultUpdated(Intent intent) {
return intent != null && intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
}
4、WifiManager.NETWORK_IDS_CHANGED_ACTION:
在網絡配置, 保存, 添加, 連接, 斷開, 忘記的操作過后, 均會對 WIFI 熱點配置形成影響, 在shell下, 如果有root權限, 可以在執行上述動作前后, 分別瀏覽 /data/misc/wifi/wpa_supplicant.conf 應該是有本質的變化, 此時會收到此廣播.具體的執行指令為:
adb shell
$ cat /data/misc/wifi/wpa_supplicant.conf
5、WifiManager.SUPPLICANT_STATE_CHANGED_ACTION:
建立連接的熱點正在發生變化. 象征變化的相關類為: SupplicantState, 你可以在接收到此廣播時, 觀察到已經建立連接的熱點的整個連接過程, 包含可能會出現連接錯誤的錯誤碼. 相關代碼為:
// 獲取當前網絡新狀態.
public static SupplicantState getCurrentNetworkState(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE);
}
// 獲取當前網絡連接狀態碼.
public static int getCurrentNetworkCode(Intent intent) {
return int netConnectErrorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0);
}
// 當前網絡是否連接失敗
public static boolean isCurrentNetworkConnectFailed(intent intent) {
return WifiManager.ERROR_AUTHENTICATING == getCurrentNetworkCode(intent);
}
6、WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
官方的注釋是這么說的, 廣播已配置的網絡發生變化, 可由添加, 修改, 刪除網絡的觸發. 當從intent中取出key值為EXTRA_MULTIPLE_NETWORKS_CHANGED, 其值為true時, 那么字段EXTRA_WIFI_CONFIGURATION中取出來的配置已經過時, 不是最新配置了, 具體的代碼為:
// 是否多重網絡發生變化.
public static boolean isMultipleNetworkChanged(Intent intent) {
return intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
}
// 獲取當前最新網絡配置:
public static WifiConfiguration getCurWifiConfig(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
}
7、WifiManager.LINK_CONFIGURATION_CHANGED_ACTION:
WIFI連接配置發生改變的廣播. 此時, 網路連接功能封裝LinkProperties和NetworkCapabilities可能發生變化.
// 獲取 LinkProperties
public static LinkProperties getLinkProperties(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_LINK_PROPERTIES);
}
// 獲取 NetworkCapabilities
public static NetworkCapabilities getNetworkCapabilities(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_CAPABILITIES);
}
8、WifiManager.NETWORK_STATE_CHANGED_ACTION:
WIFI連接狀態發生改變的廣播. 可以從intent中取得NetworkInfo, 此時NetworkInfo中提供了連接的新狀態, 如果連接成功, 可以獲取當前連接網絡的BSSID, 和WifiInfo. 相關代碼:
// 獲取當前網絡
public static NetworkInfo getCurrentNetworkInfo(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
}
// 獲取當前網路狀態.
public static NetworkInfo.State getCurrentNetworkState(NetworkInfo info) {
return info != null ? info.getState() : null;
}
// 獲取當前網路BSSID.
public static String getCurrentNetworkBssid(Intent intent) {
return intent.getStringExtra(WifiManager.EXTRA_BSSID);
}
// 獲取當前網路的WifiInfo. wifi 連接成功有效.
public static WifiInfo getCurrentWifiInfo(Intent intent) {
return intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
}
9、WifiManager.RSSI_CHANGED_ACTION:
WIFI熱點信號強度發生變化的廣播. 可以獲取當前變化熱點的最新的信號強度.
// 獲取當前熱點最新的信號強度
public static int getCurrentNetworkRssi(Intent intent) {
return intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -1000);
}
二、Android即時網絡監聽——BroadcastReceiver
通常我們在進行網絡請求之前,會先進行網絡狀態判斷,再決定是否進行網絡請求,常用方法如下:
/**
* 判斷網絡是否可用
*
* @return true/false
*/
@SuppressLint("MissingPermission")
public static boolean isNetworkAvailable() {
ConnectivityManager connMgr = (ConnectivityManager) NetworkListener.getInstance().getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connMgr == null) {
return false;
}
NetworkInfo[] infos = connMgr.getAllNetworkInfo();
if (infos != null) {
for (NetworkInfo info : infos) {
if (info.getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
/**
* 獲取當前網絡類型
*
* @return NetType
*/
@SuppressLint("MissingPermission")
public static NetType getNetType() {
ConnectivityManager connMgr = (ConnectivityManager) NetworkListener.getInstance().getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connMgr == null) {
return NetType.NONE;
}
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo == null) {
return NetType.NONE;
}
int nType = networkInfo.getType();
if (nType == ConnectivityManager.TYPE_MOBILE) {
if ("cmnet".equals(networkInfo.getExtraInfo().toLowerCase())) {
return NetType.CMNET;
} else {
return NetType.CMWAP;
}
} else if (nType == ConnectivityManager.TYPE_WIFI) {
return NetType.WIFI;
}
return NetType.NONE;
}
但這種做法無法做到即時網絡監聽,例如在播放視頻或下載文件過程中,網絡狀態發生變化,我們無法做到即時處理,解決方式有2種:
(1)使用廣播方式
(2)使用ConnectivityManager.NetworkCallback方式
本次我們先使用廣播監聽的方式進行網絡變化監聽。
我們知道,網絡狀態發生變化的時候,系統會發出android.net.CONNECTIVITY_CHANGE廣播,所以我們可以用監聽此廣播的方式來判斷網絡連接:
1、權限申請
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
2、創建常量
//網絡連接改變廣播
public static final String ANDROID_NET_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
3、定義枚舉類,表示網絡狀態:
public enum NetType {
//任意網絡
AUTO,
//WIFI
WIFI,
CMNET,
//手機上網
CMWAP,
//無網絡
NONE
}
4、定義網絡監聽接口:
public interface NetChangeListener {
/**
* 已連接
* @param netType NetType
*/
void onConnect(NetType netType);
/**
* 連接斷開
*/
void onDisConnect();
}
5、創建廣播接收者:
public class NetworkStateReceiver extends BroadcastReceiver {
private static final String TAG = "NetworkStateReceiver";
private NetType netType;//網絡類型
private NetChangeListener listener;
public NetworkStateReceiver() {
//初始化網絡連接狀態
this.netType = NetType.NONE;
}
public void setListener(NetChangeListener listener) {
this.listener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) {
Log.e(TAG, "onReceive: 異常");
return;
}
if (intent.getAction().equals(Constants.ANDROID_NET_CHANGE_ACTION)) {
Log.d(TAG, "onReceive: 網絡發生變化");
//獲取當前聯網的網絡類型
netType = NetworkUtils.getNetType();
if (NetworkUtils.isNetworkAvailable()) {
Log.d(TAG, "onReceive: 網絡連接成功");
if (listener != null) {
listener.onConnect(netType);
}
} else {
Log.e(TAG, "onReceive: 網絡連接失敗");
if (listener != null) {
listener.onDisConnect();
}
}
}
}
}
6、定義NetworkListener類(單例)用於初始化監聽:
public class NetworkListener {
private Context context;
private NetworkStateReceiver receiver;
/**
* 私有化構造方法
*/
private NetworkListener() {
receiver = new NetworkStateReceiver();
}
private static final SingletonTemplate<NetworkListener> INSTANCE = new SingletonTemplate<NetworkListener>() {
@Override
protected NetworkListener create() {
return new NetworkListener();
}
};
public static NetworkListener getInstance() {
return INSTANCE.get();
}
public Context getContext() {
return context;
}
public void setListener(NetChangeListener listener) {
receiver.setListener(listener);
}
/**
* 初始化
*
* @param context context
*/
@SuppressLint("MissingPermission")
public void init(Context context) {
this.context = context;
IntentFilter filter = new IntentFilter();
filter.addAction(Constants.ANDROID_NET_CHANGE_ACTION);
context.registerReceiver(receiver, filter);
}
}
7、使用:
Application中初始化:
NetworkListener.getInstance().init(this);
在需要監聽的Activity中實現NetChangeListener接口,並在onCreate()中初始化NetworkListener,如下:
public class MainActivity extends AppCompatActivity implements NetChangeListener {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NetworkListener.getInstance().setListener(this);
}
@Override
public void onConnect(NetType netType) {
Log.d(TAG, "onConnect: 網絡連接成功:狀態_" + netType.name());
}
@Override
public void onDisConnect() {
Log.e(TAG, "onDisConnect: 網絡連接斷開");
}
}
通過以上代碼,我們可以實現在APP運行過程中實時監聽網絡連接狀態,此方式使用廣播監聽結合接口回調實現。
三、Android即時網絡監聽——ConnectivityManager.NetworkCallback
1、新建NetworkCallbackImpl類繼承ConnectivityManager.NetworkCallback,並重寫onAvailable、onLost、onCapabilitiesChanged三個方法:
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
Log.d(TAG, "onAvailable: 網絡已連接");
}
@Override
public void onLost(Network network) {
super.onLost(network);
Log.e(TAG, "onLost: 網絡已斷開");
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
Log.d(TAG, "onCapabilitiesChanged: 網絡類型為wifi");
post(NetType.WIFI);
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
Log.d(TAG, "onCapabilitiesChanged: 蜂窩網絡");
post(NetType.CMWAP);
} else {
Log.d(TAG, "onCapabilitiesChanged: 其他網絡");
post(NetType.AUTO);
}
}
}
2、注冊NetworkCallbackImpl:
NetworkCallbackImpl networkCallback = new NetworkCallbackImpl();
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.build();
ConnectivityManager connMgr = (ConnectivityManager) NetworkListener.getInstance().getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connMgr != null) {
connMgr.registerNetworkCallback(request, networkCallback);
}
做完以上兩步之后,運行代碼,不出意外的話,網絡狀態改變就可以正常檢測出來了。
參考博客:
【1】Android WIFI開發之廣播監聽
【2】Android-WiFi開發之 WiFi廣播監聽(格式化版)
【3】Android即時網絡監聽(一)-BroadcastReceiver
【4】Android即時網絡監聽(二)-ConnectivityManager.NetworkCallback
【5】Android WiFi P2P開發實踐筆記