feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,85 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.newsdk.ams.httpdns.demo'
compileSdkVersion 34
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.newsdk.ams.httpdns.demo2"
minSdkVersion 19
targetSdkVersion 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "SERVICE_URL", "\"\""
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
forTest {
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慺orTest鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
initWith release
debuggable true
}
}
variantFilter { variant ->
def names = variant.flavors*.name
def type = variant.buildType.name
// To check for a certain build type, use variant.buildType.name == "<buildType>"
if ((names.contains("normal") && type.contains("forTest"))
|| (names.contains("intl") && type.contains("forTest"))
|| (names.contains("end2end") && type.contains("release"))
|| (names.contains("end2end") && type.contains("debug"))
) {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
testOptions {
unitTests {
all {
jvmArgs '-noverify'
systemProperty 'robolectric.logging.enable', true
}
}
}
flavorDimensions "version"
productFlavors {
normal {
}
intl {
}
end2end {
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慹nd2end鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
}
}
}
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies {
implementation project(':httpdns-sdk')
implementation("com.squareup.okhttp3:okhttp:3.9.0")
}

View File

@@ -0,0 +1,30 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/liyazhou/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn com.newsdk.sdk.android.httpdns.test.**
-dontwarn com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
-keep class com.aliyun.ams.ipdetector.Inet64Util{*;}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".HttpDnsActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SDNSActivity"
android:exported="false" />
<activity android:name=".WebViewActivity"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,160 @@
package com.newsdk.ams.httpdns.demo;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AutoCompleteTextView;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.ams.httpdns.demo.base.BaseActivity;
import com.newsdk.ams.httpdns.demo.http.HttpUrlConnectionRequest;
import com.newsdk.ams.httpdns.demo.okhttp.OkHttpRequest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpDnsActivity extends BaseActivity {
private static final String SCHEMA_HTTPS = "https://";
private static final String SCHEMA_HTTP = "http://";
private static final String[] HOSTS = new String[] {
"www.taobao.com",
"demo.cloudxdr.com"
};
private String schema = SCHEMA_HTTPS;
private String host = HOSTS[0];
private RequestIpType requestIpType = RequestIpType.v4;
private HttpUrlConnectionRequest httpUrlConnectionRequest;
private OkHttpRequest okHttpRequest;
private NetworkRequest networkRequest;
private final ExecutorService worker = Executors.newSingleThreadExecutor();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
httpUrlConnectionRequest = new HttpUrlConnectionRequest(this);
okHttpRequest = new OkHttpRequest(this);
networkRequest = httpUrlConnectionRequest;
addFourButton(
"Switch instance",
v -> {
MyApp.getInstance().changeHolder();
sendLog("Instance switched.");
},
"Show config",
v -> sendLog(MyApp.getInstance().getCurrentHolder().getCurrentConfig()),
"Clear holder cache",
v -> {
MyApp.getInstance().getCurrentHolder().cleanSp();
sendLog("Holder cache cleared.");
},
"Clear log",
v -> cleanLog()
);
addAutoCompleteTextViewButton(HOSTS, "Host", "Set host", view -> {
AutoCompleteTextView actv = (AutoCompleteTextView) view;
host = actv.getEditableText().toString();
sendLog("Host set to: " + host);
});
addTwoButton(
"Use HTTPS",
v -> {
schema = SCHEMA_HTTPS;
sendLog("Schema set to HTTPS.");
},
"Use HTTP",
v -> {
schema = SCHEMA_HTTP;
sendLog("Schema set to HTTP.");
}
);
addFourButton(
"IP type v4",
v -> {
requestIpType = RequestIpType.v4;
sendLog("Request IP type: v4");
},
"IP type v6",
v -> {
requestIpType = RequestIpType.v6;
sendLog("Request IP type: v6");
},
"IP type both",
v -> {
requestIpType = RequestIpType.both;
sendLog("Request IP type: both");
},
"IP type auto",
v -> {
requestIpType = RequestIpType.auto;
sendLog("Request IP type: auto");
}
);
addTwoButton(
"HttpUrlConnection",
v -> {
networkRequest = httpUrlConnectionRequest;
sendLog("Network stack: HttpUrlConnection");
},
"OkHttp",
v -> {
networkRequest = okHttpRequest;
sendLog("Network stack: OkHttp");
}
);
addTwoButton(
"Resolve sync",
v -> worker.execute(() -> executeResolve(false)),
"Resolve async",
v -> worker.execute(() -> executeResolve(true))
);
addTwoButton(
"Open SDNS page",
v -> startActivity(new Intent(HttpDnsActivity.this, SDNSActivity.class)),
"Open WebView page",
new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(HttpDnsActivity.this, WebViewActivity.class));
}
}
);
}
@Override
protected void onDestroy() {
super.onDestroy();
worker.shutdownNow();
}
private void executeResolve(boolean async) {
String url = schema + host;
sendLog("Request URL: " + url);
sendLog("Request IP type: " + requestIpType.name());
sendLog("Async mode: " + async);
sendLog("Stack: " + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "OkHttp"));
try {
networkRequest.updateHttpDnsConfig(async, requestIpType);
String response = networkRequest.httpGet(url);
if (response != null && response.length() > 120) {
response = response.substring(0, 120) + "...";
}
sendLog("Response: " + response);
} catch (Exception e) {
sendLog("Request failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,353 @@
package com.newsdk.ams.httpdns.demo;
import android.content.Context;
import android.content.SharedPreferences;
import com.newsdk.sdk.android.httpdns.CacheTtlChanger;
import com.newsdk.sdk.android.httpdns.HttpDns;
import com.newsdk.sdk.android.httpdns.HttpDnsService;
import com.newsdk.sdk.android.httpdns.InitConfig;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean;
import com.newsdk.ams.httpdns.demo.utils.SpUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* 淇濆瓨Httpdns 鍙?鐩稿叧閰嶇疆锛?
* 鏂逛究淇敼
*/
public class HttpDnsHolder {
public static final String SP_PREFIX = "httpdns_config_";
public static String getSpName(String accountId) {
return SP_PREFIX + accountId;
}
public static final String KEY_EXPIRED_IP = "enableExpiredIp";
public static final String KEY_CACHE_IP = "enableCacheIp";
public static final String KEY_TIMEOUT = "timeout";
public static final String KEY_HTTPS = "enableHttps";
public static final String KEY_IP_RANKING_ITEMS = "ipProbeItems";
public static final String KEY_REGION = "region";
public static final String KEY_TTL_CHANGER = "cacheTtlChanger";
public static final String KEY_HOST_NOT_CHANGE = "hostListWithFixedIp";
private HttpDnsService service;
private String accountId;
private String secret;
private String serviceUrl;
private Context context;
private boolean enableExpiredIp;
private boolean enableCacheIp;
private int timeout;
private boolean enableHttps;
private ArrayList<IPRankingBean> ipRankingList = null;
private String region;
private ArrayList<String> hostListWithFixedIp;
private HashMap<String, Integer> ttlCache;
private final CacheTtlChanger cacheTtlChanger = new CacheTtlChanger() {
@Override
public int changeCacheTtl(String host, RequestIpType type, int ttl) {
if (ttlCache != null && ttlCache.get(host) != null) {
return ttlCache.get(host);
}
return ttl;
}
};
public HttpDnsHolder(String accountId) {
this.accountId = accountId;
}
public HttpDnsHolder(String accountId, String secret) {
this.accountId = accountId;
this.secret = secret;
}
public HttpDnsHolder(String accountId, String secret, String serviceUrl) {
this.accountId = accountId;
this.secret = secret;
this.serviceUrl = serviceUrl;
}
/**
* 鍒濆鍖杊ttpdns鐨勯厤缃?
*
* @param context
*/
public void init(Context context) {
this.context = context.getApplicationContext();
SpUtil.readSp(context, getSpName(accountId), new SpUtil.OnGetSp() {
@Override
public void onGetSp(SharedPreferences sp) {
enableExpiredIp = sp.getBoolean(KEY_EXPIRED_IP, true);
enableCacheIp = sp.getBoolean(KEY_CACHE_IP, false);
timeout = sp.getInt(KEY_TIMEOUT, 5 * 1000);
enableHttps = sp.getBoolean(KEY_HTTPS, false);
ipRankingList = convertToProbeList(sp.getString(KEY_IP_RANKING_ITEMS, null));
region = sp.getString(KEY_REGION, null);
ttlCache = convertToCacheTtlData(sp.getString(KEY_TTL_CHANGER, null));
hostListWithFixedIp = convertToStringList(sp.getString(KEY_HOST_NOT_CHANGE, null));
}
});
// 鍒濆鍖杊ttpdns 鐨勯厤缃紝姝ゆ楠ら渶瑕佸湪绗竴娆¤幏鍙朒ttpDnsService瀹炰緥涔嬪墠
InitConfig.Builder builder = new InitConfig.Builder()
.setEnableExpiredIp(enableExpiredIp)
.setEnableCacheIp(enableCacheIp)
.setTimeout(timeout)
.setIPRankingList(ipRankingList)
.configCacheTtlChanger(cacheTtlChanger)
.configHostWithFixedIp(hostListWithFixedIp)
.setEnableHttps(enableHttps)
.setRegion(region);
if (serviceUrl != null && !serviceUrl.trim().isEmpty()) {
builder.setServiceUrl(serviceUrl.trim());
}
builder.buildFor(accountId);
getService();
}
public HttpDnsService getService() {
if (service == null) {
if (secret != null) {
service = HttpDns.getService(context, accountId, secret);
} else {
service = HttpDns.getService(context, accountId);
}
}
return service;
}
public String getAccountId() {
return accountId;
}
public void setEnableExpiredIp(final boolean enableExpiredIp) {
this.enableExpiredIp = enableExpiredIp;
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putBoolean(KEY_EXPIRED_IP, enableExpiredIp);
}
});
}
public void setEnableCacheIp(final boolean enableCacheIp) {
this.enableCacheIp = enableCacheIp;
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putBoolean(KEY_CACHE_IP, enableCacheIp);
}
});
}
public void setTimeout(final int timeout) {
this.timeout = timeout;
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putInt(KEY_TIMEOUT, timeout);
}
});
}
public void setEnableHttps(final boolean enableHttps) {
this.enableHttps = enableHttps;
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putBoolean(KEY_HTTPS, enableHttps);
}
});
}
public void setRegion(final String region) {
this.region = region;
getService().setRegion(region);
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_REGION, region);
}
});
}
public void addHostWithFixedIp(String host) {
if (this.hostListWithFixedIp == null) {
this.hostListWithFixedIp = new ArrayList<>();
}
this.hostListWithFixedIp.add(host);
// 閲嶅惎鐢熸晥
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_HOST_NOT_CHANGE, convertHostList(hostListWithFixedIp));
}
});
}
public void addIpProbeItem(IPRankingBean ipProbeItem) {
if (this.ipRankingList == null) {
this.ipRankingList = new ArrayList<>();
}
this.ipRankingList.add(ipProbeItem);
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_IP_RANKING_ITEMS, convertProbeList(ipRankingList));
}
});
}
public void setHostTtl(String host, int ttl) {
if (ttlCache == null) {
ttlCache = new HashMap<>();
}
ttlCache.put(host, ttl);
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_TTL_CHANGER, convertTtlCache(ttlCache));
}
});
}
public void cleanSp() {
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.clear();
}
});
}
public String getCurrentConfig() {
StringBuilder sb = new StringBuilder();
sb.append("褰撳墠閰嶇疆 accountId : ").append(accountId).append("\n")
.append("鏄惁鍏佽杩囨湡IP : ").append(enableExpiredIp).append("\n")
.append("鏄惁寮€鍚湰鍦扮紦瀛?: ").append(enableCacheIp).append("\n")
.append("鏄惁寮€鍚疕TTPS : ").append(enableHttps).append("\n")
.append("褰撳墠region璁剧疆 : ").append(region).append("\n")
.append("褰撳墠瓒呮椂璁剧疆 : ").append(timeout).append("\n")
.append("褰撳墠鎺㈡祴閰嶇疆 : ").append(convertProbeList(ipRankingList)).append("\n")
.append("褰撳墠缂撳瓨閰嶇疆 : ").append(convertTtlCache(ttlCache)).append("\n")
.append("褰撳墠涓荤珯鍩熷悕閰嶇疆 : ").append(convertHostList(hostListWithFixedIp)).append("\n")
;
return sb.toString();
}
private static String convertHostList(List<String> hostListWithFixedIp) {
if (hostListWithFixedIp == null) {
return null;
}
JSONArray array = new JSONArray();
for (String host : hostListWithFixedIp) {
array.put(host);
}
return array.toString();
}
private static String convertTtlCache(HashMap<String, Integer> ttlCache) {
if (ttlCache == null) {
return null;
}
JSONObject jsonObject = new JSONObject();
for (String host : ttlCache.keySet()) {
try {
jsonObject.put(host, ttlCache.get(host));
} catch (JSONException e) {
e.printStackTrace();
}
}
return jsonObject.toString();
}
private static String convertProbeList(List<IPRankingBean> ipProbeItems) {
if (ipProbeItems == null) {
return null;
}
JSONObject jsonObject = new JSONObject();
for (IPRankingBean item : ipProbeItems) {
try {
jsonObject.put(item.getHostName(), item.getPort());
} catch (JSONException e) {
e.printStackTrace();
}
}
return jsonObject.toString();
}
private static ArrayList<IPRankingBean> convertToProbeList(String json) {
if (json == null) {
return null;
}
try {
JSONObject jsonObject = new JSONObject(json);
ArrayList<IPRankingBean> list = new ArrayList<>();
for (Iterator<String> it = jsonObject.keys(); it.hasNext(); ) {
String host = it.next();
list.add(new IPRankingBean(host, jsonObject.getInt(host)));
}
return list;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
private static HashMap<String, Integer> convertToCacheTtlData(String json) {
if (json == null) {
return null;
}
try {
JSONObject jsonObject = new JSONObject(json);
HashMap<String, Integer> map = new HashMap<>();
for (Iterator<String> it = jsonObject.keys(); it.hasNext(); ) {
String host = it.next();
map.put(host, jsonObject.getInt(host));
}
return map;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
private static ArrayList<String> convertToStringList(String json) {
if (json != null) {
try {
JSONArray array = new JSONArray(json);
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
list.add(array.getString(i));
}
return list;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
package com.newsdk.ams.httpdns.demo;
import android.app.Application;
import android.content.SharedPreferences;
import android.util.Log;
import com.newsdk.sdk.android.httpdns.HttpDnsService;
import com.newsdk.sdk.android.httpdns.ILogger;
import com.newsdk.sdk.android.httpdns.log.HttpDnsLog;
import com.newsdk.ams.httpdns.demo.utils.SpUtil;
public class MyApp extends Application {
private static final String SP_NAME = "HTTPDNS_DEMO";
private static final String KEY_INSTANCE = "KEY_INSTANCE";
private static final String VALUE_INSTANCE_A = "A";
private static final String VALUE_INSTANCE_B = "B";
public static final String TAG = "HTTPDNS DEMO";
private static MyApp instance;
public static MyApp getInstance() {
return instance;
}
private final HttpDnsHolder holderA = new HttpDnsHolder("replace-with-your-accountId-A", "replace-with-your-secret-A", BuildConfig.SERVICE_URL);
private final HttpDnsHolder holderB = new HttpDnsHolder("replace-with-your-accountId-B", null, BuildConfig.SERVICE_URL);
private HttpDnsHolder current = holderA;
@Override
public void onCreate() {
super.onCreate();
instance = this;
// Enable logcat output for debugging.
HttpDnsLog.enable(true);
// Hook the SDK logger into app logs.
HttpDnsLog.setLogger(new ILogger() {
@Override
public void log(String msg) {
Log.d("HttpDnsLogger", msg);
}
});
// Initialize HTTPDNS configuration.
holderA.init(this);
holderB.init(this);
SpUtil.readSp(this, SP_NAME, new SpUtil.OnGetSp() {
@Override
public void onGetSp(SharedPreferences sp) {
String flag = sp.getString(KEY_INSTANCE, VALUE_INSTANCE_A);
if (flag.equals(VALUE_INSTANCE_A)) {
current = holderA;
} else {
current = holderB;
}
}
});
}
public HttpDnsHolder getCurrentHolder() {
return current;
}
public HttpDnsHolder changeHolder() {
if (current == holderA) {
current = holderB;
SpUtil.writeSp(instance, SP_NAME, new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_INSTANCE, VALUE_INSTANCE_B);
}
});
} else {
current = holderA;
SpUtil.writeSp(instance, SP_NAME, new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
editor.putString(KEY_INSTANCE, VALUE_INSTANCE_A);
}
});
}
return current;
}
public HttpDnsService getService() {
return current.getService();
}
}

View File

@@ -0,0 +1,21 @@
package com.newsdk.ams.httpdns.demo;
import com.newsdk.sdk.android.httpdns.RequestIpType;
public interface NetworkRequest {
/**
* 璁剧疆httpdns鐨勯厤缃?
*/
void updateHttpDnsConfig(boolean async, RequestIpType requestIpType);
/**
* get璇锋眰
*
* @param url
* @return
*/
String httpGet(String url) throws Exception;
}

View File

@@ -0,0 +1,127 @@
package com.newsdk.ams.httpdns.demo;
import android.os.Bundle;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.ams.httpdns.demo.base.BaseActivity;
import java.util.HashMap;
public class SDNSActivity extends BaseActivity {
private static final String[] HOSTS = new String[] {
"demo.cloudxdr.com",
"www.taobao.com"
};
private final HashMap<String, String> globalParams = new HashMap<>();
private String host = HOSTS[0];
private RequestIpType requestIpType = RequestIpType.v4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addEditTextEditTextButton("key", "value", "Add global param", new OnButtonClickMoreView() {
@Override
public void onBtnClick(View[] views) {
EditText keyView = (EditText) views[0];
EditText valueView = (EditText) views[1];
String key = keyView.getEditableText().toString();
String value = valueView.getEditableText().toString();
globalParams.put(key, value);
sendLog("Global param added: " + key + "=" + value);
}
});
addOneButton("Clear global params", new View.OnClickListener() {
@Override
public void onClick(View v) {
globalParams.clear();
sendLog("Global params cleared.");
}
});
addFourButton(
"IP type v4",
new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.v4;
sendLog("Request IP type: v4");
}
},
"IP type v6",
new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.v6;
sendLog("Request IP type: v6");
}
},
"IP type both",
new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.both;
sendLog("Request IP type: both");
}
},
"IP type auto",
new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.auto;
sendLog("Request IP type: auto");
}
}
);
addAutoCompleteTextViewButton(HOSTS, "Host", "Set host", new OnButtonClick() {
@Override
public void onBtnClick(View view) {
AutoCompleteTextView hostView = (AutoCompleteTextView) view;
host = hostView.getEditableText().toString();
sendLog("Host set to: " + host);
}
});
addEditTextEditTextButton("key", "value", "Resolve with param", new OnButtonClickMoreView() {
@Override
public void onBtnClick(View[] views) {
EditText keyView = (EditText) views[0];
EditText valueView = (EditText) views[1];
HashMap<String, String> params = new HashMap<>(globalParams);
params.put(keyView.getEditableText().toString(), valueView.getEditableText().toString());
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
host, requestIpType, params, "sdns-demo");
sendLog("SDNS result: " + result);
}
});
addTwoButton("Resolve scale1", new View.OnClickListener() {
@Override
public void onClick(View v) {
HashMap<String, String> params = new HashMap<>(globalParams);
params.put("scale", "scale1");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
host, requestIpType, params, "sdns-demo");
sendLog("scale1 result: " + result);
}
}, "Resolve scale2", new View.OnClickListener() {
@Override
public void onClick(View v) {
HashMap<String, String> params = new HashMap<>(globalParams);
params.put("scale", "scale2");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
host, requestIpType, params, "sdns-demo");
sendLog("scale2 result: " + result);
}
});
}
}

View File

@@ -0,0 +1,403 @@
package com.newsdk.ams.httpdns.demo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.TextView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class WebViewActivity extends Activity {
private WebView webView;
private static final String targetUrl = "http://www.apple.com";
private static final String TAG = MyApp.TAG + "WebView";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
initBar();
initHttpDnsWebView();
}
@Override
public boolean
onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();//杩斿洖涓婁釜椤甸潰
return true;
}
return super.onKeyDown(keyCode, event);//閫€鍑篈ctivity
}
private void initBar() {
findViewById(R.id.bar_img).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
WebViewActivity.this.finish();
}
});
((TextView) findViewById(R.id.bar_text)).setText("HTTPDNS");
}
private void initHttpDnsWebView() {
webView = (WebView) this.findViewById(R.id.wv_container);
webView.setWebViewClient(new WebViewClient() {
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String scheme = request.getUrl().getScheme().trim();
String method = request.getMethod();
Map<String, String> headerFields = request.getRequestHeaders();
String url = request.getUrl().toString();
Log.e(TAG, "url:" + url);
// 鏃犳硶鎷︽埅body锛屾嫤鎴柟妗堝彧鑳芥甯稿鐞嗕笉甯ody鐨勮姹傦紱
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
&& method.equalsIgnoreCase("get")) {
try {
URLConnection connection = recursiveRequest(url, headerFields, null);
if (connection == null) {
Log.e(TAG, "connection null");
return super.shouldInterceptRequest(view, request);
}
// 娉?锛氬浜嶱OST璇锋眰鐨凚ody鏁版嵁锛學ebResourceRequest鎺ュ彛涓苟娌℃湁鎻愪緵锛岃繖閲屾棤娉曞鐞?
String contentType = connection.getContentType();
String mime = getMime(contentType);
String charset = getCharset(contentType);
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
int statusCode = httpURLConnection.getResponseCode();
String response = httpURLConnection.getResponseMessage();
Map<String, List<String>> headers = httpURLConnection.getHeaderFields();
Set<String> headerKeySet = headers.keySet();
Log.e(TAG, "code:" + httpURLConnection.getResponseCode());
Log.e(TAG, "mime:" + mime + "; charset:" + charset);
// 鏃爉ime绫诲瀷鐨勮姹備笉鎷︽埅
if (TextUtils.isEmpty(mime)) {
Log.e(TAG, "no MIME");
return super.shouldInterceptRequest(view, request);
} else {
// 浜岃繘鍒惰祫婧愭棤闇€缂栫爜淇℃伅
if (!TextUtils.isEmpty(charset) || (isBinaryRes(mime))) {
WebResourceResponse resourceResponse = new WebResourceResponse(mime, charset, httpURLConnection.getInputStream());
resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response);
Map<String, String> responseHeader = new HashMap<String, String>();
for (String key : headerKeySet) {
// HttpUrlConnection鍙兘鍖呭惈key涓簄ull鐨勬姤澶达紝鎸囧悜璇ttp璇锋眰鐘舵€佺爜
responseHeader.put(key, httpURLConnection.getHeaderField(key));
}
resourceResponse.setResponseHeaders(responseHeader);
return resourceResponse;
} else {
Log.e(TAG, "non binary resource for " + mime);
return super.shouldInterceptRequest(view, request);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return super.shouldInterceptRequest(view, request);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// API < 21 鍙兘鎷︽埅URL鍙傛暟
return super.shouldInterceptRequest(view, url);
}
});
webView.loadUrl(targetUrl);
}
/**
* 浠巆ontentType涓幏鍙朚IME绫诲瀷
*
* @param contentType
* @return
*/
private String getMime(String contentType) {
if (contentType == null) {
return null;
}
return contentType.split(";")[0];
}
/**
* 浠巆ontentType涓幏鍙栫紪鐮佷俊鎭?
*
* @param contentType
* @return
*/
private String getCharset(String contentType) {
if (contentType == null) {
return null;
}
String[] fields = contentType.split(";");
if (fields.length <= 1) {
return null;
}
String charset = fields[1];
if (!charset.contains("=")) {
return null;
}
charset = charset.substring(charset.indexOf("=") + 1);
return charset;
}
/**
* 鏄惁鏄簩杩涘埗璧勬簮锛屼簩杩涘埗璧勬簮鍙互涓嶉渶瑕佺紪鐮佷俊鎭?
*/
private boolean isBinaryRes(String mime) {
if (mime.startsWith("image")
|| mime.startsWith("audio")
|| mime.startsWith("video")) {
return true;
} else {
return false;
}
}
public URLConnection recursiveRequest(String path, Map<String, String> headers, String reffer) {
HttpURLConnection conn;
URL url = null;
try {
url = new URL(path);
// 寮傛鎺ュ彛鑾峰彇IP
String ip = MyApp.getInstance().getService().getIpByHostAsync(url.getHost());
if (ip != null) {
// 閫氳繃HTTPDNS鑾峰彇IP鎴愬姛锛岃繘琛孶RL鏇挎崲鍜孒OST澶磋缃?
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = path.replaceFirst(url.getHost(), ip);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
if (headers != null) {
for (Map.Entry<String, String> field : headers.entrySet()) {
conn.setRequestProperty(field.getKey(), field.getValue());
}
}
// 璁剧疆HTTP璇锋眰澶碒ost鍩?
conn.setRequestProperty("Host", url.getHost());
} else {
return null;
}
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(false);
if (conn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) conn;
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
(HttpsURLConnection)conn);
// sni鍦烘櫙锛屽垱寤篠SLScocket
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// https鍦烘櫙锛岃瘉涔︽牎楠?
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String host = httpsURLConnection.getRequestProperty("Host");
if (null == host) {
host = httpsURLConnection.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
// 鍘熸湁鎶ュご涓惈鏈塩ookie锛屾斁寮冩嫤鎴?
if (containCookie(headers)) {
return null;
}
String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}
if (location != null) {
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
}
Log.e(TAG, "code: " + code + "; location: " + location + "; path " + path);
return recursiveRequest(location, headers, path);
} else {
// 鏃犳硶鑾峰彇location淇℃伅锛岃娴忚鍣ㄨ幏鍙?
return null;
}
} else {
// redirect finish.
Log.e(TAG, "redirect finish");
return conn;
}
} catch (MalformedURLException e) {
Log.w(TAG, "recursiveRequest MalformedURLException");
} catch (IOException e) {
Log.w(TAG, "recursiveRequest IOException");
} catch (Exception e) {
Log.w(TAG, "unknow exception");
}
return null;
}
private boolean needRedirect(int code) {
return code >= 300 && code < 400;
}
/**
* header涓槸鍚﹀惈鏈塩ookie
*/
private boolean containCookie(Map<String, String> headers) {
for (Map.Entry<String, String> headerField : headers.entrySet()) {
if (headerField.getKey().contains("Cookie")) {
return true;
}
}
return false;
}
static class WebviewTlsSniSocketFactory extends SSLSocketFactory {
private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName();
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
private final HttpsURLConnection conn;
public WebviewTlsSniSocketFactory(HttpsURLConnection conn) {
this.conn = conn;
}
@Override
public Socket createSocket() throws IOException {
return null;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return null;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return null;
}
// TLS layer
@Override
public String[] getDefaultCipherSuites() {
return new String[0];
}
@Override
public String[] getSupportedCipherSuites() {
return new String[0];
}
@Override
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
String peerHost = this.conn.getRequestProperty("Host");
if (peerHost == null)
peerHost = host;
Log.i(TAG, "customized createSocket. host: " + peerHost);
InetAddress address = plainSocket.getInetAddress();
if (autoClose) {
// we don't need the plainSocket
plainSocket.close();
}
// create and connect SSL socket, but don't do hostname/certificate verification yet
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
// enable TLSv1.1/1.2 if available
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
// set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i(TAG, "Setting SNI hostname");
sslSocketFactory.setHostname(ssl, peerHost);
} else {
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
try {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, peerHost);
} catch (Exception e) {
Log.w(TAG, "SNI not useable", e);
}
}
// verify hostname and certificate
SSLSession session = ssl.getSession();
if (!hostnameVerifier.verify(peerHost, session))
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
" using " + session.getCipherSuite());
return ssl;
}
}
}

