目錄
最小案例
需要的許可權
<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;
}
複製程式碼