【发布时间】:2011-03-05 10:49:02
【问题描述】:
我正在尝试获取应用程序中当前使用的 DNS 服务器的地址,无论是通过 Wifi 还是移动设备连接。 DhcpInfo 对象应该提供这个,但是我怎样才能获得一个有效的 DhcpInfo 对象呢?
【问题讨论】:
标签: android
我正在尝试获取应用程序中当前使用的 DNS 服务器的地址,无论是通过 Wifi 还是移动设备连接。 DhcpInfo 对象应该提供这个,但是我怎样才能获得一个有效的 DhcpInfo 对象呢?
【问题讨论】:
标签: android
调用getRuntime().exec 可以hang 您的应用程序。
android.net.NetworkUtils.runDhcp() 导致不必要的网络请求。
所以我更喜欢这样做:
Class<?> SystemProperties = Class.forName("android.os.SystemProperties");
Method method = SystemProperties.getMethod("get", new Class[] { String.class });
ArrayList<String> servers = new ArrayList<String>();
for (String name : new String[] { "net.dns1", "net.dns2", "net.dns3", "net.dns4", }) {
String value = (String) method.invoke(null, name);
if (value != null && !"".equals(value) && !servers.contains(value))
servers.add(value);
}
【讨论】:
android.os.SystemProperties并使用public static String get(String key, String def)
不幸的是,大多数提出的解决方案在 Android 8.0 中都不再适用
Android 官方文档在 Android 8.0 Behavior Changes 一文中非常清楚地说明了这一点。 系统属性 net.dns1、net.dns2、net.dns3 和 net.dns4 不再可用,这一更改提高了平台的隐私性。
https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri
dnsjava 库也受到影响,dnsjava 中使用的检测方法不知道 Oreo 的变化。
Varun Anand 解决方案适用于 Oreo,但在不处理与默认路由的连接方面存在弱点。因此,结果可能会因无效的 DNS 服务器首先进入结果而中毒,并且调用者可能会花费大量时间迭代列表并尝试连接到无法访问的 DNS 服务器。这已修复到我的解决方案中。 Varun Anand 解决方案的另一个问题是这只适用于 API 21 及更高版本。但我必须说,编写自己的解决方案对我来说是金矿。所以谢谢!
为了您的方便,我提供了一个完整的 DNS 服务器检测器类,您可以在任何 Android 版本上使用它。 包含完整的 cmets 以回答原因和方式。
/**
* DNS servers detector
*
* IMPORTANT: don't cache the result.
*
* Or if you want to cache the result make sure you invalidate the cache
* on any network change.
*
* It is always better to use a new instance of the detector when you need
* current DNS servers otherwise you may get into troubles because of invalid/changed
* DNS servers.
*
* This class combines various methods and solutions from:
* Dnsjava http://www.xbill.org/dnsjava/
* Minidns https://github.com/MiniDNS/minidns
*
* Unfortunately both libraries are not aware of Orero changes so new method was added to fix this.
*
* Created by Madalin Grigore-Enescu on 2/24/18.
*/
public class DnsServersDetector {
private static final String TAG = "DnsServersDetector";
/**
* Holds some default DNS servers used in case all DNS servers detection methods fail.
* Can be set to null if you want caller to fail in this situation.
*/
private static final String[] FACTORY_DNS_SERVERS = {
"8.8.8.8",
"8.8.4.4"
};
/**
* Properties delimiter used in exec method of DNS servers detection
*/
private static final String METHOD_EXEC_PROP_DELIM = "]: [";
/**
* Holds context this was created under
*/
private Context context;
//region - public //////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Constructor
*/
public DnsServersDetector(Context context) {
this.context = context;
}
/**
* Returns android DNS servers used for current connected network
* @return Dns servers array
*/
public String [] getServers() {
// Will hold the consecutive result
String[] result;
// METHOD 1: old deprecated system properties
result = getServersMethodSystemProperties();
if (result != null && result.length > 0) {
return result;
}
// METHOD 2 - use connectivity manager
result = getServersMethodConnectivityManager();
if (result != null && result.length > 0) {
return result;
}
// LAST METHOD: detect android DNS servers by executing getprop string command in a separate process
// This method fortunately works in Oreo too but many people may want to avoid exec
// so it's used only as a failsafe scenario
result = getServersMethodExec();
if (result != null && result.length > 0) {
return result;
}
// Fall back on factory DNS servers
return FACTORY_DNS_SERVERS;
}
//endregion
//region - private /////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Detect android DNS servers by using connectivity manager
*
* This method is working in android LOLLIPOP or later
*
* @return Dns servers array
*/
private String [] getServersMethodConnectivityManager() {
// This code only works on LOLLIPOP and higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
ArrayList<String> priorityServersArrayList = new ArrayList<>();
ArrayList<String> serversArrayList = new ArrayList<>();
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
// Iterate all networks
// Notice that android LOLLIPOP or higher allow iterating multiple connected networks of SAME type
for (Network network : connectivityManager.getAllNetworks()) {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
if (networkInfo.isConnected()) {
LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
List<InetAddress> dnsServersList = linkProperties.getDnsServers();
// Prioritize the DNS servers for link which have a default route
if (linkPropertiesHasDefaultRoute(linkProperties)) {
for (InetAddress element: dnsServersList) {
String dnsHost = element.getHostAddress();
priorityServersArrayList.add(dnsHost);
}
} else {
for (InetAddress element: dnsServersList) {
String dnsHost = element.getHostAddress();
serversArrayList.add(dnsHost);
}
}
}
}
}
// Append secondary arrays only if priority is empty
if (priorityServersArrayList.isEmpty()) {
priorityServersArrayList.addAll(serversArrayList);
}
// Stop here if we have at least one DNS server
if (priorityServersArrayList.size() > 0) {
return priorityServersArrayList.toArray(new String[0]);
}
} catch (Exception ex) {
Log.d(TAG, "Exception detecting DNS servers using ConnectivityManager method", ex);
}
}
// Failure
return null;
}
/**
* Detect android DNS servers by using old deprecated system properties
*
* This method is NOT working anymore in Android 8.0
* Official Android documentation state this in the article Android 8.0 Behavior Changes.
* The system properties net.dns1, net.dns2, net.dns3, and net.dns4 are no longer available,
* a change that improves privacy on the platform.
*
* https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri
* @return Dns servers array
*/
private String [] getServersMethodSystemProperties() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// This originally looked for all lines containing .dns; but
// http://code.google.com/p/android/issues/detail?id=2207#c73
// indicates that net.dns* should always be the active nameservers, so
// we use those.
final String re1 = "^\\d+(\\.\\d+){3}$";
final String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
ArrayList<String> serversArrayList = new ArrayList<>();
try {
Class SystemProperties = Class.forName("android.os.SystemProperties");
Method method = SystemProperties.getMethod("get", new Class[]{String.class});
final String[] netdns = new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
for (int i = 0; i < netdns.length; i++) {
Object[] args = new Object[]{netdns[i]};
String v = (String) method.invoke(null, args);
if (v != null && (v.matches(re1) || v.matches(re2)) && !serversArrayList.contains(v)) {
serversArrayList.add(v);
}
}
// Stop here if we have at least one DNS server
if (serversArrayList.size() > 0) {
return serversArrayList.toArray(new String[0]);
}
} catch (Exception ex) {
Log.d(TAG, "Exception detecting DNS servers using SystemProperties method", ex);
}
}
// Failed
return null;
}
/**
* Detect android DNS servers by executing getprop string command in a separate process
*
* Notice there is an android bug when Runtime.exec() hangs without providing a Process object.
* This problem is fixed in Jelly Bean (Android 4.1) but not in ICS (4.0.4) and probably it will never be fixed in ICS.
* https://stackoverflow.com/questions/8688382/runtime-exec-bug-hangs-without-providing-a-process-object/11362081
*
* @return Dns servers array
*/
private String [] getServersMethodExec() {
// We are on the safe side and avoid any bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
Process process = Runtime.getRuntime().exec("getprop");
InputStream inputStream = process.getInputStream();
LineNumberReader lineNumberReader = new LineNumberReader(new InputStreamReader(inputStream));
Set<String> serversSet = methodExecParseProps(lineNumberReader);
if (serversSet != null && serversSet.size() > 0) {
return serversSet.toArray(new String[0]);
}
} catch (Exception ex) {
Log.d(TAG, "Exception in getServersMethodExec", ex);
}
}
// Failed
return null;
}
/**
* Parse properties produced by executing getprop command
* @param lineNumberReader
* @return Set of parsed properties
* @throws Exception
*/
private Set<String> methodExecParseProps(BufferedReader lineNumberReader) throws Exception {
String line;
Set<String> serversSet = new HashSet<String>(10);
while ((line = lineNumberReader.readLine()) != null) {
int split = line.indexOf(METHOD_EXEC_PROP_DELIM);
if (split == -1) {
continue;
}
String property = line.substring(1, split);
int valueStart = split + METHOD_EXEC_PROP_DELIM.length();
int valueEnd = line.length() - 1;
if (valueEnd < valueStart) {
// This can happen if a newline sneaks in as the first character of the property value. For example
// "[propName]: [\n…]".
Log.d(TAG, "Malformed property detected: \"" + line + '"');
continue;
}
String value = line.substring(valueStart, valueEnd);
if (value.isEmpty()) {
continue;
}
if (property.endsWith(".dns") || property.endsWith(".dns1") ||
property.endsWith(".dns2") || property.endsWith(".dns3") ||
property.endsWith(".dns4")) {
// normalize the address
InetAddress ip = InetAddress.getByName(value);
if (ip == null) continue;
value = ip.getHostAddress();
if (value == null) continue;
if (value.length() == 0) continue;
serversSet.add(value);
}
}
return serversSet;
}
/**
* Returns true if the specified link properties have any default route
* @param linkProperties
* @return true if the specified link properties have default route or false otherwise
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private boolean linkPropertiesHasDefaultRoute(LinkProperties linkProperties) {
for (RouteInfo route : linkProperties.getRoutes()) {
if (route.isDefaultRoute()) {
return true;
}
}
return false;
}
//endregion
}
【讨论】:
android.net.ConnectivityManager 将使用getAllNetworkInfo() 为您提供一系列 NetworkInfo。然后使用android.net.NetworkUtils.runDhcp() 为任何给定的网络接口获取DhcpInfo - DhcpInfo 结构具有该接口的dns1 和dns2 的IP 地址(它们是表示IP 地址的整数值)。
如果您想知道如何将整数转换为 IP 地址,您可以这样做:
/**
* Convert int IP adress to String
* cf. http://teneo.wordpress.com/2008/12/23/java-ip-address-to-integer-and-back/
*/
private String intToIp(int i) {
return ( i & 0xFF) + "." +
(( i >> 8 ) & 0xFF) + "." +
(( i >> 16 ) & 0xFF) + "." +
(( i >> 24 ) & 0xFF);
}
编辑
您还可以通过执行以下操作获取DchcpInfo 对象:
WiFiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
DhcpInfo info = wifi.getDhcpInfo();
【讨论】:
以下适用于 API 21 及更高版本。它为 WiFi 和蜂窝接口返回正确的 dns 服务器。我已经验证了使用 shell 实用程序“getprop”返回的值
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
for (Network network : connectivityManager.getAllNetworks()) {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
if (networkInfo.isConnected()) {
LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
Log.d("DnsInfo", "iface = " + linkProperties.getInterfaceName());
Log.d("DnsInfo", "dns = " + linkProperties.getDnsServers());
return linkProperties.getDnsServers();
}
}
【讨论】:
我推荐 dnsjava 在 Android 上使用复杂的 DNS。让我们看看 dnsjava 如何确定连接的当前活动 DNS 服务器。 From dnsjava ResolverConfig.java:428:
/**
* Parses the output of getprop, which is the only way to get DNS
* info on Android. getprop might disappear in future releases, so
* this code comes with a use-by date.
*/
private void
findAndroid() {
// This originally looked for all lines containing .dns; but
// http://code.google.com/p/android/issues/detail?id=2207#c73
// indicates that net.dns* should always be the active nameservers, so
// we use those.
String re1 = "^\\d+(\\.\\d+){3}$";
String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
try {
ArrayList lserver = new ArrayList();
ArrayList lsearch = new ArrayList();
String line;
Process p = Runtime.getRuntime().exec("getprop");
InputStream in = p.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null ) {
StringTokenizer t = new StringTokenizer(line, ":");
String name = t.nextToken();
if (name.indexOf( "net.dns" ) > -1) {
String v = t.nextToken();
v = v.replaceAll("[ \\[\\]]", "");
if ((v.matches(re1) || v.matches(re2)) &&
!lserver.contains(v))
lserver.add(v);
}
}
configureFromLists(lserver, lsearch);
} catch ( Exception e ) {
// ignore resolutely
}
}
【讨论】:
本地替代方案是:
char dns1[PROP_VALUE_MAX];
__system_property_get("net.dns1", dns1);
或者更全面的列表:
for (i = 1; i <= MAX_DNS_PROPERTIES; i++) {
char prop_name[PROP_NAME_MAX];
snprintf(prop_name, sizeof(prop_name), "net.dns%d", i);
__system_property_get(prop_name, dns);
}
这样做有几个好处:
【讨论】:
首先将 External JARs layoutlib.jar 添加到您的构建路径,layoutlib.jar 文件位于 $SDK_PATH/platforms/android-xxx/data/,然后
String dnsStr1 = android.os.SystemProperties.get("net.dns1");
String dnsStr2 = android.os.SystemProperties.get("net.dns2");
您还可以使用 $getprop 命令查看 adb shell 中的所有属性。
【讨论】:
你可以使用java反射。示例:
ConnectivityManager mgr =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
Method getLinkPropeties;
try{
getLinkPropeties = mgr.getClass().getMethod("getLinkProperties", int.class);
}catch (InvocationTargetException e) {
e.printStackTrace();
}
【讨论】: