ConnectivityManager.bindProcessToNetwork
下面给出尽可能详尽的说明:ConnectivityManager.bindProcessToNetwork 的语义、API 层级、与旧 API 的差别、典型用法(含 Kotlin 示例)、常见坑与调试建议,以及更安全/可控的替代方案(按 socket 绑定或使用 Network 的 SocketFactory)。我会把关键事实用官方/权威来源标注出来,方便你查阅。
概览 — bindProcessToNetwork 是什么(核心含义)
ConnectivityManager.bindProcessToNetwork(network)把 当前进程(process)绑定到指定的Network对象。
一旦成功绑定:此进程中新创建的所有 socket(除非显式用 network 提供的 SocketFactory/手动绑定)都将走这个 network;并且该进程的域名解析(hostname resolution)也会被限制在该 network 的 DNS 上。如果传null,则清除绑定,恢复系统默认路由。(IUT de Sénart/Fontainebleau)- API 层级:在 API level 23(Android 6.0 / Marshmallow)加入。在 Lollipop(API 21)有旧的
setProcessDefaultNetwork,从 M 以后用bindProcessToNetwork。NDK 对应的底层接口也在 23 引入。(Android Git Repositories) - 返回值:布尔型。
true表示绑定成功;false通常表示Network已不可用(或缺权限、其他失败原因)。(IUT de Sénart/Fontainebleau)
为什么/何时用它(典型场景)
- 当设备同时有多个网络连接(例如:Wi-Fi(但无互联网)+ 蜂窝数据)且你希望把整个 App(进程) 的流量强制路由到某个特定网络(例如连接到不通 Internet 的 IoT 设备的 Wi-Fi),可以使用它来保证网络请求不被系统路由到默认有 Internet 的网络。常见于:局域网设备通信(摄像头、路由器配置页、设备配网)或把流量强制通过 VPN/专用网络进行测试。(Stack Overflow)
推荐使用模式(原则性建议)
- 不要盲目在任意时刻调用 —— 常见做法是:
ConnectivityManager.requestNetwork或NetworkRequest→ 在NetworkCallback.onAvailable(network)被回调时再调用bindProcessToNetwork(network)。在onLost()中清除绑定(传 null)。这种模式在官方示例与文档中推荐。(Android Developers) - 优先考虑按 socket 绑定或使用
Network.getSocketFactory(),而不是进程级绑定。文档明确建议:如果只需要部分流量走特定网络,优先使用单个 Socket 的绑定(Network.bindSocket()/Network.getSocketFactory()/Network.getAllByName()),这是更精细、更安全的方案;bindProcessToNetwork是粗粒度(影响整个进程),且当 network 断开时所有通过该绑定创建的 sockets 都会失败。(IUT de Sénart/Fontainebleau)
Permissions / 权限 / 行为差异
- 调用这类网络请求/绑定相关的 API 时,常见问题来自缺少必要的 Manifest 权限:
android.permission.INTERNET:进行网络操作时必需。没有这个权限,绑定会失败(或返回 false)。(Android Developers Blog)- 请求/修改网络状态(例如
requestNetwork/ 改变网络状态)在某些 Android 版本或情况下需要CHANGE_NETWORK_STATE(以及针对某些 OEM/Android 版本的行为差异)。实务中如果遇到onAvailable不触发或绑定失败,应检查这些权限与运行时条件。(Stack Overflow)
- VPN / 平台限制:正在运行的 VPN、某些 OEM ROM(厂商定制)或系统策略可以影响绑定能否成功或流量走向(有时
bindProcessToNetwork返回 false 或行为异常)。不少社区讨论/issue 提到 VPN 会阻止或影响绑定。遇到异常设备时要验证是否与 VPN/ROM 有关。(Qiita)
典型 Kotlin 使用范例(完整流程)
下面给出一个推荐流程(检查 API level、构造 NetworkRequest、注册回调、在 onAvailable 绑定、在 onLost 清理)。代码尽量简洁并包含关键注释——按你之前偏好我用 Kotlin(传统视图/非 Compose)。
// 在 Activity/Service/某个 Manager 中
private val cm: ConnectivityManager by lazy {
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
// 在回调中绑定进程到该 Network(必须在 onAvailable 中做)
val success = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cm.bindProcessToNetwork(network) // API 23+
} else {
// Lollipop 上使用旧 API(如果需要兼容)
@Suppress("DEPRECATION")
cm.setProcessDefaultNetwork(network)
}
Log.i("NetBind", "bindProcessToNetwork -> $success")
// 额外:若你只想对部分请求生效,请考虑不要在这里做进程绑定,
// 而是使用 network.getSocketFactory() 或 network.bindSocket(socket)
}
override fun onLost(network: Network) {
// 如果绑定的是同一个 network,应当清除绑定(传 null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cm.bindProcessToNetwork(null)
} else {
@Suppress("DEPRECATION")
cm.setProcessDefaultNetwork(null)
}
Log.i("NetBind", "network lost -> cleared process binding")
}
}
fun requestSpecificNetwork() {
// 例如:我们想要 Wi-Fi 类型且带某些能力的网络(按需修改)
val req = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
// .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // 视情况而定
.build()
cm.requestNetwork(req, networkCallback)
}
// 调用时也要记得在适当时机 unregisterNetworkCallback
fun stopRequesting() {
cm.unregisterNetworkCallback(networkCallback)
// 并确保已经清除 bind(上面的 onLost 也会做)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cm.bindProcessToNetwork(null)
} else {
@Suppress("DEPRECATION")
cm.setProcessDefaultNetwork(null)
}
}
要点:
- 必须在
onAvailable()中去绑定;在回调外调用可能失败(因为 network 未就绪)。(Qiita) requestNetwork需要NetworkCallback,并注意超时 /onUnavailable()的处理(不同 Android 版本在超时行为上存在差异)。(Medium)
更细粒度的替代:按 Socket 或使用 Network.getSocketFactory()
当你只想把某部分流量发到特定 network(例如仅连接局域网设备,不影响其它系统请求)时,优先使用这种方式:
Network.getSocketFactory():通过它创建 socket 或构造OkHttp/ HTTP 客户端使用的SocketFactory,让该 socket 走指定网络。Network.bindSocket(socket):把已创建的 socket 绑定到该 Network。Network.getAllByName(hostname):在该网络的 DNS 上解析 hostname(文档提到getAllByName优于全局解析)。(Microsoft Learn)
简单示例(手工 socket -> 发起 HTTP GET,示意):
// 假设 network 是 onAvailable 中提供的 Network
val socket = Socket()
network.bindSocket(socket) // 将 socket 绑定到这个 network
socket.connect(InetSocketAddress("192.168.4.1", 80), 5000)
// 之后可以通过 socket.getInputStream()/getOutputStream() 自行发送 HTTP 请求
// (或者使用 network.getSocketFactory().createSocket() 的方式)
优点:不会影响整个进程,也更容易在 network 断开时局部恢复。官方文档建议在可能时优先使用这种方式。(IUT de Sénart/Fontainebleau)
DNS 与解析行为
- 绑定进程后,进程级的 host name 解析将被限制到该 Network 的 DNS(这也是为什么整个进程绑定后,DNS 查询可能失败或解析不同)。若只想在某个 network 上解析某个 host,优先使用
Network.getAllByName()(针对 network 的解析接口)。(Android Git Repositories)
进程绑定的风险 / 常见坑(实践经验与社区问题)
- 网络断开风险:如果你把进程绑定到一个刚好没有 Internet 的 Wi-Fi,且那个 network 断开/不可用,所有通过该绑定创建的 sockets 都会“停止工作”,DNS 解析会失败。必须在
onLost中清理或实现重试逻辑。(IUT de Sénart/Fontainebleau) - 必须在回调内绑定:很多人把
bindProcessToNetwork放在没有网络的时刻调用,导致返回 false。正确做法是在NetworkCallback.onAvailable中调用。(Qiita) - 权限/厂商差异:某些机型/ROM 有 bug 或强制行为(例如在连接到某些 Wi-Fi 时系统会立刻切换到蜂窝并断 Wi-Fi),以及 VPN 会影响绑定,遇到奇怪问题要在多台机(含 Pixel)上验证。社区讨论与 issue 很多。(GitHub)
- 旧 API 的兼容:Lollipop 有
setProcessDefaultNetwork(deprecated),在 M(API23)后使用bindProcessToNetwork。如果要兼容旧设备需写分支。(Android Developers Blog)
调试技巧
- 在
onAvailable/onLost打 log(打印Network的toString()、network.hashCode()等),确认回调何时触发。(Android Developers) - 用
getBoundNetworkForProcess()(API 23+)检查当前进程是否真的绑定到了某个 network(返回Network?)。(注意:有版本差异)(Android Git Repositories) - 如果 socket 失败,确认是否因为 DNS 解析失败(尝试用
network.getAllByName()在回调中解析域名来验证)。(Android Git Repositories) - 测试时尽量在没有 VPN 的干扰下验证;若用户设备有节流/省电策略也会导致奇怪行为。(Qiita)
何时不要用 bindProcessToNetwork
- 你只需要对单个连接或少量连接控制路由时,不要使用进程绑定(用 socket 绑定或 socketFactory)。
- 不要把整个应用长期绑定到一个没有 Internet 的 Wi-Fi(除非你知道所有行为并在 lost 时可靠清理),因为会影响第三方 SDK、后台任务等。(IUT de Sénart/Fontainebleau)
参考与重要出处(便于深入阅读)
- Android 官方 Connectivity 文档与网络操作指南(NetworkRequest / 绑定示例):Android Developers(Connectivity / reading-network-state / example)。(Android Developers)
- 官方博客示例:Connecting your App to a Wi-Fi Device(讲解
setProcessDefaultNetwork→bindProcessToNetwork的过渡与示例)。(Android Developers Blog) - API reference(说明
bindProcessToNetwork行为、何时清除绑定与返回含义,已标注添加 API level):各种 API 文档条目(ConnectivityManager 参考)。(IUT de Sénart/Fontainebleau) - NDK / 底层接口说明(表明该功能在 API 23 引入,及对应的底层函数):NDK networking reference。(Android Developers)
- 社区讨论 / 常见坑(StackOverflow / Qiita / GitHub issues),说明 VPN/某些机型与回调时机等实务问题。(Stack Overflow)
快速总结(要点)
bindProcessToNetwork(network):把整个进程绑定到network,影响新建 sockets 与 DNS。API 23+。(IUT de Sénart/Fontainebleau)- 推荐流程:
requestNetwork→ 在onAvailable中bindProcessToNetwork(network)→ 在onLost中bindProcessToNetwork(null);补充处理onUnavailable/超时。(Android Developers) - 若只需部分流量走特定网络,优先使用
Network.getSocketFactory()或Network.bindSocket()(更细粒度,风险更小)。(Microsoft Learn) - 注意权限、VPN、OEM 差异与网络断开时的资源清理。(Stack Overflow)