美文网首页
Cronet双网卡逻辑

Cronet双网卡逻辑

作者: Magic旭 | 来源:发表于2023-04-23 11:35 被阅读0次

场景

为了追求极致的用户体验,每个app都耗尽脑汁想尽办法优化自身,特别是网络卡顿时候的体验,期待在wifi卡顿情况下,通过白名单控制域名走用户的蜂窝网络通道。这期主要分享下wifi连接情况下,如何用蜂窝网发送相应的请求。后续还会着重分析一个问题,卡了自己好久的问题,后面也是看了android官方文档解释才明白为啥出这个问题。

介绍

一、系统Api
首先我们要了解下系统的api实现,主要就是Network#bindSocket方法,这个方法是获取对应通道的network对象,再把客户端网络传输的socket绑定到对应通道的network对象上,从而把对应的网络请求切换到不同通道上,实现双网卡交互的逻辑。


二、Cronet底层如何实现双通道逻辑
了解系统的Api后,我们对整体android的系统Api有个认知,那接下来我根据源码和大家简单分析下Cronet底层socket如何绑定对应的网络通道的。看下面Cronet的代码,可以看出Cronet c++底层也是加载native库,通过打开Android底层的文件句柄,直接访问Network#bindSocket的C++方法,所以说为什么我在第一步先介绍Android系统的Api,各种框架八九不离十都会和系统的api打交道的,只不过是分走java层还是走C++层而已。

//大于等于android 6.0
MarshmallowSetNetworkForSocket GetMarshmallowSetNetworkForSocket() {
  // On Android M and newer releases use supported NDK API.
  base::FilePath file(base::GetNativeLibraryName("android"));
  // See declaration of android_setsocknetwork() here:
  // http://androidxref.com/6.0.0_r1/xref/development/ndk/platforms/android-M/include/android/multinetwork.h#65
  // Function cannot be called directly as it will cause app to fail to load on
  // pre-marshmallow devices.
  void* dl = dlopen(file.value().c_str(), RTLD_NOW);
  return reinterpret_cast<MarshmallowSetNetworkForSocket>(
      dlsym(dl, "android_setsocknetwork"));
}

//小于android 6.0
LollipopSetNetworkForSocket GetLollipopSetNetworkForSocket() {
  // On Android L use setNetworkForSocket from libnetd_client.so. Android's netd
  // client library should always be loaded in our address space as it shims
  // socket().
  base::FilePath file(base::GetNativeLibraryName("netd_client"));
  // Use RTLD_NOW to match Android's prior loading of the library:
  // http://androidxref.com/6.0.0_r5/xref/bionic/libc/bionic/NetdClient.cpp#37
  // Use RTLD_NOLOAD to assert that the library is already loaded and avoid
  // doing any disk IO.
  void* dl = dlopen(file.value().c_str(), RTLD_NOW | RTLD_NOLOAD);
  return reinterpret_cast<LollipopSetNetworkForSocket>(
      dlsym(dl, "setNetworkForSocket"));
}

Cronet支持双通道链接
Android 6.0以上的C++方法android_setsocknetwork方法


三、Cronet如何使用的
这里从tcp协议分析Cronet如何指定网络通道。下面代码可以看出函数参数需要一个叫NetworkHandle一个对象,其实这个对象就是Java的Network的id,这个Android系统api也有方法可以参考copy一份的,那么到这里整个链路都比较清晰了。要Cronet走不同的网络通道,首先要获取对应通道的Network的id,其次通过id在Cronet决定走tcp/udp协议后,本地创建的socket绑定对应的id,就可以实现不同网络通道传输啦。

//tcp socket bind network
int TCPSocketPosix::BindToNetwork(handle::NetworkHandle network) {
  DCHECK(IsValid());
  DCHECK(!IsConnected());
  VLOG(1) << "cellular BindToNetwork: " << network;
#if defined(OS_ANDROID)
  return android::BindToNetwork(socket_->socket_fd(), network);
#else
  NOTIMPLEMENTED();
  return ERR_NOT_IMPLEMENTED;
#endif  // #if defined(OS_ANDROID)
}

TCP的BindToNetwork的代码


四、如何获取对应的Network的id
我这里其实不是最好的实现,因为我也是看网上和官网的文档后,只能想到这种方式去获取。但这个方式有个致命缺陷,需要用户同意打开某个高级权限才可以获取到对应的Network的id。但是微信是可以做到不需要用户打开权限,网络差的情况下直接走蜂窝网通道拉取消息,这个我暂时没想到能咋整。
下面代码需要申请权限:
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
主要是WRITE_SETTINGS在高版本手机需要用户主动选择打开才行,动态获取Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)就可以跳转到设置页面,让用户去打开了。

public class NetworkHelper {
    private static final String TAG = "NetworkIdHelper";
    /**
     * Network handle representing the default network. To be used when a network has not been * explicitly set.
     */
    private static final long DEFAULT_NETWORK_HANDLE = -1;
    private static NetworkCallbackImpl _networkCallback = null;
    private static Network _network = null;
    private static long _networkId = DEFAULT_NETWORK_HANDLE;
    private static ConnectivityManager _connectivityManager = null;

