以下内容不建议使用,因为 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是:
上面显示出口 IP 为一个 211 开头的地址,这是因为用于测试的代理服务搭建在另一个家庭网络环境中,运营商为鹏博士,可以看到已经正确的使用代理访问了目标网站。(当然也可以搭建在某些国外vps上)
当不使用代理的时候,出口ip是:
当使用 url 直接访问此网站,不经过代理请求时,此时的 IP 地址为180 开头,是电信的宽带网络。
至此,通过代理访问一个网站的功能已经实现。虽然没有直接提供代理api进行相关操作,但通过这种曲线救国方式也能实现代理访问网站的需求。
经过测试,这种代理方式可以很好的满足需求,如果是某些项目确实需要在手机上通过 WebView 代理访问网站, 那么这样做也可以有很好的兼容性,下面是实际使用过程中打开京东网页的截图: