Android端WIFI/流量共存技术方案

1 需求背景

最近在做一个和IOT设备相关的App,App和设备会连接到IOT设备的热点Wifi上,但是该Wifi无法上网。同时App有和后端服务器进行交互的需求,因此又需要APP能够同时通过流量上网。

2 技术调研

下面这三个概念先熟悉一下, 后面的方案和这三个息息相关:

  • ConnectivityManager: Android提供的操作手机网络相关的系统服务,可以连接Wifi,查询手机的网络信息;
  • Network: 表示一种网络配置的基本信息单元,简单理解,WiFi是一个Network, 流量是另一个Network;
  • Socket: 这里既指Java层的Socket,也包括NDK中通过socket()创建的socketFD;

ConnectivityManager提供API查询当前手机连接有哪些Network,以及bindProcessToNetwork, bindSocket, createSocket等API来更精细化的控制网络通信:

  • bindProcessToNetwork: 将当前App进程绑定到某一个Network,即app中所有的网络通信都会通过这个Network进行处理;
  • bindSocket: 将当前进程创建的某一个socket绑定到某一个Network,即这个socket的通信流程通过这个Network处理,App中的其余网络通信还是通过系统默认Network处理;
  • createSocket: 创建一个和某个Network绑定的socket,这个socket的网络通信只能通过这个Network进行处理;

例如获取并绑定APP网络到流量:

kotlin 复制代码
 fun bindToCellular(context: Context, success: Consumer<Boolean>) {
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val builder = NetworkRequest.Builder()
    builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    val request = builder.build()
    val callback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            connectivityManager.bindProcessToNetwork(network)
            success.accept(true)
        }
    }
    connectivityManager.requestNetwork(request, callback)
} 

获取已连接的Wifi:

kotlin 复制代码
 fun getWifiNetwork(context: Context): Network? {
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    for (network in connectivityManager.allNetworks) {
        val networkInfo = connectivityManager.getNetworkInfo(network)
        if (networkInfo != null && ConnectivityManager.TYPE_WIFI == networkInfo.type && networkInfo.isConnected) {
            return network
        }
    }
    return null
}

App在连接Wifi之后,一般都需要强制使用bindProcessToNetwork将App绑定到这个Wifi上,否则有的版本上Wifi会很快断开连接。因此我们的网络交互流程可能会变成下面这样子:

rust 复制代码
 扫描Wifi -> 连接Wifi -> 绑定Wifi -> 绑定流量。 

我们期望的是同时绑定了Wifi和流量之后,App能够自动根据我们的网络请求决定是走Wifi还是流量。但是现实却不是这样的。在Google的设计中,bindProcessToNetwork只能让App绑定在一个Network上,多次调用,会以最后绑定的为主:

  • 绑定Wifi -> 绑定流量, 只会走流量;
  • 绑定流量 -> 绑定Wifi, 只会走Wifi。

3 技术方案

如果只是一个纯Java层的App,不涉及到NDK中使用网络通信,那么直接采用如下方案应该可以解决问题:

绑定Wifi -> 绑定流量, 然后在Okhttp中添加一个自定义的SocketFactory,将SocketFactory创建的Socket和Wifi进行绑定,总之就是将创建的socket通过Network中的bindSocket和Wifi进行绑定即可。

比较复杂的是我的App中使用了大量的NDK的代码,在NDK中使用了一些三方库,例如FFMpeg。这些三方库直接通过socket(), connect()这些系统调用API来进行网络通信。

因此是不是也有办法将NDK中创建的socketfd和wifi network进行绑定成为了解决这个问题的核心。

第一步: 如何拿到这些fd? 对使用的三方库的api一顿搜索,有的暴露了socket创建的回调接口,有的完全就没有暴露(比如FFMpeg)。没有暴露接口的只能把源码拿下来,找到内部创建socket的地方,自己添加个回调接口进去把fd传出来。

第二步: 拿到fd之后,如何进行绑定。

  • 通过jni把fd传出去到java层,创建一个FileDescriptor(), FileDescriptor也可以和network进行绑定;

  • 是否可以直接在jni进行绑定: 查了一下,Android刚好提供了一个这样的接口:

    arduino 复制代码
    int android_setsocknetwork(net_handle_t network, int fd)

    该接口的第一个参数就是Network中的handle字段,因此直接通过public long getNetworkHandle();将该字段从java层传下来即可。

上述两种方案都试了一下,测试ok。比较坑的是注意socketfd回调出来的时机: 必须在socket connect之前,不然绑定是不会生效的。

相关推荐
louisgeek15 分钟前
Android NSD 网络服务发现
android
张可1 小时前
历时两年半开发,Fread 项目现在决定开源,基于 Kotlin Multiplatform 和 Compose Multiplatform 实现
android·前端·kotlin
余辉zmh1 小时前
【Linux系统篇】:信号的生命周期---从触发到保存与捕捉的底层逻辑
android·java·linux
孤鸿玉2 小时前
[Flutter小试牛刀] 低配版signals,添加多层监听链
android·前端·响应式设计
雨和卡布奇诺2 小时前
LiveData源码浅析
android
淡蓝色_justin2 小时前
Hilt-plus 简介
android·android jetpack
app1e2342 小时前
ctfshow web入门 命令执行(29-77)
android·前端
恋猫de小郭5 小时前
Flutter 在 Dart 3.8 开始支持 Null-Aware Elements 语法,自动识别集合里的空元素
android·前端·flutter
fatiaozhang95275 小时前
咪咕MG101_晨星MSO9380芯片_安卓5.1.1_免拆卡刷固件包
android·电视盒子·av1·机顶盒rom·魔百盒刷机