#+AUTHOR 张老虎
前言
由于项目需要使用wifi连接硬件,同时要求能够使用手机网络上网(4G),将方法整理如下
Android实现
原理概述
Android系统在5.1及以上开始支持Wifi和数据网络同时开启,方法有:
- 在Api Level21及以上,代表手机网卡的类Network中有bindSocket()方法,可以将单个Socket绑定至某网卡;
- 或者调用getSocketFactory()方法,直接工厂模式生产Socket;
- 在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的方法,所以对其进行了改造,由于最近七牛改动较大,不合并他们的代码了,源码地址
- 增加了绑定网卡功能
- 原来的上传字节数组没有进度回调,增加上传ByteBuffer接口(含有进度回调)
IPhone
原理
IPhone上在当前网络没有设置DNS服务器时,需要DNS解析的网络请求会自动使用可用网卡,而不需要DNS解析的,还会走当前网卡,
所以是比较简单的。
方法
Wifi连接上硬件后,记下自动分配的IP和子网掩码,修改为静态IP,填写IP和子网掩码,DNS和路由IP都置空。
在应用中,需要与硬件通信的请求,直接指定目的IP和端口,需要联网的HTTP请求,正常使用。
网友评论