判斷手機(jī)沒網(wǎng)了半火,手機(jī)又有網(wǎng)了越妈,看起來應(yīng)該是個(gè)很基礎(chǔ)的功能,但是我總感覺實(shí)現(xiàn)起來有點(diǎn)別扭钮糖。
以前判斷網(wǎng)絡(luò)是否連接的時(shí)候是ping一下服務(wù)器梅掠,這個(gè)方法目前看還是萬無一失酌住,ping不通App就沒有數(shù)據(jù),除非服務(wù)器掛了阎抒。
try {
//-c 3 ping3次酪我,-w 60 超時(shí)時(shí)間為60秒
Process p = Runtime.getRuntime().exec("ping -c 3 -w 60 " + "服務(wù)器ip地址");
return p.waitFor() == 0;
} catch (Exception e) {
e(e.toString(), TAG);
return false;
}
同時(shí)還使用了ConnectivityManager#getActiveNetworkInfo方法獲取聯(lián)網(wǎng)狀態(tài),比如用NetworkInfo#isConnected()判斷網(wǎng)絡(luò)是否連接且叁,用NetworkInfo#getType()判斷網(wǎng)絡(luò)連接的類型都哭。但是版本29之后NetworkInfo這個(gè)類被標(biāo)記過期了。
推薦使用ConnectivityManager中的NetworkCallback獲取網(wǎng)絡(luò)連接的相關(guān)信息逞带∑劢茫或者通過getNetworkCapabilityes或者getNetworkLinkProperties同步獲取。
之前獲取網(wǎng)絡(luò)變化狀態(tài)的方法是通過注冊(cè)廣播CONNECTIVITY_ACTION展氓,同樣被標(biāo)記過期
同時(shí)推薦了另外三個(gè)方法穆趴,這個(gè)三個(gè)方法就不是通過廣播獲取網(wǎng)絡(luò)狀態(tài)了,而是通過接口回調(diào)的方式異步返回信息遇汞。
研究一下新方法應(yīng)該怎么用未妹。還是在ConnectivityManager 這個(gè)類中。
ConnectivityManager就是用來告訴我們網(wǎng)絡(luò)的連接狀態(tài)空入,以及通知網(wǎng)絡(luò)連接發(fā)生了哪些變化络它。那么現(xiàn)在應(yīng)該如何判斷當(dāng)前的網(wǎng)絡(luò)狀態(tài)?ConnectivityManager#getType的注釋中給了一些提示歪赢。
/**
* Reports the type of network to which the
* info in this {@code NetworkInfo} pertains.
* @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link
* ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link
* ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other
* types defined by {@link ConnectivityManager}.
* @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
* instead with one of the NetworkCapabilities#TRANSPORT_* constants :
* {@link #getType} and {@link #getTypeName} cannot account for networks using
* multiple transports. Note that generally apps should not care about transport;
* {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
* {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
* apps concerned with meteredness or bandwidth should be looking at, as they
* offer this information with much better accuracy.
*/
@Deprecated
public int getType() {
synchronized (this) {
return mNetworkType;
}
}
這個(gè)提到了如果想判斷當(dāng)前網(wǎng)絡(luò)連接的類型酪耕,可以用NetworkCapabilities#hasTransport以及NetworkCapabilities中的常量進(jìn)行判斷,隨后又說app中可以不關(guān)心傳輸方式(transport)是怎樣的轨淌,通過NetworkCapabilities#NET_CAPABILITY_NOT_METERED和NetworkCapabilities#getLinkDownstreamBandwidthKbps可以更準(zhǔn)確的獲取到我們想要的信息迂烁。
其中g(shù)etLinkDownstreamBandwidthKbps就是字面意思,下載的帶寬是多少递鹉,個(gè)人認(rèn)為這也是判斷網(wǎng)絡(luò)是否連接的標(biāo)準(zhǔn)盟步,有數(shù)據(jù)回來了網(wǎng)絡(luò)就連接好了。有時(shí)連接到了路由器躏结,但是網(wǎng)絡(luò)欠費(fèi)了却盘,狀態(tài)是連接成功,實(shí)際上確沒網(wǎng)媳拴。
另外提到的NET_CAPABILITY_NOT_METERED黄橘,這個(gè)常量代表了是否是付費(fèi)網(wǎng)絡(luò),比如手機(jī)連接的是wifi屈溉,那么就是not metered塞关,未計(jì)量、不收費(fèi)的子巾,如果連接的是運(yùn)營(yíng)商的移動(dòng)網(wǎng)絡(luò)帆赢,那么就是metered小压,計(jì)量的、收費(fèi)的椰于。
最終判斷網(wǎng)絡(luò)是否連接的方式是怠益,是否有數(shù)據(jù)傳輸回來:
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
int downstreamBandwidthKbps = networkCapabilities.getLinkDownstreamBandwidthKbps();
return downstreamBandwidthKbps > 0;
如果此時(shí)網(wǎng)絡(luò)不是連通狀態(tài),那么獲取到的networkCapabilities直接就是null瘾婿。
接下來看看如何監(jiān)聽網(wǎng)絡(luò)狀態(tài)變化蜻牢,文檔里推薦了三個(gè)方法,其中requestNetwork方法需要Manifest.permission.CHANGE_NETWORK_STATE權(quán)限偏陪,或者是修改系統(tǒng)設(shè)置(android.provider.Settings.System)孩饼。修改系統(tǒng)設(shè)置很麻煩,debug模式無法設(shè)置竹挡,需要打包并給apk系統(tǒng)簽名镀娶,然后跳轉(zhuǎn)到一個(gè)修改系統(tǒng)頁面手動(dòng)允許。
我用手里的華為手機(jī)測(cè)試時(shí)揪罕,設(shè)置了CHANGE_NETWORK_STATE權(quán)限梯码,就不需要修改系統(tǒng)設(shè)置了,官方文檔對(duì)于這兩種操作的說明也是用“or”進(jìn)行連接的好啰,所以暫時(shí)就認(rèn)為兩種操作取其一就可以了轩娶,如果其他手機(jī)既需要權(quán)限又需要修改系統(tǒng)設(shè)置,可能也可能框往。
registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback)方法就稍微省事一些鳄抒,不涉及修改系統(tǒng)設(shè)置,只需要考慮如何設(shè)置NetworkRequest就行了椰弊,比如要監(jiān)聽移動(dòng)網(wǎng)絡(luò)和wifi许溅。
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addTransportType(TRANSPORT_WIFI);
builder.addTransportType(TRANSPORT_CELLULAR);
NetworkRequest request = builder.build();
如果還是覺得麻煩,request也不想設(shè)置秉版,因?yàn)檫€得弄明白request的參數(shù)都代表什么贤重,可以直接用registerDefaultNetworkCallback(NetworkCallback networkCallback),監(jiān)聽系統(tǒng)默認(rèn)的network清焕,參數(shù)只有callback回調(diào)并蝗,不需要對(duì)請(qǐng)求進(jìn)行設(shè)置,也可以監(jiān)聽移動(dòng)網(wǎng)絡(luò)和wifi的變化秸妥。
網(wǎng)絡(luò)連接有變化時(shí)會(huì)在NetworkCallback回調(diào)中告知我們滚停,NetworkCallback中的方法有:
mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
//連接準(zhǔn)備就緒時(shí)調(diào)用
}
@Override
public void onLosing(Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
//斷開連接時(shí)調(diào)用。隨后會(huì)出現(xiàn)新的連接粥惧,緊接著觸發(fā)onAvailable
}
@Override
public void onLost(Network network) {
super.onLost(network);
//斷開連接時(shí)調(diào)用键畴,徹底斷開,比如手動(dòng)關(guān)閉手機(jī)網(wǎng)絡(luò)影晓。
}
@Override
public void onUnavailable() {
super.onUnavailable();
//未發(fā)現(xiàn)適用的網(wǎng)絡(luò)
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
//Capabilities屬性改變時(shí)調(diào)用
}
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
super.onLinkPropertiesChanged(network, linkProperties);
//LinkProperties屬性改變時(shí)調(diào)用
}
});
當(dāng)連接到新的網(wǎng)絡(luò)時(shí)镰吵,onAvailable檩禾,onCapabilitiesChanged挂签,onLinkPropertiesChanged疤祭,三個(gè)方法會(huì)依次出現(xiàn)。
在測(cè)試時(shí)發(fā)現(xiàn)饵婆,如果手動(dòng)斷開網(wǎng)絡(luò)勺馆,僅僅會(huì)調(diào)用onLoss方法,再此手動(dòng)打開手機(jī)網(wǎng)絡(luò)侨核,才會(huì)觸發(fā)onAvailable草穆。同時(shí),在源碼中發(fā)現(xiàn)了如下用法搓译。
在onAvailable中記錄下當(dāng)前連接的NetWork悲柱,然后在onLost中判斷斷開的是否為當(dāng)前的連接的NetWork,如果是些己,則認(rèn)為網(wǎng)絡(luò)斷開豌鸡。
這里還有個(gè)細(xì)節(jié)需要弄清楚,手機(jī)是否一次只能連接一個(gè)Network段标,如果是涯冠,那么onAvailable和onLost組合才能正確判斷網(wǎng)絡(luò)是否連接。我在測(cè)試時(shí)逼庞,如果手機(jī)先連接了移動(dòng)網(wǎng)絡(luò)蛇更,在切換到Wi-Fi網(wǎng)絡(luò),會(huì)調(diào)用onAvailable方法赛糟,此時(shí)如果關(guān)閉Wi-Fi派任,會(huì)調(diào)用onLost方法,但此時(shí)手機(jī)為無網(wǎng)絡(luò)狀態(tài)(ping不通)璧南,并不會(huì)馬上切換回移動(dòng)網(wǎng)絡(luò)吨瞎,需要一小段時(shí)間才能切換回移動(dòng)網(wǎng)絡(luò)時(shí),然后重新觸發(fā)onAvailable方法穆咐。
所以判斷網(wǎng)絡(luò)斷開的方式就可以是在onAvailable中確認(rèn)網(wǎng)絡(luò)連接颤诀,在onLost中確認(rèn)網(wǎng)絡(luò)斷開:
public void onAvailable(Network network) {
super.onAvailable(network);
mCurrentNetwork = network;
//網(wǎng)絡(luò)連接 do sth
}
@Override
public void onLost(Network network) {
super.onLost(network);
ConnectivityManager conn = (ConnectivityManager) mContext.getSystemService(Activity.CONNECTIVITY_SERVICE);
if (conn == null) {
return;
}
if (network.equals(mCurrentNetwork)) {
mCurrentNetwork = null;
//延遲3s執(zhí)行,如果mCurrentNetwork仍為null对湃,那么可能沒有新的Network連接崖叫,則認(rèn)為網(wǎng)絡(luò)斷開
Handler handler = new Handler();
handler.postDelayed(() -> {
if (mCurrentNetwork == null ) {
//網(wǎng)絡(luò)斷開 do sth
}
}, 3000);
}
}
Google之所依廢棄以前的方法,就是為了提供給我們更多拍柒,更準(zhǔn)確網(wǎng)絡(luò)信息心傀。比如在onCapabilitiesChanged和onLinkPropertiesChanged兩個(gè)回調(diào)方法中,把參數(shù)打印一下拆讯。
networkCapabilities.toString():
[ Transports: CELLULAR Capabilities: SUPL&INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED&FOREGROUND LinkUpBandwidth>=51200Kbps LinkDnBandwidth>=102400Kbps Specifier: <2>]
linkProperties.toString():
NetworkCapabilities脂男,描述了網(wǎng)絡(luò)的種類和帶寬养叛,原來描述手機(jī)網(wǎng)絡(luò)只需要一個(gè)常量TYPE_MOBILE,但是隨著手機(jī)網(wǎng)絡(luò)的速度越來快宰翅,4G弃甥、5G的發(fā)展,簡(jiǎn)單的分類不足以描述越來越多的手機(jī)網(wǎng)絡(luò)汁讼,于是Google采用了直接用帶寬描述網(wǎng)絡(luò)連接的方式淆攻,所以不直接給一個(gè)返回布爾值的方法判斷網(wǎng)絡(luò)連接狀態(tài)也沒啥好別扭的了。
LinkProperties 則包含地址嘿架、網(wǎng)關(guān)瓶珊、dns扳碍、代理等信息拧廊。
如果想知道具體的網(wǎng)絡(luò)種類撑螺,可以通過NetworkCapabilities#hasTransport方法荸型,通過不同的參數(shù)喷兼,可以判斷當(dāng)前的連接的網(wǎng)絡(luò)是什么馏予,參數(shù)有7種趾痘。
TRANSPORT_CELLULAR:蜂窩網(wǎng)絡(luò)
TRANSPORT_WIFI:wifi
TRANSPORT_BLUETOOTH:藍(lán)牙撥號(hào)逆济,手機(jī)通過藍(lán)牙連接pc上的網(wǎng)絡(luò)蜀肘,網(wǎng)上有教程
TRANSPORT_ETHERNET:因特網(wǎng)绊汹,手機(jī)直接連接網(wǎng)線,網(wǎng)上有這樣的轉(zhuǎn)接頭
TRANSPORT_VPN:vpn
TRANSPORT_WIFI_AWARE:這個(gè)沒見過扮宠,據(jù)說場(chǎng)景大概是西乖,比如你和一家商店通過這種協(xié)議進(jìn)行了網(wǎng)絡(luò)連接,下次你路過這家商店的時(shí)候坛增,手機(jī)會(huì)直接收到這家店的商品信息
TRANSPORT_LOWPAN:ipv6網(wǎng)絡(luò)
Google也提示我們不需要關(guān)心這些获雕,關(guān)注是否計(jì)費(fèi)和網(wǎng)絡(luò)帶寬就行。