Android静态ip设置的坑
Android静态ip设置,对于这个功能,如果没有接触过,会给人感觉是个特别简单的功能,直接调用系统的接口即可,其实这个功能还是有许多坑的,因为谷歌在Android SDK中对相关的API进行非系统层的隐藏,打上了@hide标签,使得上层无法直接调用现有的接口,其中隐藏的API包含:EthernetManager,IpConfiguration,StaticIpConfiguration等接口,其中有的API虽然提供,但是关键的接口却被隐藏了。导致本来简单功能,变得无比繁琐,因此本文就对静态ip设置的坑做个总结,为相关开发者提供一个简易的参考。
解决Android静态ip设置的问题,可以从两个方面着手:
一方面有系统层接口的支持
系统接口也就是直接由系统层提供接口,一般是framework jar包上层直接调用接口传参,但是需要调整Gradle中的SDK的加载顺序,保证framework包正确替换AndroidSDK的jar包,否则编译会报错。
另一方面就是App层自己使用java的动态机制
动态机制即java的反射,通过反射调用系统隐藏的接口,但是这种方式发现在Android6以下的版本是可以的,Android6以上的版本虽然设置成功,设置里面参数也改了,但是重启你会发现,已经设置的静态模式会自动切换为动态模式,即系统不做数据存储保存,只是零时保存在内存中,所以你重启以后就被系统还原了,参考GitHub分析是高版本谷歌拦截了反射机制,不让非系统层修改数据,而且这个拦截是在C++层进行的,特别恶心,你绕都绕不过去,不过如果你的应用是Launcher应用的话,可以在App层做取巧,就是记录用户重启前是否做静态ip的保存,如果用户操作了静态ip保存,那么App启动的时候在Application的onCreate方法中下发静态ip配置,保证重启前静态ip状态和重启后的状态一致就能解决这个问题。以下就静态ip设置从系统层,App层两个方面做简要的代码实现分析:
1.系统层的实现:就是系统提供的接口直接调用,思路是先创建EthernetManager对象,接着创建StaticIpConfiguration对象,再使用StaticIpConfiguration对象创建IpConfiguration对象,最后将IpConfiguration设置给EthernetManager中即可,代码如下:
public class FEthernetUtil { private static EthernetManager mEthManager; private static StaticIpConfiguration mStaticIpConfiguration; private static IpConfiguration mIpConfiguration; @SuppressLint("WrongConstant") public static int setStaticIp(Context context, String iFace, String ip, String gateway, String netmask, String dns1, String dns2) {//获取EthernetManager对象 mEthManager = (EthernetManager) context.getSystemService("ethernet"); if (setStaticIpConfiguration(ip, gateway, netmask, dns1, dns2)) { mEthManager.setConfiguration(iFace, mIpConfiguration); return 0; } return 1; } private static boolean setStaticIpConfiguration(String ip, String gateway, String netmask, String dns1, String dns2) { Inet4Address inetAddr = (Inet4Address) InetAddresses.parseNumericAddress(ip); int prefixLength = maskStr2InetMask(netmask); InetAddress gatewayAddr = InetAddresses.parseNumericAddress(gateway); InetAddress dnsAddr = InetAddresses.parseNumericAddress(dns1); if (null == inetAddr || inetAddr.getAddress().toString().isEmpty() || prefixLength == 0 || gatewayAddr.toString().isEmpty() || dnsAddr.toString().isEmpty()) { return false; } String dnsStr2 = dns2; ArrayList dnsAddrs = new ArrayList(); dnsAddrs.add(dnsAddr); if (!dnsStr2.isEmpty()) { dnsAddrs.add(InetAddresses.parseNumericAddress(dns2)); } mStaticIpConfiguration = new StaticIpConfiguration.Builder().setIpAddress(new LinkAddress(inetAddr, prefixLength)).setGateway(gatewayAddr).setDnsServers(dnsAddrs).build(); mIpConfiguration = new IpConfiguration(); mIpConfiguration.setIpAssignment(IpConfiguration.IpAssignment.STATIC); mIpConfiguration.setProxySettings(IpConfiguration.ProxySettings.NONE); mIpConfiguration.setStaticIpConfiguration(mStaticIpConfiguration); return true; } private static int maskStr2InetMask(String maskStr) { StringBuffer sb; String str; int inetmask = 0; int count = 0; Pattern pattern = Pattern.compile("(^((\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])$)|^(\\d|[1-2]\\d|3[0-2])$"); if (pattern.matcher(maskStr).matches() == false) { return 0; } String[] ipSegment = maskStr.split("\\."); for (String s : ipSegment) { sb = new StringBuffer(Integer.toBinaryString(Integer.parseInt(s))); str = sb.reverse().toString(); count = 0; for (int i = 0; i < str.length(); i++) { i = str.indexOf("1", i); if (i == -1) break; count++; } inetmask += count; } return inetmask; }}
拿到系统层的jar包,跟使用其他jar一样进行gradle依赖配置,需要注意是gradle中的jar包加载顺序修改:
plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android'} ......dependencies { implementation 'androidx.core:core-ktx:1.8.0' api(files("libs/framework.jar"))//系统层配置静态ip的jar包}gradle.projectsEvaluated { tasks.withType(JavaCompile) { Set fileSet = options.bootstrapClasspath.getFiles(); List newFileList = new ArrayList<>() newFileList.add(new File("libs/framework.jar"))//加载本地系统jar包 newFileList.addAll(fileSet) options.bootstrapClasspath = files(newFileList.toArray()) }}//调整jar包的顺序preBuild { doLast { def imlFile = file(project.name + ".iml") println('Change ' + project.name + '.iml order') try { def parsedXml = (new XmlParser()).parse(imlFile) def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' } parsedXml.component[1].remove(jdkNode) def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform" new groovy.util.Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK']) groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile)) } catch (FileNotFoundException e) { } }
2.App层的实现:App层实现通过反射来操作,实现思路其实和系统层实现是一样的逻辑,首先需要先获取EthernetManager对象,接着创建StaticIpConfiguration对象,再根据StaticIpConfiguration对象反射创建,IpConfiguration对象,和系统层对比App层是使用反射来获取对象而已逻辑是一样的:以下是代码实现:
class EthernetUtil @Inject constructor( @ApplicationContext private val context: Context, private val ipConfigRepository: IPConfigRepository, private val guideRepository: GuideRepository) { fun setEthernetStaticIp( address: String, mask: String, gate: String, dns: String, dns2: String ): Boolean { d(TAG, "setEthernetStaticIp --> ip: $address, mask: $mask, gate: $gate, dns: $dns, dns2: $dns2") return try { val ethernetManagerCls = Class.forName("android.net.EthernetManager") //获取EthernetManager实例 @SuppressLint("WrongConstant") val ethManager = getApp().getSystemService("ethernet") val ipInfo = ipConfigRepository.getDefaultIp() val primaryDNS = if (dns.isNullOrEmpty() || dns.isNullOrBlank()) { ipInfo.IPAddress?.PrimaryDNS?.ipAddress } else { dns } val secondaryDNS = if (dns2.isNullOrEmpty() || dns2.isNullOrBlank()) { ipInfo.IPAddress?.SecondaryDNS?.ipAddress } else { dns2 } //创建StaticIpConfiguration val staticIpConfiguration = newStaticIpConfiguration(address, gate, mask, primaryDNS ?: "", secondaryDNS ?: "") //创建IpConfiguration val ipConfiguration = newIpConfiguration(staticIpConfiguration) //获取EthernetManager的setConfiguration() val setConfigurationMethod = ethernetManagerCls.getDeclaredMethod( "setConfiguration", *arrayOf(NET_ETH_0.javaClass, ipConfiguration.javaClass) ) //保存静态ip设置 saveIpSettings(getApp(), address, mask, gate, dns, dns2)//保存和硬终端一致dns为空也配置 //设置静态IP setConfigurationMethod.invoke(ethManager, NET_ETH_0, ipConfiguration) guideRepository.guideStatus = true true } catch (e: Exception) { e.printStackTrace() false } } @Throws(Exception::class) private fun newStaticIpConfiguration( address: String, gate: String, mask: String, dns: String, dns2: String ): Any { val staticIpConfigurationCls = Class.forName("android.net.StaticIpConfiguration") //实例化StaticIpConfiguration val staticIpConfiguration = staticIpConfigurationCls.newInstance() val ipAddress = staticIpConfigurationCls.getField("ipAddress") val gateway = staticIpConfigurationCls.getField("gateway") val domains = staticIpConfigurationCls.getField("domains") val dnsServers = staticIpConfigurationCls.getField("dnsServers") //设置ipAddress ipAddress[staticIpConfiguration] = newLinkAddress(address, mask) //设置网关 gateway[staticIpConfiguration] = InetAddress.getByName(gate) //设置掩码 domains[staticIpConfiguration] = mask //设置dns1 val dnsList = dnsServers[staticIpConfiguration] as ArrayList dnsList.add(InetAddress.getByName(dns)) //设置dns2 dnsList.add(InetAddress.getByName(dns2)) return staticIpConfiguration } @Throws(Exception::class) private fun newLinkAddress(address: String, mask: String): Any? { val linkAddressCls = Class.forName("android.net.LinkAddress") val linkAddressConstructor = linkAddressCls.getDeclaredConstructor( InetAddress::class.java, Int::class.java ) d(TAG, "子网掩码参数:ip: ${InetAddress.getByName(address)} ${maskStr2InetMask(mask)}") return linkAddressConstructor.newInstance( InetAddress.getByName(address), maskStr2InetMask(mask) ) } @Throws(Exception::class) private fun newIpConfiguration(staticIpConfiguration: Any): Any { val ipConfigurationCls = Class.forName("android.net.IpConfiguration") val ipConfiguration = ipConfigurationCls.newInstance() //设置StaticIpConfiguration val staticIpConfigurationField = ipConfigurationCls.getField("staticIpConfiguration") staticIpConfigurationField[ipConfiguration] = staticIpConfiguration //获取ipAssignment、proxySettings的枚举值 val ipConfigurationEnum = getIpConfigurationEnum(ipConfigurationCls) //设置ipAssignment val ipAssignment = ipConfigurationCls.getField("ipAssignment") ipAssignment[ipConfiguration] = ipConfigurationEnum["IpAssignment.STATIC"] //设置proxySettings val proxySettings = ipConfigurationCls.getField("proxySettings") proxySettings[ipConfiguration] = ipConfigurationEnum["ProxySettings.STATIC"] return ipConfiguration } private fun getIpConfigurationEnum(ipConfigurationCls: Class<*>): Map { val enumMap: MutableMap = HashMap() val enumClass = ipConfigurationCls.declaredClasses for (enumC in enumClass) { val enumConstants = enumC.enumConstants ?: continue for (enu in enumConstants) { enumMap[enumC.simpleName + "." + enu.toString()] = enu } } return enumMap } private fun saveIpSettings( context: Context, address: String, mask: String, gate: String, dns: String, dns2: String ) { val contentResolver = context.contentResolver Settings.Global.putString(contentResolver, "ethernet_static_ip", address) Settings.Global.putString(contentResolver, "ethernet_static_mask", mask) Settings.Global.putString(contentResolver, "ethernet_static_gateway", gate) Settings.Global.putString(contentResolver, "ethernet_static_dns1", dns) Settings.Global.putString(contentResolver, "ethernet_static_dns2", dns2) } private fun maskStr2InetMask(maskStr: String): Int { var sb: StringBuffer var str: String var inetmask = 0 var count = 0 val pattern: Pattern = Pattern.compile("(^((\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])$)|^(\\d|[1-2]\\d|3[0-2])$") if (pattern.matcher(maskStr).matches() === false) { d(TAG, "subMask is error") return 0 } val ipSegment = maskStr.split("\\.".toRegex()).toTypedArray() for (n in ipSegment.indices) { sb = StringBuffer(Integer.toBinaryString(ipSegment[n].toInt())) str = sb.reverse().toString() count = 0 var i = 0 while (i < str.length) { i = str.indexOf("1", i) if (i == -1) break count++ i++ } inetmask += count } return inetmask }
来源地址:https://blog.csdn.net/DZMNLFH/article/details/132568731