之前做过 WebView 代理访问网站的需求,并调查了多种方法,最后使用了 WebViewClient 拦截 shouldInterceptRequest
并自行构造响应来实现。
但这种实现并不支持文件上传,以及 post 数据。随后的深入发现 Google jetpack 库提供了设置代理的方法,经过测试,此种方法兼容性好,且实用性更高。
所以优先使用 webkit 库设置代理,不支持的情况可以用反射
似乎为 WebView 设置代理访问的需求不是太高。网上相关文章比较少。
导入 jetpack 的 webkit 库
在项目的 app 工程的 build.gradle
中导入依赖
// webkit
implementation 'androidx.webkit:webkit:1.4.0'
为 webview 设置代理
在初始化 webview 的时候设置代理
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
Log.i("Info", "设置代理");
//设置代理
ProxyConfig proxyConfig = new ProxyConfig.Builder()
.addProxyRule("http://192.168.0.26:8100")
.addDirect().build();
ProxyController.getInstance().setProxyOverride(proxyConfig, new Executor() {
@Override
public void execute(Runnable command) {
//do nothing
Log.i("Info", "代理设置完成");
}
}, new Runnable() {
@Override
public void run() {
Log.w("Wanning", "WebView代理 改变");
}
});
在调用 setProxyOverride
方法设置代理的时候,并不需要传入 webview 对象。它会为应用程序内的所有 WebView 全部设置代理。
其中 .addProxyRule("http://192.168.0.26:8100")
就是为 webview 设置代理地址,协议为 http
,域名及端口为:192.168.0.26:8100
addProxyRule
方法根据文档。支持 http
https
socks
三种协议
这种设置代理的方法,简单易用
开发板不支持webkit库的情况
使用:WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)
在许多手机和模拟器上,都会返回 true
,也能正确的设置代理,
这样当使用:ProxyController.getInstance().setProxyOverride
设置时,能够正确的使用代理连接
但在定制的 Android 开发板上,WebViewFeature.isFeatureSupported
返回了 false
, 在返回false,不支持使用 webkit 库进行代理设置的时候,可以使用反射作为备用措施,在开发板上,使用反射开启代理测试成功。这样实现了兼容。
private fun setWebviewProxy() {
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
Logger.i("支持使用 webkit 设置代理")
//设置代理
val proxyConfig = ProxyConfig.Builder()
.addProxyRule("socks://127.0.0.1:1080")
.addDirect().build();
ProxyController.getInstance().setProxyOverride(proxyConfig, {
@Override
fun execute(command: Runnable) {
//do nothing
Log.i("Info", "代理设置完成");
}
}, {
@Override
fun run() {
Log.w("Wanning", "WebView代理 改变");
}
});
} else {
val invokeBool = WebviewSettingProxy.setProxy(
mAgentWeb?.getWebCreator()?.webView,
"192.168.1.238",
8090,
"这里填 Application 的包名:com.example.test.Application"
)
Logger.i("不支持设置代理, 使用反射等方式尝试: " + invokeBool)
}
WebviewSettingProxy
类实现放在最末尾
清除webview代理
private fun clearProxy() {
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
ProxyController.getInstance().clearProxyOverride({
LogUtil.d("取消代理")
}, {
LogUtil.d("取消代理 成功")
})
} else {
WebviewSettingProxy.revertBackProxy(
mAgentWeb?.webCreator?.webView,
"这里填 Application 的包名:com.example.test.Application"
)
}
}
网站直连
这种方式是应用内的 WebView 全局代理,如果需要绕过某些网站,可以指定,或者使用通配符进行绕过:
ProxyConfig proxyConfig = new ProxyConfig.Builder()
.addProxyRule("http://192.168.0.26:8100")
.addBypassRule("www.baidu.com")
.addBypassRule("www.bing.com")
.addBypassRule("*.bing.com")
.addDirect().build();
addBypassRule
可以多次调用
其它
如果需要代理访问国外网站怎么办?总不能在 国外 vps 上搭建一个 socks 代理,然后为 webview 设置。gfw 会阻断常规的,连向国外的协议
目前使用的方式是:国外vps 搭建其它的协议,如 ss 等。国内 vps 作为ss 客户端,连接 国外vps ,并同时作为 socks 的服务端。让 webview 连接 国内的 vps。使用国内 vps 作为跳板机进行访问
WebviewSettingProxy
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Proxy;
import android.os.Build;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.webkit.WebView;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class WebviewSettingProxy {
private static final String LOG_TAG = "WebviewSettingProxy";
/**
* 给webview设置代理
*
* @param webview
* @param host ip
* @param port 端口
* @param applicationClassName 注意这里applicationClassName 传递的是 application 的类名
* @return
*/
public static boolean setProxy(WebView webview, String host, int port, String applicationClassName) {
// 3.2 (HC) or lower
if (Build.VERSION.SDK_INT <= 13) {
// return setProxyUpToHC(webview, host, port);
}
// ICS: 4.0
else if (Build.VERSION.SDK_INT <= 15) {
return setProxyICS(webview, host, port);
}
// 4.1-4.3 (JB)
else if (Build.VERSION.SDK_INT <= 18) {
return setProxyJB(webview, host, port);
}
// 4.4 (KK) & 5.0 (Lollipop)
else {
return setProxyKKPlus(webview, host, port, applicationClassName);
}
return false;
}
/**
* 给webview取消代理
*
* @param webview
* @param applicationClassName 注意这里applicationClassName 传递的是 application 的类名
* @return
*/
public static boolean revertBackProxy(WebView webview, String applicationClassName) {
if (webview == null || TextUtils.isEmpty(applicationClassName)) {
return true;
}
// 3.2 (HC) or lower
if (Build.VERSION.SDK_INT <= 13) {
return true;
}
// ICS: 4.0
else if (Build.VERSION.SDK_INT <= 15) {
return revertProxyICS(webview);
}
// 4.1-4.3 (JB)
else if (Build.VERSION.SDK_INT <= 18) {
return revertProxyJB(webview);
}
// 4.4 (KK) & 5.0 (Lollipop)
else {
return revertProxyKKPlus(webview, applicationClassName);
}
}
@SuppressWarnings("all")
private static boolean setProxyICS(WebView webview, String host, int port) {
try {
Log.d(LOG_TAG, "Setting proxy with 4.0 API.");
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
Class wv = Class.forName("android.webkit.WebView");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webview);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
updateProxyInstance.invoke(sJavaBridge, ppcont.newInstance(host, port, null));
Log.d(LOG_TAG, "Setting proxy with 4.0 API successful!");
return true;
} catch (Exception ex) {
Log.e(LOG_TAG, "failed to set HTTP proxy: " + ex);
return false;
}
}
private static boolean revertProxyICS(WebView webview) {
try {
Log.d(LOG_TAG, "Setting proxy with 4.0 API.");
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
Class wv = Class.forName("android.webkit.WebView");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webview);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
Object o = null;
updateProxyInstance.invoke(sJavaBridge, o);
Log.d(LOG_TAG, "Setting proxy with 4.0 API successful!");
return true;
} catch (Exception ex) {
Log.e(LOG_TAG, "failed to set HTTP proxy: " + ex);
return false;
}
}
/**
* Set Proxy for Android 4.1 - 4.3.
*/
@SuppressWarnings("all")
private static boolean setProxyJB(WebView webview, String host, int port) {
Log.d(LOG_TAG, "Setting proxy with 4.1 - 4.3 API.");
try {
Class wvcClass = Class.forName("android.webkit.WebViewClassic");
Class wvParams[] = new Class[1];
wvParams[0] = Class.forName("android.webkit.WebView");
Method fromWebView = wvcClass.getDeclaredMethod("fromWebView", wvParams);
Object webViewClassic = fromWebView.invoke(null, webview);
Class wv = Class.forName("android.webkit.WebViewClassic");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webViewClassic);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
updateProxyInstance.invoke(sJavaBridge, ppcont.newInstance(host, port, null));
} catch (Exception ex) {
Log.e(LOG_TAG, "Setting proxy with >= 4.1 API failed with error: " + ex.getMessage());
return false;
}
Log.d(LOG_TAG, "Setting proxy with 4.1 - 4.3 API successful!");
return true;
}
private static boolean revertProxyJB(WebView webview) {
Log.d(LOG_TAG, "revert proxy with 4.1 - 4.3 API.");
try {
Class wvcClass = Class.forName("android.webkit.WebViewClassic");
Class wvParams[] = new Class[1];
wvParams[0] = Class.forName("android.webkit.WebView");
Method fromWebView = wvcClass.getDeclaredMethod("fromWebView", wvParams);
Object webViewClassic = fromWebView.invoke(null, webview);
Class wv = Class.forName("android.webkit.WebViewClassic");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webViewClassic);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
Object o = null;
updateProxyInstance.invoke(sJavaBridge, o);
} catch (Exception ex) {
Log.e(LOG_TAG, "Setting proxy with >= 4.1 API failed with error: " + ex.getMessage());
return false;
}
Log.d(LOG_TAG, "revert proxy with 4.1 - 4.3 API successful!");
return true;
}
// from https://stackoverflow.com/questions/19979578/android-webview-set-proxy-programatically-kitkat
@SuppressLint("NewApi")
@SuppressWarnings("all")
private static boolean setProxyKKPlus(WebView webView, String host, int port, String applicationClassName) {
Log.d(LOG_TAG, "Setting proxy with >= 4.4 API.");
Context appContext = webView.getContext().getApplicationContext();
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", port + "");
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", port + "");
try {
Class applictionCls = Class.forName(applicationClassName);
Field loadedApkField = applictionCls.getField("mLoadedApk");
loadedApkField.setAccessible(true);
Object loadedApk = loadedApkField.get(appContext);
Class loadedApkCls = Class.forName("android.app.LoadedApk");
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
receiversField.setAccessible(true);
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
for (Object receiverMap : receivers.values()) {
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
Class clazz = rec.getClass();
if (clazz.getName().contains("ProxyChangeListener")) {
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
onReceiveMethod.invoke(rec, appContext, intent);
}
}
}
Log.d(LOG_TAG, "Setting proxy with >= 4.4 API successful!");
return true;
} catch (ClassNotFoundException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchFieldException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalAccessException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalArgumentException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchMethodException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (InvocationTargetException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
}
return false;
}
@SuppressLint("NewApi")
@SuppressWarnings("all")
private static boolean revertProxyKKPlus(WebView webView, String applicationClassName) {
Context appContext = webView.getContext().getApplicationContext();
Properties properties = System.getProperties();
properties.remove("http.proxyHost");
properties.remove("http.proxyPort");
properties.remove("https.proxyHost");
properties.remove("https.proxyPort");
try {
Class applictionCls = Class.forName(applicationClassName);
Field loadedApkField = applictionCls.getField("mLoadedApk");
loadedApkField.setAccessible(true);
Object loadedApk = loadedApkField.get(appContext);
Class loadedApkCls = Class.forName("android.app.LoadedApk");
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
receiversField.setAccessible(true);
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
for (Object receiverMap : receivers.values()) {
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
Class clazz = rec.getClass();
if (clazz.getName().contains("ProxyChangeListener")) {
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
// intent.putExtra("proxy", null);
onReceiveMethod.invoke(rec, appContext, intent);
}
}
}
Log.d(LOG_TAG, "Revert proxy with >= 4.4 API successful!");
return true;
} catch (ClassNotFoundException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchFieldException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalAccessException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalArgumentException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchMethodException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (InvocationTargetException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
}
return false;
}
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
boolean oldAccessibleValue = field.isAccessible();
field.setAccessible(true);
Object result = field.get(classInstance);
field.setAccessible(oldAccessibleValue);
return result;
}
}