View File

@@ -0,0 +1,264 @@
package com.newsdk.ams.httpdns.demo.base;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.newsdk.ams.httpdns.demo.MyApp;
import com.newsdk.ams.httpdns.demo.R;
public class BaseActivity extends Activity {
public static final int MSG_WHAT_LOG = 10000;
private ScrollView logScrollView;
private TextView logView;
private LinearLayout llContainer;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_WHAT_LOG:
logView.setText(logView.getText() + "\n" + (String) msg.obj);
handler.post(new Runnable() {
@Override
public void run() {
logScrollView.fullScroll(View.FOCUS_DOWN);
}
});
break;
}
}
};
logScrollView = findViewById(R.id.logScrollView);
logView = findViewById(R.id.tvConsoleText);
llContainer = findViewById(R.id.llContainer);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
handler = null;
}
/**
* 鍙戦€佹棩蹇楀埌鐣岄潰
*
* @param log
*/
protected void sendLog(String log) {
Log.d(MyApp.TAG, log);
if (handler != null) {
Message msg = handler.obtainMessage(MSG_WHAT_LOG, log);
handler.sendMessage(msg);
}
}
protected void cleanLog() {
logView.setText("");
}
protected void addView(int layoutId, OnViewCreated created) {
FrameLayout container = new FrameLayout(this);
View.inflate(this, layoutId, container);
llContainer.addView(container, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
created.onViewCreated(container);
}
protected void addOneButton(
final String labelOne, final View.OnClickListener clickListenerOne
) {
addView(R.layout.item_one_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
btnOne.setOnClickListener(clickListenerOne);
}
});
}
protected void addTwoButton(
final String labelOne, final View.OnClickListener clickListenerOne,
final String labelTwo, final View.OnClickListener clickListenerTwo
) {
addView(R.layout.item_two_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
btnOne.setOnClickListener(clickListenerOne);
Button btnTwo = view.findViewById(R.id.btnTwo);
btnTwo.setText(labelTwo);
btnTwo.setOnClickListener(clickListenerTwo);
}
});
}
protected void addThreeButton(
final String labelOne, final View.OnClickListener clickListenerOne,
final String labelTwo, final View.OnClickListener clickListenerTwo,
final String labelThree, final View.OnClickListener clickListenerThree
) {
addView(R.layout.item_three_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
btnOne.setOnClickListener(clickListenerOne);
Button btnTwo = view.findViewById(R.id.btnTwo);
btnTwo.setText(labelTwo);
btnTwo.setOnClickListener(clickListenerTwo);
Button btnThree = view.findViewById(R.id.btnThree);
btnThree.setText(labelThree);
btnThree.setOnClickListener(clickListenerThree);
}
});
}
protected void addFourButton(
final String labelOne, final View.OnClickListener clickListenerOne,
final String labelTwo, final View.OnClickListener clickListenerTwo,
final String labelThree, final View.OnClickListener clickListenerThree,
final String labelFour, final View.OnClickListener clickListenerFour
) {
addView(R.layout.item_four_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
btnOne.setOnClickListener(clickListenerOne);
Button btnTwo = view.findViewById(R.id.btnTwo);
btnTwo.setText(labelTwo);
btnTwo.setOnClickListener(clickListenerTwo);
Button btnThree = view.findViewById(R.id.btnThree);
btnThree.setText(labelThree);
btnThree.setOnClickListener(clickListenerThree);
Button btnFour = view.findViewById(R.id.btnFour);
btnFour.setText(labelFour);
btnFour.setOnClickListener(clickListenerFour);
}
});
}
protected void addEditTextButton(
final String hint,
final String labelOne, final OnButtonClick clickListenerOne
) {
addView(R.layout.item_edit_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
final EditText editText = view.findViewById(R.id.etOne);
editText.setHint(hint);
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickListenerOne.onBtnClick(editText);
}
});
}
});
}
protected void addEditTextEditTextButton(
final String hintOne, final String hintTwo,
final String labelOne, final OnButtonClickMoreView clickListenerOne
) {
addView(R.layout.item_edit_edit_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
final EditText editTextOne = view.findViewById(R.id.etOne);
editTextOne.setHint(hintOne);
final EditText editTextTwo = view.findViewById(R.id.etTwo);
editTextTwo.setHint(hintTwo);
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickListenerOne.onBtnClick(new View[]{editTextOne, editTextTwo});
}
});
}
});
}
protected void addAutoCompleteTextViewButton(
final String[] strings, final String hint, final String labelOne, final OnButtonClick clickListenerOne
) {
addView(R.layout.item_autocomplete_button, new OnViewCreated() {
@Override
public void onViewCreated(View view) {
Button btnOne = view.findViewById(R.id.btnOne);
btnOne.setText(labelOne);
final AutoCompleteTextView actvOne = view.findViewById(R.id.actvOne);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_dropdown_item_1line, strings);
actvOne.setAdapter(adapter);
actvOne.setHint(hint);
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickListenerOne.onBtnClick(actvOne);
}
});
}
});
}
public interface OnViewCreated {
void onViewCreated(View view);
}
public interface OnButtonClick {
void onBtnClick(View view);
}
public interface OnButtonClickMoreView {
void onBtnClick(View[] views);
}
}

View File

@@ -0,0 +1,258 @@
package com.newsdk.ams.httpdns.demo.http;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
import android.util.Log;
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
import com.newsdk.sdk.android.httpdns.NetType;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.sdk.android.httpdns.SyncService;
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector;
import com.newsdk.ams.httpdns.demo.MyApp;
import com.newsdk.ams.httpdns.demo.NetworkRequest;
import com.newsdk.ams.httpdns.demo.utils.Util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
* 浣跨敤HttpUrlConnection 瀹炵幇璇锋眰
*/
public class HttpUrlConnectionRequest implements NetworkRequest {
public static final String TAG = MyApp.TAG + "HttpUrl";
private final Context context;
private boolean async;
private RequestIpType type;
public HttpUrlConnectionRequest(Context context) {
this.context = context.getApplicationContext();
}
@Override
public void updateHttpDnsConfig(boolean async, RequestIpType requestIpType) {
this.async = async;
this.type = requestIpType;
}
@Override
public String httpGet(String url) throws Exception {
Log.d(TAG, "浣跨敤httpUrlConnection 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
HttpURLConnection conn = getConnection(url);
InputStream in = null;
BufferedReader streamReader = null;
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
in = conn.getErrorStream();
String errStr = null;
if (in != null) {
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
errStr = readStringFrom(streamReader).toString();
}
Log.d(TAG, "璇锋眰澶辫触 " + conn.getResponseCode() + " err " + errStr);
throw new Exception("Status Code : " + conn.getResponseCode() + " Msg : " + errStr);
} else {
in = conn.getInputStream();
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String responseStr = readStringFrom(streamReader).toString();
Log.d(TAG, "璇锋眰鎴愬姛 " + responseStr);
return responseStr;
}
}
private HttpURLConnection getConnection(String url) throws IOException {
final String host = new URL(url).getHost();
HttpURLConnection conn = null;
HTTPDNSResult result;
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
if (async) {
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(host, type);
} else {
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(host, type);
}
Log.d(TAG, "httpdns 瑙f瀽 " + host + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
String newUrl = url.replace(host, "[" + result.getIpv6s()[0] + "]");
conn = (HttpURLConnection) new URL(newUrl).openConnection();
conn.setRequestProperty("Host", host);
Log.d(TAG, "浣跨敤ipv6鍦板潃 " + newUrl);
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
String newUrl = url.replace(host, result.getIps()[0]);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
conn.setRequestProperty("Host", host);
Log.d(TAG, "浣跨敤ipv4鍦板潃 " + newUrl);
}
if (conn == null) {
Log.d(TAG, "httpdns 鏈繑鍥炶В鏋愮粨鏋滐紝璧發ocaldns");
conn = (HttpURLConnection) new URL(url).openConnection();
}
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(false);
if (conn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) conn;
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
(HttpsURLConnection)conn);
// sni鍦烘櫙锛屽垱寤篠SLSocket
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// https鍦烘櫙锛岃瘉涔︽牎楠?
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String host = httpsURLConnection.getRequestProperty("Host");
if (null == host) {
host = httpsURLConnection.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
URL originalUrl = new URL(url);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
}
return getConnection(location);
}
return conn;
}
private boolean needRedirect(int code) {
return code >= 300 && code < 400;
}
static class WebviewTlsSniSocketFactory extends SSLSocketFactory {
private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName();
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
private HttpsURLConnection conn;
public WebviewTlsSniSocketFactory(HttpsURLConnection conn) {
this.conn = conn;
}
@Override
public Socket createSocket() throws IOException {
return null;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return null;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return null;
}
// TLS layer
@Override
public String[] getDefaultCipherSuites() {
return new String[0];
}
@Override
public String[] getSupportedCipherSuites() {
return new String[0];
}
@Override
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
String peerHost = this.conn.getRequestProperty("Host");
if (peerHost == null)
peerHost = host;
Log.i(TAG, "customized createSocket. host: " + peerHost);
InetAddress address = plainSocket.getInetAddress();
if (autoClose) {
// we don't need the plainSocket
plainSocket.close();
}
// create and connect SSL socket, but don't do hostname/certificate verification yet
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
// enable TLSv1.1/1.2 if available
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
// set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i(TAG, "Setting SNI hostname");
sslSocketFactory.setHostname(ssl, peerHost);
} else {
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
try {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, peerHost);
} catch (Exception e) {
Log.w(TAG, "SNI not useable", e);
}
}
// verify hostname and certificate
SSLSession session = ssl.getSession();
if (!hostnameVerifier.verify(peerHost, session))
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
" using " + session.getCipherSuite());
return ssl;
}
}
/**
* stream to string
*/
public static StringBuilder readStringFrom(BufferedReader streamReader) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = streamReader.readLine()) != null) {
sb.append(line);
}
return sb;
}
}

View File

@@ -0,0 +1,98 @@
package com.newsdk.ams.httpdns.demo.okhttp;
import android.content.Context;
import android.util.Log;
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
import com.newsdk.sdk.android.httpdns.NetType;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.sdk.android.httpdns.SyncService;
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector;
import com.newsdk.ams.httpdns.demo.MyApp;
import com.newsdk.ams.httpdns.demo.NetworkRequest;
import com.newsdk.ams.httpdns.demo.utils.Util;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.ConnectionPool;
import okhttp3.Dns;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* okhttp瀹炵幇缃戠粶璇锋眰
*/
public class OkHttpRequest implements NetworkRequest {
public static final String TAG = MyApp.TAG + "Okhttp";
private final OkHttpClient client;
private boolean async;
private RequestIpType type;
public OkHttpRequest(final Context context) {
client = new OkHttpClient.Builder()
// 杩欓噷閰嶇疆杩炴帴姹狅紝鏄负浜嗘柟渚挎祴璇昲ttpdns鑳藉姏锛屾寮忎唬鐮佽涓嶈閰嶇疆
.connectionPool(new ConnectionPool(0, 10 * 1000, TimeUnit.MICROSECONDS))
.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
HTTPDNSResult result;
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
if (async) {
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(hostname, type);
} else {
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(hostname, type);
}
Log.d(TAG, "httpdns 瑙f瀽 " + hostname + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
List<InetAddress> inetAddresses = new ArrayList<>();
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
Log.d(TAG, "netType is: " + HttpDnsNetworkDetector.getInstance().getNetType(context));
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
for (int i = 0; i < result.getIpv6s().length; i++) {
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIpv6s()[i])));
}
Log.d(TAG, "浣跨敤ipv6鍦板潃" + inetAddresses);
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
for (int i = 0; i < result.getIps().length; i++) {
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIps()[i])));
}
Log.d(TAG, "浣跨敤ipv4鍦板潃" + inetAddresses);
}
if (inetAddresses.size() == 0) {
Log.d(TAG, "httpdns 鏈繑鍥濱P锛岃蛋localdns");
return Dns.SYSTEM.lookup(hostname);
}
return inetAddresses;
}
})
.build();
}
@Override
public void updateHttpDnsConfig(boolean async, RequestIpType requestIpType) {
this.async = async;
this.type = requestIpType;
}
@Override
public String httpGet(String url) throws Exception {
Log.d(TAG, "浣跨敤okhttp 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
int code = response.code();
String body = response.body().string();
Log.d(TAG, "浣跨敤okhttp 璇锋眰缁撴灉 code " + code + " body " + body);
if (code != HttpURLConnection.HTTP_OK) {
throw new Exception("璇锋眰澶辫触 code " + code + " body " + body);
}
return body;
}
}

