目錄
最小案例
需要的權限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter=new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver,intentFilter);
}
@Override
protected void onDestroy(){
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
private final class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
ConnectivityManager cm=(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo= cm.getActiveNetworkInfo();
if(networkInfo == null){
// WIFI 和 移動網絡都關閉 即沒有有效網絡
Log.e(TAG, "當前無網絡連接");
return;
}
String typeName = "";
if(networkInfo.getType()==ConnectivityManager.TYPE_WIFI){
//WIFI網絡跳轉的頁面.
typeName = networkInfo.getTypeName();//==> WIFI
}else if(networkInfo.getType()==ConnectivityManager.TYPE_MOBILE) {
//無線網絡跳轉的頁面
typeName = networkInfo.getTypeName();//==> MOBILE
}
Log.e(TAG, "==>" + typeName);
Log.e(TAG, "==>" + networkInfo.getDetailedState());
}
}
}
概況
ConnectivityManager主要職責,官方說明:
- Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
- Send broadcast intents when network connectivity changes
- Attempt to “fail over” to another network when connectivity to a network is lost
- Provide an API that allows applications to query the coarse-grained or fine-grained state of the available networks
- Provide an API that allows applications to request and select networks for their data traffic
平民翻譯:
- 監控網絡連接(Wi-Fi, GPRS, UMTS, etc.);
- 當網絡連接改變時發送Intent;
- 當連接到一個網絡失敗時,嘗試用其他網絡補救;
- 提供API給應用查詢有效網絡粗略或者精確的狀態;
- 提供API給應用爲它們的數據傳輸請求和選擇網絡;
實踐概要
NetworkInfo 實踐詳解
名稱 | 說明 |
---|---|
isConnected() | 判斷網絡連接是否存在 |
isAvailable() | 判斷網絡連接(注:isConnected爲true,不代表isAvailable爲true) |
getDetailedState() | (詳細)報告當前網絡狀態 getState()報告當前網絡狀態 |
getExtrInfo() | 報告關於網絡狀態的額外信息,由較低的網絡層提供的 |
getType() | 獲取當前網絡的類型 和ConnectivityManager.TYPE_**對比 |
getTypeName() | 獲取當前網絡的類型名如 “WIFI” or “MOBILE” |
注意:
- isConnected() 和 isAvailable() 都爲true 不能保證能正常連接到服務器,解決方案用ping命令,參考ShellUtil.execCmd:
//ShellUtil.CommandResult result = ShellUtil.execCmd(String.format("ping -c 1 %s", ip), false);
String[] command = new String[]{String.format("ping -c 1 %s", ip)};
int result = -1;
Process process = Runtime.getRuntime().exec(isRoot ? "su" : "sh");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) continue;
os.write(command.getBytes());
os.writeBytes(LINE_SEP);
os.flush();
}
os.writeBytes("exit" + LINE_SEP);
os.flush();
result = process.waitFor();
- getDetailedState/getState 直接看NetworkInfo源碼getState等到的值爲
public enum State {
CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
}
- getExtrInfo() WIFI鏈接返回SSID名稱,移動網絡返回APN(Access Point Name),電信物聯網得卡到列如:ctnet或ctwap
ConnectivityManager 實踐詳解
ConnectivityManager類按照其主要職能,常用方法大致可分爲三類:
- 獲取網絡鏈接信息getXXX如getActiveNetwork,getDefaultProxy
- 多網絡鏈接綁定指定網絡 bindsocket和bindprocesstonetwork(6.0的系統 api>23)
- 監聽類型 registerDefaultNetworkCallback/registerNetworkCallback
方法 | 詳解 |
---|---|
addDefaultNetworkActiveListener | 在網絡有一定量的數據傳輸時間隔調用,可用於鏈接優化 |
cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
cm.addDefaultNetworkActiveListener(new ConnectivityManager.OnNetworkActiveListener() {
@Override public void onNetworkActive() {
Log.e(TAG, "Active ==>"); //無論是否成功鏈接外網,只要有一定量的數據傳輸就會間隔有回調
}
});
方法 | 詳解 |
---|---|
bindProcessToNetwork | 多網絡同時鏈接時指定網絡鏈接 |
應用場景:
- 手機同時連接移動網絡和本地局域網,手機所有的連接默認訪問移動網絡,需要手機連接本地局域網:
Okhttp,picasso與glidez指定網絡訪問的案例
方法 | 詳解 |
---|---|
registerNetworkCallback | 指定網絡監聽的內容回調 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP/*5.0*/) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
// 請注意這裏會有一個版本適配bug,所以請在這裏添加非空判斷
if (connectivityManager != null) {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
connectivityManager.registerNetworkCallback(request,new ConnectivityManager.NetworkCallback(){
/**
* 網絡可用的回調連接成功
* */
@Override public void onAvailable(Network network) {
super.onAvailable(network);
Log.e(TAG, "onAvailable ==>" + network.toString());
}
/**
* 實踐中在網絡連接正常的情況下,丟失數據會有回調
* */
@Override public void onLosing(Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
Log.e(TAG, "onLosing ==>" + network.toString() + " max==>" + maxMsToLive);
}
/**
* 網絡不可用時調用和onAvailable成對出現
*/
@Override public void onLost(Network network) {
super.onLost(network);
Log.e(TAG, "onLost ==>" + network.toString());
}
@Override public void onUnavailable() {
super.onUnavailable();
Log.e(TAG, "onUnavailable ==>");
}
/**
* 字面直接能理解
* @param network 新連接網絡
* @param networkCapabilities 新連接網絡的一些能力參數
*/
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
Log.e(TAG, "onCapabilitiesChanged ==>" + networkCapabilities.toString());
//WIFI -> CELLULAR
//[ Transports: CELLULAR Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED LinkUpBandwidth>=51200Kbps LinkDnBandwidth>=102400Kbps Specifier: <3>]
//CELLULAR -> WIFI
//==>[ Transports: WIFI Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps SignalStrength: -42]
}
/**
* 和上面類似,但是沒有試出效果
*/
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
super.onLinkPropertiesChanged(network, linkProperties);
Log.e(TAG, "onLinkPropertiesChanged ==>" + linkProperties.toString());
}
});
}
}
更多監聽內容設置請訪問官網NetworkRequest
Network 淺析
從源碼中可以得出Network
就是一個Parcelable數據,最重要的參數是
public final int netId;
在回想一下Network
什麼時候第一次獲得
@Override public void onAvailable(Network network)
如果到這裏還啥都不get
代碼意圖,那看Network提供的方法
public InetAddress[] getAllByName(String host) throws UnknownHostException {
return InetAddress.getAllByNameOnNet(host, netId);
}
public InetAddress getByName(String host) throws UnknownHostException {
return InetAddress.getByNameOnNet(host, netId);
}
bindSocket(Socket socket)
getSocketFactory()
openConnection(URL url)
openConnection(URL url, Proxy proxy)
好到此我的理解是在網絡連接onAvailable
成功後,返回基於netId的底層網絡標識,可以基於這個Network建立一些特殊的URL訪問;
實用工具
//NetUtils.java
private static final int NETWORK_TYPE_GSM = 16;
private static final int NETWORK_TYPE_TD_SCDMA = 17;
private static final int NETWORK_TYPE_IWLAN = 18;
/**
* 判斷網絡是否可用
* <p>需要異步 ping,如果 ping 不通就說明網絡不可用</p>
*
* @param ip ip 地址(自己服務器 ip),如果爲空,ip 爲阿里巴巴公共 ip
* @return {@code true}: 可用<br>{@code false}: 不可用
*/
public static boolean isAvailableByPing(String ip) {
if (ip == null || ip.length() <= 0) {
// 阿里巴巴公共 ip
ip = "223.5.5.5";
}
ShellUtil.CommandResult result = ShellUtil.execCmd(String.format("ping -c 1 %s", ip), false);
boolean ret = result.result == 0;
if (result.successMsg != null) {
Log.d("NetUtil", "isAvailableByPing() called" + result.successMsg);
}
if (result.errorMsg != null) {
Log.d("NetUtil", "isAvailableByPing() called" + result.errorMsg);
}
return ret;
}
/**
* 判斷移動數據是否打開
*
* @return {@code true}: 是<br>{@code false}: 否
*/
public static boolean isMobileDataEnabled() {
try {
TelephonyManager tm = (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
Method getMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("getDataEnabled");
if (getMobileDataEnabledMethod != null) {
return (boolean) getMobileDataEnabledMethod.invoke(tm);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 打開或關閉移動數據
*
* @param enabled {@code true}: 打開<br>{@code false}: 關閉
*/
public static void setMobileDataEnabled(boolean enabled) {
try {
TelephonyManager tm = (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
Method setMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
if (null != setMobileDataEnabledMethod) {
setMobileDataEnabledMethod.invoke(tm, enabled);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private enum NetworkType {
// wifi
NETWORK_WIFI,
// 4G 網
NETWORK_4G,
// 3G 網
NETWORK_3G,
// 2G 網
NETWORK_2G,
// 未知網絡
NETWORK_UNKNOWN,
// 沒有網絡
NETWORK_NO
}
/**
* 獲取當前網絡類型
*
* @return 網絡類型
* <ul>
* <li>{@link NetworkType#NETWORK_WIFI } </li>
* <li>{@link NetworkType#NETWORK_4G } </li>
* <li>{@link NetworkType#NETWORK_3G } </li>
* <li>{@link NetworkType#NETWORK_2G } </li>
* <li>{@link NetworkType#NETWORK_UNKNOWN} </li>
* <li>{@link NetworkType#NETWORK_NO } </li>
* </ul>
*/
public static NetworkType getNetworkType() {
NetworkType netType = NetworkType.NETWORK_NO;
NetworkInfo info = getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
if (info.getType() == ConnectivityManager.TYPE_WIFI) {
netType = NetworkType.NETWORK_WIFI;
} else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
switch (info.getSubtype()) {
case NETWORK_TYPE_GSM:
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_IDEN:
netType = NetworkType.NETWORK_2G;
break;
case NETWORK_TYPE_TD_SCDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
case TelephonyManager.NETWORK_TYPE_EHRPD:
case TelephonyManager.NETWORK_TYPE_HSPAP:
netType = NetworkType.NETWORK_3G;
break;
case NETWORK_TYPE_IWLAN:
case TelephonyManager.NETWORK_TYPE_LTE:
netType = NetworkType.NETWORK_4G;
break;
default:
String subtypeName = info.getSubtypeName();
// 中國移動 聯通 電信 三種 3G 制式
if (subtypeName.equalsIgnoreCase("TD-SCDMA")
|| subtypeName.equalsIgnoreCase("WCDMA")
|| subtypeName.equalsIgnoreCase("CDMA2000")) {
netType = NetworkType.NETWORK_3G;
} else {
netType = NetworkType.NETWORK_UNKNOWN;
}
break;
}
} else {
netType = NetworkType.NETWORK_UNKNOWN;
}
}
return netType;
}