安卓Webview如何在KitKat系统中通过编程方式设置代理

8

如何在最新的Kitkat版本中以编程方式设置Android webview代理?

这个SO链接讨论了SDK版本18及以下的问题。但是,由于底层的webkit实现已经改变并且现在使用Chromium,因此这些解决方案在Kitkat上不再起作用。

6个回答

20

这是我的解决方案:

public static void setKitKatWebViewProxy(Context appContext, String host, int port) {
    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("android.app.Application");
        Field loadedApkField = applictionCls.getDeclaredField("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);

                    /*********** optional, may be need in future *************/
                    final String CLASS_NAME = "android.net.ProxyProperties";
                    Class cls = Class.forName(CLASS_NAME);
                    Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
                    constructor.setAccessible(true);
                    Object proxyProperties = constructor.newInstance(host, port, null);
                    intent.putExtra("proxy", (Parcelable) proxyProperties);
                    /*********** optional, may be need in future *************/

                    onReceiveMethod.invoke(rec, appContext, intent);
                }
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}

我希望它能帮助你。

注意:如参数名所示,Context参数应该是一个应用程序上下文,在这里您可以使用自己实现的继承Application的Application实例。


我尝试在Android 4.4.2模拟器上运行您的代码,但是它无法找到“02-24 08:26:56.324:W / System.err(2678):java.lang.NoSuchFieldException:mLoadedApk”。请问您的代码中的“mLoadedApk”是什么意思? - Herry
1
你应该使用应用程序上下文作为第一个参数,就像我最近的更新所说的那样。@Herry - xjy2061
干得好,但是我似乎无法使用此代码在第一次设置后将代理重置回正常状态。有什么想法吗? - koenmetsu
4
为了重置,您可以将前四行更改为:Properties properties = System.getProperties(); properties.remove("http.proxyHost"); properties.remove("http.proxyPort"); properties.remove("https.proxyHost"); properties.remove("https.proxyPort");,并将可选块更改为 intent.putExtra("proxy", null); - xjy2061
1
@njtman 可能您导入了错误的类,ArrayMap 应该是在 API 级别 19 中添加的 android.util.ArrayMap。 - xjy2061
显示剩余8条评论

4

我对@xjy2061的答案做了一些修改。

更改内容如下:

  1. 将getDeclaredField改为getField --> 如果您声明了自己的应用程序类,则使用此选项。否则无法找到它。

还要记得将“com.your.application”更改为您自己应用程序类的全名。

private static boolean setKitKatWebViewProxy(WebView webView, String host, int port) {
    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("acr.browser.barebones.Jerky");
        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);

                    /*********** optional, may be need in future *************/
                    final String CLASS_NAME = "android.net.ProxyProperties";
                    Class cls = Class.forName(CLASS_NAME);
                    Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
                    constructor.setAccessible(true);
                    Object proxyProperties = constructor.newInstance(host, port, null);
                    intent.putExtra("proxy", (Parcelable) proxyProperties);
                    /*********** optional, may be need in future *************/

                    onReceiveMethod.invoke(rec, appContext, intent);
                }
            }
        }
        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);
    } catch (InstantiationException 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;
}

2
我正在创建一个cordova安卓应用程序,但是无法弄清楚为什么对于公司网络上的内部主机的ajax请求在KitKat上失败了。所有本地网页请求都成功了,并且在4.4以下的安卓版本中,所有ajax请求也都成功了。只有当处于内部公司wifi时,ajax请求才会失败,这更加令人困惑。
结果发现,KitKat使用了一个新的chrome webview,它与以前的安卓版本中使用的标准webviews不同。在kitkat使用的chromium版本中存在一个bug,它不遵循代理排除列表。我们公司的wifi设置了代理服务器,并排除了所有内部主机。最终,ajax请求失败是因为代理身份验证失败。由于这些请求是针对内部主机的,所以根本不应该经过代理。我能够适应xjy2061的答案来适应我的用例。
希望这能帮助未来的某个人,节省他们几天的头痛。
//Set KitKat proxy w/ proxy exclusion.    
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void setKitKatWebViewProxy(Context appContext, String host, int port, String exclusionList) {

    Properties properties = System.getProperties();
    properties.setProperty("http.proxyHost", host);
    properties.setProperty("http.proxyPort", port + "");
    properties.setProperty("https.proxyHost", host);
    properties.setProperty("https.proxyPort", port + "");
    properties.setProperty("http.nonProxyHosts", exclusionList);
    properties.setProperty("https.nonProxyHosts", exclusionList);

    try {
        Class applictionCls = Class.forName("android.app.Application");
        Field loadedApkField = applictionCls.getDeclaredField("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);

                    /*********** optional, may be need in future *************/
                    final String CLASS_NAME = "android.net.ProxyProperties";
                    Class cls = Class.forName(CLASS_NAME);
                    Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
                    constructor.setAccessible(true);
                    Object proxyProperties = constructor.newInstance(host, port, exclusionList);
                    intent.putExtra("proxy", (Parcelable) proxyProperties);
                    /*********** optional, may be need in future *************/

                    onReceiveMethod.invoke(rec, appContext, intent);
                }
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}

您可以按照以下方式调用上述方法:
首先,在文件顶部导入此库。
import android.util.ArrayMap;

然后调用该方法。
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
//check first to see if we are running KitKat
if (currentapiVersion >= Build.VERSION_CODES.KITKAT){
    setKitKatWebViewProxy(context, proxy, port, exclusionList);
}

1

0

在某些设备上,当在onCreate中设置代理配置后立即加载页面时,提供的解决方案存在一些问题。稍微延迟一段时间后再打开网页可以解决这个问题。似乎代理配置需要一些时间才能生效。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接