View File

@@ -0,0 +1,28 @@
package com.newsdk.ams.httpdns.demo.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class SpUtil {
public static void readSp(Context context, String name, OnGetSp onGetSp) {
SharedPreferences sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
onGetSp.onGetSp(sp);
}
public static void writeSp(Context context, String name, OnGetSpEditor onGetSpEditor) {
SharedPreferences sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
onGetSpEditor.onGetSpEditor(editor);
editor.commit();
}
public interface OnGetSp {
void onGetSp(SharedPreferences sp);
}
public interface OnGetSpEditor {
void onGetSpEditor(SharedPreferences.Editor editor);
}
}

View File

@@ -0,0 +1,95 @@
package com.newsdk.ams.httpdns.demo.utils;
import android.util.Log;
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
import com.newsdk.sdk.android.httpdns.RequestIpType;
import com.newsdk.sdk.android.httpdns.SyncService;
import com.newsdk.ams.httpdns.demo.MyApp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadUtil {
public static void multiThreadTest(
final String[] validHosts,
final int hostCount,
final int threadCount,
final int executeTime,
final boolean async,
final RequestIpType type
) {
new Thread(() -> runMultiThreadTest(validHosts, hostCount, threadCount, executeTime, async, type)).start();
}
private static void runMultiThreadTest(
String[] validHosts,
int hostCount,
int threadCount,
int executeTime,
boolean async,
RequestIpType type
) {
int validCount = Math.min(validHosts.length, hostCount);
Log.d(MyApp.TAG, "Start multiThreadTest, threads=" + threadCount
+ ", executeTimeMs=" + executeTime
+ ", hostCount=" + hostCount
+ ", validHostCount=" + validCount
+ ", async=" + async
+ ", ipType=" + type.name());
ArrayList<String> hosts = new ArrayList<>(hostCount);
for (int i = 0; i < hostCount - validCount; i++) {
hosts.add("test" + i + ".cloudxdr.com");
}
hosts.addAll(Arrays.asList(validHosts).subList(0, validCount));
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
CountDownLatch done = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
pool.execute(() -> {
Random random = new Random(Thread.currentThread().getId());
long begin = System.currentTimeMillis();
int requestCount = 0;
int successCount = 0;
while (System.currentTimeMillis() - begin < executeTime) {
String host = hosts.get(random.nextInt(hosts.size()));
try {
HTTPDNSResult result;
if (async) {
result = MyApp.getInstance().getService().getIpsByHostAsync(host, type);
} else {
result = ((SyncService) MyApp.getInstance().getService()).getByHost(host, type);
}
if (result != null && result.getIps() != null && result.getIps().length > 0) {
successCount++;
}
} catch (Throwable t) {
Log.w(MyApp.TAG, "multiThreadTest request failed: " + t.getMessage());
}
requestCount++;
}
Log.w(MyApp.TAG, "multiThreadTest thread=" + Thread.currentThread().getId()
+ ", requestCount=" + requestCount
+ ", successCount=" + successCount);
done.countDown();
});
}
try {
done.await();
} catch (InterruptedException ignored) {
} finally {
pool.shutdownNow();
}
}
}

