美文网首页
Android和IOS实现Wifi及4G双通道

Android和IOS实现Wifi及4G双通道

作者: 张老虎 | 来源:发表于2018-10-14 19:46 被阅读322次

    #+AUTHOR 张老虎

    前言

    由于项目需要使用wifi连接硬件,同时要求能够使用手机网络上网(4G),将方法整理如下

    Android实现

    原理概述

    Android系统在5.1及以上开始支持Wifi和数据网络同时开启,方法有:

    1. 在Api Level21及以上,代表手机网卡的类Network中有bindSocket()方法,可以将单个Socket绑定至某网卡;
    2. 或者调用getSocketFactory()方法,直接工厂模式生产Socket;
    3. 在Api Level23及以上(即Android 6.0), 可直接调用ConnectivityManager的bindProcessToNetwork()方法, 将整个进程的网络请求都绑定到某网卡,虽说最方便, 但是目前5.1到6.0之间的手机还有百分之20多(数据来源), 不容小觑,因此没有采用这个方法

    系统设置

    手机为节电,默认设置为打开Wifi即关闭手机网络,需要在开发者选项中打开[始终开启数据连接]才能在Wifi开启情况下获取到4G网卡。
    在应用中可提示用户打开开发者选项,打开后在开发者选项的[网络]分组下,开启[始终开启数据连接
    打开开发者选项跳转方法:

        //判断是否已经开启开发者选项
        val enableAdb = Settings.Secure.getInt(contentResolver, Settings.Global.ADB_ENABLED, 0) > 0
        val i = Intent(Settings.ACTION_SETTINGS)
        if (enableAdb) {
            //已打开, 直接跳转至开发者选项
            i.action = ACTION_APPLICATION_DEVELOPMENT_SETTINGS
        } else {
            //未打开, 跳转到关于手机页面,连续点击版本号至手机提示已打开开发者选项
            //In some cases, a matching Activity may not exist, so ensure you
            //* safeguard against this.
            i.action = ACTION_DEVICE_INFO_SETTINGS
        }
        startActivity(i)
    

    实际使用中,发现小米和华为部分机型需要“修改系统设置”权限才可正常获取网卡信息, 申请权限代码如下:

        if (!Settings.System.canWrite(getApplicationContext())) {
            val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:$packageName"))
            startActivity(intent)
        }
    

    获取网卡方法如下, 此处可以在子线程循环检查,或者在网络变化的广播中接收通知:

        ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        Network etherNetwork = null;
        for (Network network : connectivityManager.getAllNetworks()) {
            NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
            //在所有开启的网卡中过滤4G网卡
            if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                etherNetwork = network;
                LinkProperties lp = connectivityManager.getLinkProperties(network);
                for (InetAddress addr : lp.getDnsServers()) {
                    //保存4G网卡DNS设置,如果需要DNS解析会用到,我是在修改七牛SDK是用到的
                    dnsServers.add(addr);
                }
            }
        }
        if (etherNetwork == null) {
            //没有获取到4G网卡,说明没有开启4G网络或没有打开[始终开启数据连接], 可提示用户去开启
        }
        
        //虽然这里已经获取到4G网络了,但在实际使用中发现某些机型还需要执行下面的requestNetwork(),
        //在回调触发时,4G网络才真正可用,所以上面获取etherNetwork只是判断网卡有没有启用
        NetworkRequest.Builder builder = new NetworkRequest.Builder();
        builder.addCapability(NET_CAPABILITY_INTERNET);
        //强制使用蜂窝数据网络-移动数据
        builder.addTransportType(TRANSPORT_CELLULAR);
        NetworkRequest build = builder.build();
        
        //检查有没有修改系统设置权限
        if (!Settings.System.canWrite(getApplicationContext())) {
            //申请修改系统设置权限,代码在上面,这里不重复了
        } else {
            connectivityManager.requestNetwork(build, new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    super.onAvailable(network);
                    Log.i(TAG, "切换4G网卡"); 
                    //拿到网卡,并且网卡可用,切换网络请求到4G网卡,修改方法见下一个小节
                    ApiManager.getInstance().changeTo4G(PhotoApplication.this, network, dnsServers);
                }
            });
        }
    

    部分第三方框架

    Retrofit、Glide和七牛SDK都是基于OkHTTP的,因此重点在于OKHTTP的网络切换,我使用的OKHTTP版本3.9.1, 可以设置SocketFactory和DNS解析器,
    SocketFactory直接从Network中获取,下面还需要一个DNS解析器,我基于七牛的Happy-DNS改了一个

    七牛Happy-DNS

    源码放在github, 构造方法没变,修改了DnsManager.queryInetAddress()方法,增加了一个Network参数, 用法见OKHTTP设置方法

    OKHTTP设置方法

        IResolver r1 = AndroidDnsServer.defaultResolver();
        IResolver r2 = null;
        try {
            r2 = new Resolver(InetAddress.getByName("119.29.29.29"));
        } catch (UnknownHostException ex) {
            //这里其实不会触发, 传入的不是Host而是IP
            ex.printStackTrace();
        }
        final DnsManager dns = new DnsManager(new NetworkInfo(NetworkInfo.NetSatus.MOBILE, NetworkInfo.ISP_GENERAL, dnsServers), arrayOf(r1, r2), null);
        OkHttpClient okClientData = new OkHttpClient.Builder()
                               .socketFactory(net4G.getSocketFactory())
                               .dns(new Dns() {
                                   @Override
                                   public List<InetAddress> lookup(String hostname)  {
                                       //DNSManager修改的地方在这里使用
                                       return listOf(dns.queryInetAdress(new Domain(hostname), net4G));
                                   }})
                               .build();
    

    Retrofit及其他一些寄语OKHTTP的网络请求库

    修改了OKHTTP,Retrofit就没什么可说了,直接传入上面构造好的OkHttpClient就行了

    Glide

    Glide使用OKHttp,要在build.gradle中添加依赖:

        implementation('com.github.bumptech.glide:okhttp3-integration:4.5.0') {
                exclude group: 'glide-parent'
        }
    

    然后就简单啦, 传入上面创建好的OKHTTPClient

        val factory = OkHttpUrlLoader.Factory(okHttpClient)
        Glide.get(context).registry.replace(GlideUrl::class.java, InputStream::class.java, factory)
    

    七牛SDK改造

    七牛本来的SDK没有传入OKHTTPClient的方法,所以对其进行了改造,由于最近七牛改动较大,不合并他们的代码了,源码地址

    1. 增加了绑定网卡功能
    2. 原来的上传字节数组没有进度回调,增加上传ByteBuffer接口(含有进度回调)

    IPhone

    原理

    IPhone上在当前网络没有设置DNS服务器时,需要DNS解析的网络请求会自动使用可用网卡,而不需要DNS解析的,还会走当前网卡,
    所以是比较简单的。

    方法

    Wifi连接上硬件后,记下自动分配的IP和子网掩码,修改为静态IP,填写IP和子网掩码,DNS和路由IP都置空。
    在应用中,需要与硬件通信的请求,直接指定目的IP和端口,需要联网的HTTP请求,正常使用。

    相关文章

      网友评论

          本文标题:Android和IOS实现Wifi及4G双通道

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