吐槽一下,官方貌似没有以太网和WiFi的配置流程,网络设置这部分主要借鉴的是
Settings
中的内容,简单记录下,方便日后 copy
以太网配置需要使用EthernetManager
类,我们看下这个类的特点:
/**
* A class representing the IP configuration of the Ethernet network.
*
* @hide
*/
@SystemApi
@TestApi
@SystemService(Context.ETHERNET_SERVICE)
public class EthernetManager
这个类对普通应用并不开放,需要一些特别的权限
环境准备工作
- 进行静态网络设置需要先把app配置为系统级
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxxx.xxxx"
android:sharedUserId="android.uid.system">
- 由于使用到了
EthernetManager
类,这个类Android
定义为隐藏的,所以我们需要依赖一个framework.jar
。
compileOnly files('frameworks.jar')
compileOnly
即可
- 此时在
AS
中还是会显示找不到EthernetManager
AS
管理的每个module
中有一个.yml
文件- 把
.yml
文件中的framework.jar
的顺序放到最前面就可以了
像这样
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: ./../../fm-libs/ironman_frameworks.jar" level="project" />
//......
<orderEntry type="module" module-name="factory_test_interf" />
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK" />
这样,AS
就会优先查找framework.jar
中的类。
如果启动编译,应该还会出现找不到类的情况,此时我们还需要在Project
的build.gradle
中进行如下配置:
allprojects {
repositories {
//......
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
//......
options.compilerArgs.add('-Xbootclasspath/p:/Users/lijie/fm-libs/frameworks.jar')
}
}
}
这样编译时应该就会查找framework.jar
中的类了
代码开始
- 一个接口类
NetworkConfiguration
,从Settings
中借鉴过来的。
import android.net.IpConfiguration;
import android.net.wifi.WifiManager;
/**
* Provide unified interface for network configuration that works for both Wi-Fi and Ethernet.
*/
public interface NetworkConfiguration {
/**
* Set IpConfiguration
*
* @param configuration IpConfiguration to set
*/
public void setIpConfiguration(IpConfiguration configuration);
/**
* Get IpConfiguration
*
* @return IpConfiguration
*/
public IpConfiguration getIpConfiguration();
/**
* Save current network configuration to system
*
* @param listener listener to notify the result
*/
public void save(WifiManager.ActionListener listener);
/**
* Get printable name for this network.
*
* @return Printable name
*/
public String getPrintableName();
}
- 以太网配置实现类
EthernetConfig
,实现了NetworkConfiguration
接口
import android.content.Context;
import android.net.EthernetManager;
import android.net.IpConfiguration;
import android.net.wifi.WifiManager;
import android.os.Looper;
/**
* Ethernet configuration that implements NetworkConfiguration.
*/
class EthernetConfig implements NetworkConfiguration {
private EthernetManager mEthernetManager;
private IpConfiguration mIpConfiguration;
private String mName;
private String mInterfaceName;
EthernetConfig(Context context) {
if (Thread.currentThread().getId() != context.getMainLooper().getThread().getId()) {
// 由于 EthernetManager 在初始化的时候会启动一个Handler
// 如果不是在主线程中,并且没有调用Looper.prepare(),会出现异常
if (Looper.myLooper() == null) {
Looper.prepare();
}
}
mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
mIpConfiguration = new IpConfiguration();
mName = "Ethernet";
}
@Override
public IpConfiguration getIpConfiguration() {
return mIpConfiguration;
}
@Override
public void setIpConfiguration(IpConfiguration configuration) {
mIpConfiguration = configuration;
}
@Override
public void save(WifiManager.ActionListener listener) {
if (mInterfaceName != null) {
mEthernetManager.setConfiguration(mInterfaceName, mIpConfiguration);
}
if (listener != null) {
listener.onSuccess();
}
}
/**
* Load IpConfiguration from system.
*/
public void load() {
String[] ifaces = mEthernetManager.getAvailableInterfaces();
if (ifaces.length > 0) {
mInterfaceName = ifaces[0];
mIpConfiguration = mEthernetManager.getConfiguration(mInterfaceName);
}
}
@Override
public String getPrintableName() {
return mName;
}
}
- 配置流程工具类
EthernetUtils
,注释很详细了哈
import android.content.Context;
import android.net.IpConfiguration;
import android.net.LinkAddress;
import android.net.StaticIpConfiguration;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;
import java.net.Inet4Address;
import java.util.concurrent.CountDownLatch;
import static com.fengmi.factory_test_interf.sdk_utils.NetworkUtil.netMask2Length;
public final class EthernetUtils {
private static final String TAG = "EthernetUtils";
private static volatile boolean res = false;
private EthernetUtils() {
}
/**
* 设置 IP 获取方式为 DHCP 动态分配
*
* @param context 上下文对象
* @return 配置是否保存成功
*/
public static boolean saveDHCPEthernetConfig(Context context) {
res = false;
//1. 创建一个EthernetConfig对象
EthernetConfig netConfig = new EthernetConfig(context);
//2. load 加载系统配置到对象中
netConfig.load();
//3. 设置IP获取方式为 DHCP
netConfig.getIpConfiguration().setIpAssignment(IpConfiguration.IpAssignment.DHCP);
//4. 设置一个等待latch
CountDownLatch latch = new CountDownLatch(1);
//5. 保存配置到系统,并监听是否设置成功
netConfig.save(new WifiManager.ActionListener() {
@Override
public void onSuccess() {
Log.d(TAG, "set IP assignment DHCP success");
res = true;
latch.countDown();
}
@Override
public void onFailure(int i) {
Log.d(TAG, "set IP assignment DHCP failed, code=" + i);
res = false;
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return res;
}
/**
* 设置以太网静态IP<p/>
* 网上都没能找到正规的流程说明,从Settings中借鉴来的代码<p/>
* 人性化的我添加了注释,吼吼吼<p/>
* 网络配置相关的科普链接{https://blog.51cto.com/zhoutao/93629}
*
* @param context 上下文
* @param ipAddress ip 地址,格式为:10.10.10.10
* @param netMask 子网掩码,格式为:255.255.255.0
* @param gateway 默认网关,格式为:10.10.10.1
* @param dns dns服务地址,google 提供了两个免费的
* @return 配置是否成功
*/
public static boolean saveStaticEthernetConfig(Context context,
String ipAddress,
String netMask,
String gateway,
String dns) {
res = false;
// 创建一个 StaticIpConfiguration 对象,用来保存静态IP相关的信息
StaticIpConfiguration staticConfig = new StaticIpConfiguration();
// 创建一个EthernetConfig对象
EthernetConfig netConfig = new EthernetConfig(context);
// load 加载系统配置到对象中
netConfig.load();
// 关联配置信息对象
netConfig.getIpConfiguration().setStaticIpConfiguration(staticConfig);
// 针对 IP地址 做一些异常判断
if (TextUtils.isEmpty(ipAddress)) {
Log.i(TAG, "empty ipv4 addr");
return false;
}
// 根据子网掩码计算Prefix Length,看看究竟掩护了多少位
// 其实就是计算子网掩码比特位为1的位数。
int networkPrefixLength;
try {
networkPrefixLength = netMask2Length(netMask);
} catch (Exception e) {
e.printStackTrace();
return false;
}
//根据 IP 和 子网掩码的Prefix Length信息 生成 ipAddress 对象
Inet4Address inetAddr;
try {
// 将IP 转化为 Inet4Address 对象
inetAddr = (Inet4Address) android.net.NetworkUtils.numericToInetAddress(ipAddress);
// 将 `Inet4Address 对象` 和 `子网掩码的Prefix Length信息` 打包成LinkAddress对象
// 赋值给 StaticIpConfiguration 对象中的 ipAddress
staticConfig.ipAddress = new LinkAddress(inetAddr, networkPrefixLength);
} catch (IllegalArgumentException | ClassCastException e) {
e.printStackTrace();
return false;
}
// 将网关信息设置给 StaticIpConfiguration 对象中的 gateway
if (!TextUtils.isEmpty(gateway)) {
try {
staticConfig.gateway =
android.net.NetworkUtils.numericToInetAddress(gateway);
} catch (IllegalArgumentException | ClassCastException e) {
e.printStackTrace();
return false;
}
}
//将 dns 添加到 StaticIpConfiguration 对象的 DNS 服务列表中
if (!TextUtils.isEmpty(dns)) {
try {
staticConfig.dnsServers.add(
android.net.NetworkUtils.numericToInetAddress(dns));
} catch (IllegalArgumentException | ClassCastException e) {
e.printStackTrace();
return false;
}
}
// 添加两个Google提供的免费DNS服务器的IP地址
try {
staticConfig.dnsServers.add(
android.net.NetworkUtils.numericToInetAddress("8.8.8.8"));
staticConfig.dnsServers.add(
android.net.NetworkUtils.numericToInetAddress("8.8.4.4"));
} catch (IllegalArgumentException | ClassCastException e) {
e.printStackTrace();
return false;
}
// 设置 IpAssignment 为 Static 模式
netConfig.getIpConfiguration().setIpAssignment(IpConfiguration.IpAssignment.STATIC);
// 设置一个等待latch
CountDownLatch latch = new CountDownLatch(1);
// 保存配置信息到系统,并监听设置结果
netConfig.save(new WifiManager.ActionListener() {
@Override
public void onSuccess() {
Log.d(TAG, "set static IP assignment success");
res = true;
latch.countDown();
}
@Override
public void onFailure(int i) {
Log.d(TAG, "set static IP assignment failed, code=" + i);
res = false;
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return res;
}
/**
* 计算子网掩码的 Prefix Length
*
* @param netMask 子网掩码
* @return Prefix Length
*/
private static int netMask2Length(String netMask) {
byte[] array = netMask2ByteArray(netMask);
if (array == null) {
return -1;
}
int i = array[0] << 24 | array[1] << 16 | array[2] << 8 | array[3];
int j = 32;
int k = 1;
while (((i & k) == 0) && (k != 0)) {
k <<= 1;
j--;
}
return j;
}
/**
* 将子网掩码转化为字节数组
*
* @param str 子网掩码
* @return 字符数组,格式不正确返回null
*/
private static byte[] netMask2ByteArray(String str) {
if (str == null || str.length() == 0) {
return null;
}
StringTokenizer tokenizer = new StringTokenizer(str, ".");
if (tokenizer.countTokens() != 4) {
return null;
}
byte[] arrayOfByte = new byte[4];
for (int i = 0; tokenizer.hasMoreTokens(); i++) {
int j = Integer.parseInt(tokenizer.nextToken());
arrayOfByte[i] = (byte) j;
}
return arrayOfByte;
}
}
总结
想要完成这个功能
- 在
EthernetManager
这种隐藏类的导入上需要费些时间 - 熟悉网络配置相关的名词需要些时间
- IP 地址
- 子网掩码
- 默认网关
- dns
- 熟悉配置流程需要些时间
- 首先加载系统网络配置
- 然后设置对应的网络参数
- 然后设置不同的类型
- DHCP
- STATIC
- 最后保存配置
实测可用,WiFi的实现和这个流程也差不多,功能虽然已经完成,但是在流程上还有些疑惑,就不先贴出来了,嘻嘻!