View File

@@ -0,0 +1,25 @@
package com.newsdk.ams.httpdns.demo.utils;
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
import java.lang.reflect.Field;
public class Util {
/**
* 鑾峰彇ttl锛?
* 姝ゆ柟娉曟槸鐢ㄤ簬娴嬭瘯鑷畾涔塼tl鏄惁鐢熸晥
*/
public static int getTtl(HTTPDNSResult result) {
try {
Field ttlField = HTTPDNSResult.class.getDeclaredField("ttl");
ttlField.setAccessible(true);
return ttlField.getInt(result);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return -1;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,49 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/new_bg" />
<ScrollView
android:id="@+id/logScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvConsoleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:textColor="@android:color/white"
android:textSize="20sp" />
</ScrollView>
</FrameLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#EAEAEA">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="#f2f2f2">
<ImageView
android:id="@+id/bar_img"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@mipmap/back"
android:padding="16dp" />
<TextView
android:id="@+id/bar_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:textSize="18sp" />
<TextView
android:id="@+id/bar_more"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="鏇村"
android:textSize="16sp"
android:gravity="center"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:visibility="gone" />
</LinearLayout>
<WebView
android:id="@+id/wv_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/actvOne"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/actvOne"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/etOne"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etOne" />
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etOne" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/etTwo" />
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnFour"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@@ -0,0 +1,31 @@
<resources>
<string name="app_name">閵嗘劙妯嬮柌灞肩隘HttpDns閵嗘厪emo缁嬪绨</string>
<string name="action_settings">Settings</string>
<string name="normal_parse">閺咁噣鈧俺袙閺</string>
<string name="request_taobao">鐟欙絾鐎藉ǎ妯虹杺閸╃喎鎮</string>
<string name="request_apple">鐟欙絾鐎絘pple閸╃喎鎮</string>
<string name="request_douban">鐟欙絾鐎界挒鍡欐憵閸╃喎鎮</string>
<string name="https_parse">HTTPS瀵偓閸</string>
<string name="timeout">鐠佸墽鐤嗙搾鍛</string>
<string name="set_expired">閸忎浇顔忔潻鍥ㄦ埂閸╃喎鎮</string>
<string name="set_cache">瀵偓閸氼垱瀵旀稊鍛缂傛挸鐡</string>
<string name="set_degration_filter">闂勫秶楠囩粵鏍殣</string>
<string name="set_pre_resolve">妫板嫯袙閺</string>
<string name="set_region">region</string>
<string name="sync_request">閸氬本顒炵憴锝嗙€</string>
<string name="multi_sync_request">閸氬本顒炵憴锝嗙€介獮璺哄絺</string>
<string name="multi_request">楠炶泛褰傜憴锝嗙€</string>
<string name="main_about_us">閸忓厖绨幋鎴滄粦</string>
<string name="main_helper">鐢喖濮稉顓炵妇</string>
<string name="main_clear_text">濞撳懘娅庤ぐ鎾冲濞戝牊浼</string>
<string name="layout_aboutus_arr">All Rights Reserved.</string>
<string name="layout_aboutus_company">闂冨潡鍣锋禍?鏉烆垯娆?閺堝妾洪崗顒€寰冮悧鍫熸綀閹碘偓閺</string>
<string name="layout_aboutus_copyright">Copyright 婕?2009 - 2016 New.com</string>
<string name="layout_aboutus_app_version">1.1.3</string>
<string name="layout_helpus_content">Q : 娴犫偓娑斿牊妲搁悽銊﹀煕娴捇鐛橠emo閿涚剠nA : 閻劍鍩涙担鎾荤崣Demo鐏忚鲸妲搁梼鍧楀櫡娴滄垵閽╅崣棰佽礋閹劏鍤滈崝銊ュ灡瀵よ櫣娈戦妴浣烘暏閺夈儰缍嬫瀛抰tpDns閺堝秴濮熼崪灞藉冀妫牆缂撶拋顔炬暏閻ㄥ嫪绔存稉顏勭毈Demo閿涘矁顔€閹劋缍嬫灞肩┒閹规灚鈧線鐝弫鍫㈡畱HttpDns閺堝秴濮熼妴淇搉\nQ : 婵″倷缍嶉懕鏃傞兇閹存垳婊戦敍鐒卬A : App Demo閻╃鍙ч梻顕€顣介敍宀冾嚞閹兼粎鍌ㄩ柦澶愭嫟缂囥倕褰块敍?1777313</string>
</resources>

View File

@@ -0,0 +1,14 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme"></style>
<style name="button_allgrade_content">
<item name="android:layout_margin">1dip</item>
<item name="android:background">#ffffff</item>
<item name="android:textSize">18sp</item>
<item name="android:clickable">true</item>
<item name="android:textColor">#413d41</item>
</style>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<New-anchors>
<certificates src="system" />
</New-anchors>
</base-config>
</network-security-config>