要从您的应用以动态方式选择并连接网络,请执行以下步骤:
- 创建一个
[ConnectivityManager]( )
。 - 使用
[NetworkRequest.Builder]( )
类创建一个[NetworkRequest]( )
对象,并指定您的应用感兴趣的网络功能和传输类型。 - 要扫描合适的网络,请调用
[requestNetwork()]( ))
或[registerNetworkCallback()]( ))
,并传入[NetworkRequest]( )
对象和[ConnectivityManager.NetworkCallback]( )
的实现。如果您想在检测到合适的网络时主动切换到该网络,请使用[requestNetwork()]( ))
方法;如果只是接收已扫描网络的通知而不需要主动切换,请改用[registerNetworkCallback()]( ))
方法。
当系统检测到合适的网络时,它会连接到该网络并调用 [onAvailable()]( ))
回调。您可以使用回调中的 [Network]( )
对象来获取有关网络的更多信息,或者引导通信使用所选网络。
app都采用指定的网络
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder req = new NetworkRequest.Builder();
req.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
cm.requestNetwork(req.build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
ConnectivityManager.setProcessDefaultNetwork(network);
} else {
connectivityManager.bindProcessToNetwork(network);
}
} catch (IllegalStateException e) {
Log.e(TAG, "ConnectivityManager.NetworkCallback.onAvailable: ", e);
}
}
// Be sure to override other options in NetworkCallback() too…
}
指定某个请求采用指定的网络
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder req = new NetworkRequest.Builder();
req.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
cm.requestNetwork(req.build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// If you want to use a raw socket…
network.bindSocket(…);
// Or if you want a managed URL connection…
URLConnection conn = network.openConnection(new URL(“http://www.baidu.com/”));
}
// Be sure to override other options in NetworkCallback() too…
}
Android 中的实现
1. 先看一下 frameworks/base/core/java/android/net/ConnectivityManager.java 中 setProcessDefaultNetwork 的实现
public static boolean setProcessDefaultNetwork(Network network) {
int netId = (network == null) ? NETID_UNSET : network.netId;
if (netId == NetworkUtils.getBoundNetworkForProcess()) {
return true;
}
if (NetworkUtils.bindProcessToNetwork(netId)) {
// Set HTTP proxy system properties to match network.
// TODO: Deprecate this static method and replace it with a non-static version.
try {
Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
} catch (SecurityException e) {
// The process doesn’t have ACCESS_NETWORK_STATE, so we can’t fetch the proxy.
Log.e(TAG, “Can’t set proxy properties”, e);
}
// Must flush DNS cache as new network may have different DNS resolutions.
InetAddress.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
// cause subsequent fetches to be performed on old network.
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
return true;
} else {
return false;
}
}
2. 在 setProcessDefaultNetwork 的时候,HttpProxy,DNS 都会使用当前网络的配置,再来看一下 NetworkUtils.bindProcessToNetwork
/frameworks/base/core/java/android/net/NetworkUtils.bindProcessToNetwork 其实是直接转到了 /system/netd/client/NetdClient.cpp 中
int setNetworkForTarget(unsigned netId, std::atomic_uint* target) {
if (netId == NETID_UNSET) {
*target = netId;
return 0;
}
// Verify that we are allowed to use |netId|, by creating a socket and trying to have it marked
// with the netId. Call libcSocket() directly; else the socket creation (via netdClientSocket())
// might itself cause another check with the fwmark server, which would be wasteful.
int socketFd;
if (libcSocket) {
socketFd = libcSocket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
} else {
socketFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
}
if (socketFd < 0) {
return -errno;
}
int error = setNetworkForSocket(netId, socketFd);
if (!error) {
*target = netId;
}
close(socketFd);
return error;
}
extern “C” int setNetworkForSocket(unsigned netId, int socketFd) {
if (socketFd < 0) {
return -EBADF;
}
FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0};
return FwmarkClient().send(&command, socketFd);
}
extern “C” int setNetworkForProcess(unsigned netId) {
return setNetworkForTarget(netId, &netIdForProcess);
}
3. 客户端发送 FwmarkCommand::SELECT_NETWORK 通知服务端处理,代码在 /system/netd/server/FwmarkServer.cpp
int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
// …
Fwmark fwmark;
socklen_t fwmarkLen = sizeof(fwmark.intValue);
if (getsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) {
return -errno;
}
switch (command.cmdId) {
// …
case FwmarkCommand::SELECT_NETWORK: {
fwmark.netId = command.netId;
if (command.netId == NETID_UNSET) {
fwmark.explicitlySelected = false;
fwmark.protectedFromVpn = false;
permission = PERMISSION_NONE;
} else {
if (int ret = mNetworkController->checkUserNetworkAccess(client->getUid(),
command.netId)) {
return ret;
}
fwmark.explicitlySelected = true;
fwmark.protectedFromVpn = mNetworkController->canProtect(client->getUid());
}
break;
}
// …
}
fwmark.permission = permission;
if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue,
sizeof(fwmark.intValue)) == -1) {
return -errno;
}
return 0;
}
union Fwmark {
uint32_t intValue;
struct {
unsigned netId : 16;
bool explicitlySelected : 1;
bool protectedFromVpn : 1;
Permission permission : 2;
};
Fwmark() : intValue(0) {}
};
最后其实只是给 socketFd 设置了 mark,为什么这样就可以达到使用特定网络的目的呢。这里的实现原理大致为:
1. 该进程在创建socket时(app首先调用setProcessDefaultNetwork()),android底层会利用setsockopt函数设置该socket的SO_MARK为netId(android有自己的管理逻辑,每个Network有对应的ID),以后利用该socket发送的数据都会被打上netId的标记(fwmark 值)。
2. 利用策略路由,将打着netId标记的数据包都路由到指定的网络接口,例如WIFI的接口wlan0。
Linux 中的策略路由暂不在本章展开讨论,这里只需要了解通过这种方式就能达到我们的目的。
Hook socket api
也就是说只要在当前进程中利用setsockopt函数设置所有socket的SO_MARK为netId,就可以完成所有的请求都走特定的网络接口。
1. 先来看一下 /bionic/libc/bionic/socket.cpp
int socket(int domain, int type, int protocol) {
return __netdClientDispatch.socket(domain, type, protocol);
}
2. /bionic/libc/private/NetdClientDispatch.h
struct NetdClientDispatch {
int (accept4)(int, struct sockaddr, socklen_t*, int);
int (connect)(int, const struct sockaddr, socklen_t);
int (*socket)(int, int, int);
unsigned (*netIdForResolv)(unsigned);
};
extern LIBC_HIDDEN struct NetdClientDispatch __netdClientDispatch;
3. /bionic/libc/bionic/NetdClientDispatch.cpp
extern “C” __socketcall int __accept4(int, sockaddr*, socklen_t*, int);
extern “C” __socketcall int __connect(int, const sockaddr*, socklen_t);
extern “C” __socketcall int __socket(int, int, int);
static unsigned fallBackNetIdForResolv(unsigned netId) {
return netId;
}
文末
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊
小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。
其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
过的一些优质视频教程。
[外链图片转存中…(img-PEVzlaID-1714755798139)]
其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!