转载自:http://blog.csdn.net/u010019468/article/details/72886859
这个功能如标题所述:在wifi和移动数据网络同时开启之下,在Android5.0之前系统并没有很好地提供这样的api来实现这样的功能。现在需要wifi开着的情况下,强制通过移动数据网络发送网络请求,可能会觉得哪会有这样的蛋疼需求,认为只要能访问就行了,还要特地移动网络,那我只能讲你们的业务发展中没有这样的需求。好了废话不多说,实现如下:
Wifi下指定移动网络访问服务端
首先注意权限申请,需要如下权限才能切换:
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
@TargetApi(21)
private void forceSendRequestByMobileData() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NET_CAPABILITY_INTERNET);
builder.addTransportType(TRANSPORT_CELLULAR);
NetworkRequest build = builder.build();
connectivityManager.requestNetwork(build, new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
try {
URL url = new URL("");
HttpURLConnection connection = (HttpURLConnection)network.openConnection(url);
/*******省略参数配置*******/
connection.connect();
/*******数据流处理*******/
} catch (Exception e) {
}
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
其中
builder.addCapability(NET_CAPABILITY_INTERNET);
//强制使用蜂窝数据网络-移动数据
builder.addTransportType(TRANSPORT_CELLULAR);
通过配置TransportType参数TRANSPORT_CELLULAR来指定移动网络,其值可以为int有如下几种:分别为移动数据网络、wifi、蓝牙、以太网、Vpn等5中传输通道。这时就不论当前手机有多少网络是处于连接中,都可以指定单个。
/**
* Indicates this network uses a Cellular transport.
*/
public static final int TRANSPORT_CELLULAR = 0;
/**
* Indicates this network uses a Wi-Fi transport.
*/
public static final int TRANSPORT_WIFI = 1;
/**
* Indicates this network uses a Bluetooth transport.
*/
public static final int TRANSPORT_BLUETOOTH = 2;
/**
* Indicates this network uses an Ethernet transport.
*/
public static final int TRANSPORT_ETHERNET = 3;
/**
* Indicates this network uses a VPN transport.
*/
public static final int TRANSPORT_VPN = 4;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
上文中addCapability(NET_CAPABILITY_INTERNET)其参数配置也是固定范围可选的,不能乱配,共有19个参数可配:
public static final int NET_CAPABILITY_MMS = 0;
public static final int NET_CAPABILITY_SUPL = 1;
public static final int NET_CAPABILITY_DUN = 2;
public static final int NET_CAPABILITY_FOTA = 3;
public static final int NET_CAPABILITY_IMS = 4;
public static final int NET_CAPABILITY_CBS = 5;
public static final int NET_CAPABILITY_WIFI_P2P = 6;
public static final int NET_CAPABILITY_IA = 7;
public static final int NET_CAPABILITY_RCS = 8;
public static final int NET_CAPABILITY_XCAP = 9;
public static final int NET_CAPABILITY_EIMS = 10;
public static final int NET_CAPABILITY_NOT_METERED = 11;
/**
* Indicates that this network should be able to reach the internet.
*/
public static final int NET_CAPABILITY_INTERNET = 12;
public static final int NET_CAPABILITY_NOT_RESTRICTED = 13;
public static final int NET_CAPABILITY_TRUSTED = 14;
public static final int NET_CAPABILITY_NOT_VPN = 15;
public static final int NET_CAPABILITY_VALIDATED = 16;
public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
public static final int NET_CAPABILITY_FOREGROUND = 18;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
其中有默认如下配置能力,故若要访问网络需要加上NET_CAPABILITY_INTERNET
private static final long DEFAULT_CAPABILITIES =
(1 << NET_CAPABILITY_NOT_RESTRICTED) |
(1 << NET_CAPABILITY_TRUSTED) |
(1 << NET_CAPABILITY_NOT_VPN);
在connectivityManager.requestNetwork请求之后如果此次netWork是可以使用的,就会回调ConnectivityManager.NetworkCallback中onAvailable(Network network)函数,这时候利用返回的Network来进行http连接了network.openConnection(url)。其源码如下:
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
int timeoutMs, int legacyType) {
sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs, REQUEST,
legacyType);
}
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
NetworkCallback networkCallback, int timeoutSec, int action,
int legacyType) {
if (networkCallback == null) {
throw new IllegalArgumentException("null NetworkCallback");
}
if (need == null && action != REQUEST) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
try {
incCallbackHandlerRefCount();
synchronized(sNetworkCallback) {
if (action == LISTEN) {
networkCallback.networkRequest = mService.listenForNetwork(need,
new Messenger(sCallbackHandler), new Binder());
} else {
networkCallback.networkRequest = mService.requestNetwork(need,
new Messenger(sCallbackHandler), timeoutSec, new Binder(), legacyType);
}
if (networkCallback.networkRequest != null) {
sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
return networkCallback.networkRequest;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
可以看到 connectivityManager.requestNetwork()
requestNetwork(NetworkRequest request, NetworkCallback networkCallback)
又调用到
sendRequestForNetwork(request.networkCapabilities,networkCallback, timeoutMs, REQUEST,legacyType)
来实现请求,其源码可以看到requestNetwork的请求类型是REQUEST,继而走到:
networkCallback.networkRequest = mService.requestNetwork(need,
new Messenger(sCallbackHandler), timeoutSec, new Binder(), legacyType);
最后是通过mService这个是aidl进程通信的IConnectivityManager.Sub内部类prox代理对象,通过binder机制通知远端进行requestNetwork方法。
最后不要忘记了注销监听NetworkCallback:
mConnectivityManager.unregisterNetworkCallback(networkCallback)
适配问题
介绍使用以及原理就是这么多了,但是并不会实际中并不会就那么完美,首先想到就是适配情况
-
适配机型问题
在多个品牌手机中oppo,小米,魅族几个机型中发现可用能够实现这个功能,但在华为p系列p7以及荣耀手机中可以在回调中onAvailable(Network network)正常返回Network,也能进行连接,但读取网络结果时就一直出现超时。
解决办法:先检查是否有权限,特别注意oppo手机wifi第一次用户安装登录默认是没有数据网络权限的,所以总会导致接口访问失败超时,需要用户切换到移动数据网络连接状态下,让用户授权。
若权限存在的话,可以用下面提到的startUsingNetworkFeature低版本的兼容方法尝试,目前自测是可以的。区别在于这个不使用NetWork,直接在打开的移动网络中进行连接访问。
-
适配低版本
上述也提到在5.0之前要实现这样的功能呢,在低版本中可以使用startUsingNetworkFeature(int networkType, String feature)这个被抛弃方法实现,其实和上述实现原理差不多,只是使用起来性能以及效率问题,这个老Api在某些机型上不能立即采用移动网络进行请求,需要等待些时间重试。
public int startUsingNetworkFeature(int networkType, String feature) {
checkLegacyRoutingApiAccess();
NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
if (netCap == null) {
Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
feature);
return PhoneConstants.APN_REQUEST_FAILED;
}
NetworkRequest request = null;
synchronized (sLegacyRequests) {
LegacyRequest l = sLegacyRequests.get(netCap);
if (l != null) {
Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
renewRequestLocked(l);
if (l.currentNetwork != null) {
return PhoneConstants.APN_ALREADY_ACTIVE;
} else {
return PhoneConstants.APN_REQUEST_STARTED;
}
}
request = requestNetworkForFeatureLocked(netCap);
}
if (request != null) {
Log.d(TAG, "starting startUsingNetworkFeature for request " + request);
return PhoneConstants.APN_REQUEST_STARTED;
} else {
Log.d(TAG, " request Failed");
return PhoneConstants.APN_REQUEST_FAILED;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
继续看requestNetworkForFeatureLocked(netCap)实现
private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
int delay = -1;
int type = legacyTypeForNetworkCapabilities(netCap);
try {
delay = mService.getRestoreDefaultNetworkDelay(type);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
LegacyRequest l = new LegacyRequest();
l.networkCapabilities = netCap;
l.delay = delay;
l.expireSequenceNumber = 0;
l.networkRequest = sendRequestForNetwork(netCap, l.networkCallback, 0,
REQUEST, type);
if (l.networkRequest == null) return null;
sLegacyRequests.put(netCap, l);
sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
return l.networkRequest;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
可以看到最终也走到lsendRequestForNetwork(netCap, l.networkCallback, 0,REQUEST, type);这个方法和5.0之后的requestNetwork(NetworkRequest request, NetworkCallback networkCallback)是一个效果。
- 甚至有人可能会想到先断开wifi,再通过移动网络来访问,如下:
ConnectivityManager cm = (ConnectivityManager)Context.getSystemService(Context.CONNECTIVITY_SERVICE)
NetworkInfo ni = cm.getActiveNetworkInfo()
if(ni == null)
//no connectivity, abort
if(ni.getType() == ConnectivityManager.TYPE_WIFI || ni.getType() == ConnectivityManager.TYPE_WIMAX) {
WifiManager wm = (WifiManager)Context.getSystemService(Context.WIFI_SERVICE)
if( wm != null)
wm.disconnect()
//this will force android to fallback to other available n/w which is 3G
}
while(true) {
NetworkInfo ni = cm.getActiveNetworkInfo()
if( ni != null && ni.getType() == ConnectivityManager.TYPE_MOBILE && ni.isConnected()) {
//send your http request
break
}
//sleep for some time, so that android can connect you to other n/w
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
但是并不好用,好多手机都不能通过代码api来实现开关wifi以及移动网络,相比较而言可以用前者更合适。