目录
最小案例
需要的权限
<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;
}