Android的Webview使用代理访问网站的另一种实现

Android的Webview使用代理访问网站的另一种实现

以下内容不建议使用,因为 Google jetpack 库提供了代理方法,可以认为是官方的解决方案。

可以查看新的文章:android webview 设置代理


之前遇到一个需求,webview使用某个代理访问网站。但是却发现 webview 并没有提供相关接口 (现在jetpack 库提供了方法了),而能够搜索到的相关文档都是通过反射调用系统某些api,但这些 api 在高版本系统上显然用不了了。于是尝试通过拦截请求,自行构造返回数据来实现代理。

通过拦截网址请求,然后自行构造数据的方式可以很好的兼容 Android 高版本。

下面是尝试用拦截并从代理获取数据的 Demo:

创建项目

创建项目,并初始化一个 WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getBtn.setOnClickListener { getMethodUrl() }
        postBtn.setOnClickListener { postMethodUrl() }
        initWebView()

        webView.webViewClient = MWebViewClient()
    }

.........

    inner class MWebViewClient : WebViewClient() {
        override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
            val resp = proxyOpenUrl(url, "GET", null)
            if (resp != null) {
                return resp
            }
            return super.shouldInterceptRequest(view, url)
        }

        override fun shouldInterceptRequest(
            view: WebView?,
            request: WebResourceRequest?
        ): WebResourceResponse? {
            val resp =
                proxyOpenUrl(
                    request?.url?.toString(),
                    request?.method ?: "GET",
                    request?.requestHeaders
                )
            if (resp != null) {
                return resp
            }
            return super.shouldInterceptRequest(view, request)
        }
    }

通过自定义 WebviewClient 中的 shouldInterceptRequest 方法,来拦截一个 url 请求。

当一些初始化步骤完成后,接下来只要专注方法: proxyOpenUrl 即可

代理方法实现

proxyOpenUrl 方法的实现逻辑是:

根据传入的 url ,向代理服务器发出请求。接收到 inputStream 流,然后根据接收到的数据流,构造一个 Response 并返回给 WebviewClient。这样实际获取数据并不由 WebView 进行,而是自行获取,也可以通过 Okhttp 等库来获取响应数据

下面是具体的实现:

    private fun proxyOpenUrl(
        url: String?,
        method: String,
        headers: Map<String, String>?
    ): WebResourceResponse? {
        logger.i("request url:" + url)
        if (url == null) {
            return null
        }
        // 创建了一个 Proxy 类,并在 openConnection 的时候使用。这样便会通过代理打开链接
//            val webUrl = URL(url)
//            val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("192.168.1.101", 8080))
//            val urlConnection = webUrl.openConnection(proxy)

        // 上面的代理可以使用,但是有缺点是代理会被识别。下面是通过代理打开某些网站
        // 一般来说,没有特殊需求,直接使用上方的 HTTP 代理方式即可
        // 下面这种方式是通过一台服务器,在其后拼接url实现代理
        // 这种方式可以规避防火墙,且可以实现二级代理。例如:通过香港服务器,连接美国二级代理,最终出口IP显示为美国
        val webUrl = URL("https://cn-proxy.zwc365.com/$url")
        // 不使用代理请求时
        //val webUrl = URL("$url")

        val urlConnection = webUrl.openConnection() as HttpURLConnection
        urlConnection.requestMethod = method
        urlConnection.connect()

        val contentType = urlConnection.contentType
        var encoding = urlConnection.contentEncoding
        val httpCode = urlConnection.responseCode
        val headers = urlConnection.headerFields
        val types = contentType.split(";")
        if (encoding == null) {
            for (item in types) {
                if (item.contains("charset")) {
                    encoding = item.substring(item.indexOf("=") + "=".length)
                }
            }
        }
        logger.i(contentType)
        logger.i(encoding)
        logger.i(httpCode)
        logger.i(headers)

        try {
            val webResp = WebResourceResponse(
                types[0],
                encoding,
                httpCode,
                "status $httpCode",
                toHeader(headers),
                urlConnection.inputStream
            )
            return webResp
        } catch (e: Exception) {
            logger.i("get file faild:" + LogUtil.objToString(e))
        }
        return null
    }

实现效果

当运行这个程序,使用代理请求时会发现 IP 地址有所变更。这里以请求一个能够看到出口ip地址的网站为例(https://cip.cc 可以查看到当前出口IP):

当使用代理的时候,出口ip是

proxy

上面显示出口 IP 为一个 211 开头的地址,这是因为用于测试的代理服务搭建在另一个家庭网络环境中,运营商为鹏博士,可以看到已经正确的使用代理访问了目标网站。(当然也可以搭建在某些国外vps上)


当不使用代理的时候,出口ip是

noproxy

当使用 url 直接访问此网站,不经过代理请求时,此时的 IP 地址为180 开头,是电信的宽带网络。


至此,通过代理访问一个网站的功能已经实现。虽然没有直接提供代理api进行相关操作,但通过这种曲线救国方式也能实现代理访问网站的需求。

经过测试,这种代理方式可以很好的满足需求,如果是某些项目确实需要在手机上通过 WebView 代理访问网站, 那么这样做也可以有很好的兼容性,下面是实际使用过程中打开京东网页的截图:

jdproxy

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2020/11/11/android-webview-proxy

Buy me a cup of coffee ☕.