    public static void init(Context context) {
        Log.i(TAG, "init");
        _connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    public static void openMobileNetwork(AvailableNetworkCallback callback) {
        Log.i(TAG, "openMobileNetwork");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (_connectivityManager != null) {
                try {
                    NetworkRequest.Builder builder = new NetworkRequest.Builder();
                    builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
                    NetworkRequest request =
                            new NetworkRequest.Builder()
                                    // 设置指定的⽹络传输类型(蜂窝传输) 等于⼿机⽹络
                                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                                    // 设置感兴趣的⽹络功能
                                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
                    _networkCallback = new NetworkCallbackImpl(_connectivityManager, callback);
                    _connectivityManager.requestNetwork(request, _networkCallback);
                } catch (Exception e) {
                    Log.i(TAG, "openMobileNetwork error: " + e.getMessage());
                }
            }
        }
    }

    public static void closeMobileNetwork() {
        Log.i(TAG, "closeMobileNetwork");
        if (_connectivityManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                _connectivityManager.unregisterNetworkCallback(_networkCallback);
            }
        }
    }

    private static synchronized long getNetworkToNetId() {
        if (_network != null) {
            _networkId = Build.VERSION.SDK_INT >= 23 ? _network.getNetworkHandle() :
                    (long) Integer.parseInt(_network.toString());
        }
        return _networkId;
    }

    public static long networkToNetId() {
        if (_networkId == DEFAULT_NETWORK_HANDLE) {
            _networkId = getNetworkToNetId();
        }
        Log.i(TAG, "cellular networkToNetId _networkId: " + _networkId);
        return _networkId;
    }

    public interface AvailableNetworkCallback {
        void onAvailable(long networkId);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static class NetworkCallbackImpl extends ConnectivityManager.NetworkCallback {
        final ConnectivityManager connectivityManager;
        AvailableNetworkCallback availableNetworkCallback;

        public NetworkCallbackImpl(ConnectivityManager connectivityManager, AvailableNetworkCallback callback) {
            this.connectivityManager = connectivityManager;
            this.availableNetworkCallback = callback;
        }

        @Override
        public void onAvailable(Network network) {
            super.onAvailable(network);
            Log.i(TAG, "Mobile Network Available");
            _network = network;
            networkToNetId();
            if (_networkId != DEFAULT_NETWORK_HANDLE) {
                availableNetworkCallback.onAvailable(_networkId);
            }
        }

        @Override
        public void onLost(Network network) {
            super.onLost(network);
            Log.i(TAG, "Mobile Network onLost");
            _network = null;
        }
    }
}

问题

在实际开发过程中遇到个非常棘手问题,在某些国内手机上,获取到对应的network的id,给了Cronet绑定后,Cronet使用的Android系统回调会抛出蜂窝网onLost的回调,然后后续再继续bindSocket的话就会一直失败返回-1,这个问题搞了我一周都没找到解决办法。
后面通过阅读官网的文档才发现,双通道的实现需要依赖手机打开了 始终开启移动数据设置这个开关,如果没有打开这个配置,一段时间后移动网络就会断开连接,常规网络回调将收到对 onLost() 的调用。这些是来自官网的解释,证明如果不开启这个开关是无法实现双网卡的逻辑的。
双网卡官方解答地址

总结

  1. 遇到问题要多点看回android的开发文档,说不定就能找到你的答案了。
  2. 在学习中进步,在实践中收获知识。

相关文章

  • Cronet

    介绍: 接口: 组件及工作流程: 接口基本组件包括: 序号 组件 功能 1 CronetEngine Cronet...

  • Cronet网络库(Quic连接出错篇章一)

    问题 因为业务需求把所有业务的域名都搞成相同的,导致Cronet在底层连接出错的判断逻辑也跟着出错了。原因是域名相...

  • Cronet 使用

    1. 按照https://www.jianshu.com/p/aed96e11fe36[https://www.j...

  • vscode调试 调试so

    背景 编译flutter和cronet(chromium分离)在android --AS 环境中使用会导致调试困难...

  • Cronet代码解析

    从Cronet.mm开始 1. CRNHTTPProtocolHandler来处理拦截的请求 2.CRNHTTPP...

  • Centos7 多网卡绑定实现负载均衡

    常见绑定模式 以双网卡配置示例

  • 双网卡内外网互联

    在企业中经常会遇到即需要连内网,又需要连接互联网,这时就需要双网卡,以windows7为例,双网卡安装好,分别设置...

  • 双网卡

    https://my.oschina.net/7shell/blog/308887http://www.yimij...

  • linux双网卡多网段配置静态路由

    双网卡 eno0 IP :172.20.80.100 掩码:255.255.255.0 网关:172.20.80....

  • 双网卡绑定

    一台服务器同时有安装双网卡,要对网卡进行配置。即进行双网卡绑定。 概念:网卡:一块被设计用来允许计算机在计算机网络...

网友评论

      本文标题:Cronet双网卡逻辑

      本文链接:https://www.haomeiwen.com/subject/ibnejdtx.html