Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Android webview拦截H5接口请求

Android webview拦截H5的接口请求并返回处理好的数据代码示例

作者:大渔歌_

这篇文章主要给大家介绍了关于Android webview拦截H5的接口请求并返回处理好的数据的相关资料,通过WebView的shouldInterceptRequest方法,Android可以拦截并处理WebView中的H5网络请求,需要的朋友可以参考下

Android webview拦截H5的接口请求并返回处理好的数据

Android 可以通过 WebView 的 shouldInterceptRequest 方法拦截到 H5 中的网络请求。这是一个 WebViewClient 中的回调方法,允许开发者在 WebView 发起网络请求时对其进行处理和修改。

具体使用方法如下:

以下是代码示例:

// Kotlin 代码示例
webView.webViewClient = object : WebViewClient() {
    // 对于 API 21 及更高版本
    override fun shouldInterceptRequest(
        view: WebView, 
        request: WebResourceRequest
    ): WebResourceResponse? {
        val url = request.url.toString()
        // 这里你可以判断 URL,并根据需要拦截或修改请求
        if (url.contains("your_target_url")) {
            // 可以在这里做一些处理,例如替换请求或返回本地数据
            val inputStream = ... // 自定义输入流
            return WebResourceResponse("text/html", "UTF-8", inputStream)
        }
        // 不拦截,继续请求
        return super.shouldInterceptRequest(view, request)
    }
    
    // 对于 API 11 到 API 20 的设备
    override fun shouldInterceptRequest(
        view: WebView, 
        url: String
    ): WebResourceResponse? {
        // 这里处理逻辑类似于上面的代码
        if (url.contains("your_target_url")) {
            // 自定义处理逻辑
            val inputStream = ... 
            return WebResourceResponse("text/html", "UTF-8", inputStream)
        }
        return super.shouldInterceptRequest(view, url)
    }
}

在 shouldInterceptRequest 方法中,你可以返回一个 WebResourceResponse 对象,来改变或替换原始的网络请求,也可以通过默认实现让请求继续执行。

获取网络接口请求的数据(如 API 请求的响应),然后将其返回给 H5,

有以下几种方式可以考虑:

1. 拦截请求并手动发起请求

你可以通过 shouldInterceptRequest 方法拦截 WebView 的 API 请求,自己在 Java 或 Kotlin 中使用 HttpURLConnection 或 OkHttp 发起网络请求,处理完响应后,再将响应数据传递给 H5。

示例代码:

webView.webViewClient = object : WebViewClient() {
    override fun shouldInterceptRequest(
        view: WebView, 
        request: WebResourceRequest
    ): WebResourceResponse? {
        val url = request.url.toString()

        // 判断是否是需要拦截的接口请求
        if (url.contains("your_api_endpoint")) {
            // 通过 OkHttp 或 HttpURLConnection 发起请求
            val response = fetchApiData(url)

            // 将获取的响应内容传递给 H5
            view.post {
                view.evaluateJavascript("javascript:handleApiResponse('${response}')", null)
            }

            // 返回一个空响应或自定义内容
            return WebResourceResponse("application/json", "UTF-8", null)
        }

        return super.shouldInterceptRequest(view, request)
    }

    // 使用 OkHttp 发起网络请求(可以根据你的需求选择合适的网络库)
    private fun fetchApiData(url: String): String {
        // 简单 OkHttp 请求示例
        val client = OkHttpClient()
        val request = Request.Builder().url(url).build()

        client.newCall(request).execute().use { response ->
            return response.body?.string() ?: ""
        }
    }
}

在这个例子中,shouldInterceptRequest 会拦截指定的网络请求,然后用 OkHttp 发起请求,并将获取的响应数据通过 evaluateJavascript 方法传回到 WebView 中的 H5 页面。

2. 通过 WebView 和 H5 的 JavaScript 通信

另一种方式是通过 WebView 的 addJavascriptInterface 方法,将 Android 代码和 H5 进行通信。当 Android 发起请求后,可以通过接口将数据传递给 H5。

在 H5 中定义一个 JavaScript 函数,用于接收数据:

function handleApiResponse(response) {
    console.log("API Response: ", response);
    // 这里可以对响应进行处理,渲染到页面等
}

在 Android 端:

webView.addJavascriptInterface(object {
    @JavascriptInterface
    fun sendDataToH5(data: String) {
        // 发送数据到 H5
        webView.evaluateJavascript("javascript:handleApiResponse('$data')", null)
    }
}, "AndroidInterface")

webView.webViewClient = object : WebViewClient() {
    override fun shouldInterceptRequest(
        view: WebView, 
        request: WebResourceRequest
    ): WebResourceResponse? {
        val url = request.url.toString()
        
        if (url.contains("your_api_endpoint")) {
            val response = fetchApiData(url)
            
            // 通过 JavaScript 接口传递数据到 H5
            view.post {
                view.evaluateJavascript("javascript:AndroidInterface.sendDataToH5('${response}')", null)
            }
            
            return WebResourceResponse("application/json", "UTF-8", null)
        }
        return super.shouldInterceptRequest(view, request)
    }
}

这种需要考虑到H5 发过来的网络接口请求类型吗?post 还是 get

是的,在使用 shouldInterceptRequest 拦截 H5 发过来的网络请求时,需要考虑请求的类型(GET 或 POST)。这是因为 GET 和 POST 请求的处理方式不同,尤其是 POST 请求,它带有请求体(body)数据,而 GET 请求通常只包含 URL 参数。

shouldInterceptRequest 可以帮助你拦截请求,但对于 POST 请求的处理,需要特别注意提取和使用请求体。

如何区分 GET 和 POST 请求?

在 API 21 及更高版本中,shouldInterceptRequest 方法中的 WebResourceRequest 对象包含了更多信息,可以通过它的 getMethod() 方法来区分请求类型。

override fun shouldInterceptRequest(
    view: WebView, 
    request: WebResourceRequest
): WebResourceResponse? {
    val url = request.url.toString()
    val method = request.method  // 获取请求方法,如 GET 或 POST

    // 根据请求类型处理
    if (method == "POST") {
        // 获取 POST 请求的 body(在 Android 的 WebView 中,直接获取 POST body 是不支持的,除非使用特定工具)
        // 自定义逻辑处理 POST 请求
    } else if (method == "GET") {
        // 处理 GET 请求
    }

    return super.shouldInterceptRequest(view, request)
}

处理 POST 请求

Android 的 shouldInterceptRequest 本身不直接提供获取 POST 请求体的功能。不过你可以使用其他方式来处理,例如通过在 JS 中提前将请求体通过某种方式传递给 Android。

方法 1:使用 evaluateJavascript 传递 POST 请求数据

你可以让 H5 页面在发出 POST 请求前,通过 JavaScript 提前将 POST 请求的数据发送到 WebView。然后 Android 端可以拦截并处理这些数据。

方法 2:通过自定义网络层发送 POST 请求

另一种方式是在 shouldInterceptRequest 中拦截 POST 请求后,手动发起 HTTP 请求,处理请求体和响应数据。虽然 shouldInterceptRequest 本身不直接提供请求体,但你可以在 H5 页面中通过 JavaScript 提前将 POST 请求体传递给 Android,或者通过其他网络拦截方式处理(例如使用 OkHttp 的拦截器)。

override fun shouldInterceptRequest(
    view: WebView, 
    request: WebResourceRequest
): WebResourceResponse? {
    val url = request.url.toString()
    val method = request.method  // 获取请求方法,如 GET 或 POST

    if (method == "POST") {
        // 自定义发起 HTTP 请求,处理 POST 请求体和响应
        val response = performCustomHttpPost(url, request)
        return WebResourceResponse("application/json", "UTF-8", response)
    }

    return super.shouldInterceptRequest(view, request)
}

fun performCustomHttpPost(url: String, request: WebResourceRequest): InputStream {
    // 使用 OkHttp 或 HttpURLConnection 发送自定义 POST 请求
    val client = OkHttpClient()
    val requestBody = ... // 构建请求体

    val request = Request.Builder()
        .url(url)
        .post(requestBody)
        .build()

    val response = client.newCall(request).execute()
    return response.body?.byteStream() ?: ByteArrayInputStream(ByteArray(0))
}

小结

shouldInterceptRequest 能判断出来网络请求的类型吗?比如script xhr 等

shouldInterceptRequest 本身并不能直接判断网络请求的具体类型(如 scriptxhr 等),因为它没有提供一个字段明确表明请求是某种类型的资源(如 JavaScript 文件、XHR 请求等)。不过,你可以通过请求的 URL 和 请求头信息 来推断请求的类型。

在 API 21 及以上的 Android 版本中,WebResourceRequest 对象提供了丰富的信息,包括请求的 URL、请求方法(GET、POST 等)、请求头等,可以根据这些信息推断请求类型。

如何通过 URL 和请求头判断请求类型?

代码示例:如何通过 URL 和请求头推断请求类型

override fun shouldInterceptRequest(
    view: WebView, 
    request: WebResourceRequest
): WebResourceResponse? {
    val url = request.url.toString()
    val headers = request.requestHeaders
    val acceptHeader = headers["Accept"]  // 获取 Accept 头

    // 判断是否为脚本请求 (script)
    if (url.endsWith(".js") || acceptHeader?.contains("application/javascript") == true) {
        Log.d("WebView", "拦截到脚本请求: $url")
        // 在此可以拦截或修改脚本请求
    }

    // 判断是否为 XHR 请求 (通过 URL 或 Accept 头判断)
    if (url.contains("/api/") || acceptHeader?.contains("application/json") == true) {
        Log.d("WebView", "拦截到 XHR 请求: $url")
        // 在此可以拦截或修改 XHR 请求
    }

    // 判断是否为 CSS 请求
    if (url.endsWith(".css") || acceptHeader?.contains("text/css") == true) {
        Log.d("WebView", "拦截到 CSS 请求: $url")
        // 在此可以拦截或修改 CSS 请求
    }

    // 其他资源类型的请求
    Log.d("WebView", "其他类型的请求: $url")
    
    return super.shouldInterceptRequest(view, request)
}

常见资源类型的判断方式

小结

虽然 shouldInterceptRequest 没有直接提供资源类型的字段,但你可以通过 URL 的格式和请求头(特别是 Accept 头)来推断请求的类型,从而拦截特定的资源请求。

script xhr font png preflight 分别是什么,应该怎么判断呢?都可以通过 val acceptHeader = headers["Accept"] 来进行判断吗?

Accept 头在 HTTP 请求中指示客户端希望接收的响应数据类型,它确实能在一些情况下帮助我们判断请求的资源类型。但并不是所有类型的请求都会明确使用 Accept 头。因此,我们需要结合 URL 和 请求头 的其他信息(如 Content-TypeAccess-Control-Request-Method 等)来进行判断。

1. Script(JavaScript 请求)

JavaScript 请求是用来加载 .js 文件的资源请求。

判断依据:

if (url.endsWith(".js") || acceptHeader?.contains("application/javascript") == true || acceptHeader?.contains("text/javascript") == true) {
    Log.d("WebView", "拦截到 JavaScript 请求: $url")
}

2. XHR 请求(XMLHttpRequest / Fetch 请求)

XHR 请求通常用于 AJAX 请求或使用 Fetch API 的异步网络请求。

判断依据:

if (url.contains("/api/") || acceptHeader?.contains("application/json") == true || headers["X-Requested-With"] == "XMLHttpRequest") {
    Log.d("WebView", "拦截到 XHR 请求: $url")
}

3. Font 请求(字体文件请求)

字体文件通常以 .woff.woff2.ttf.otf 等后缀结尾。

判断依据:

if (url.endsWith(".woff") || url.endsWith(".woff2") || url.endsWith(".ttf") || url.endsWith(".otf") || acceptHeader?.contains("font/") == true) {
    Log.d("WebView", "拦截到字体文件请求: $url")
}

4. PNG 请求(图片请求)

图片请求通常包括 .png.jpg.gif 等格式。

判断依据:

if (url.endsWith(".png") || url.endsWith(".jpg") || url.endsWith(".jpeg") || url.endsWith(".gif") || acceptHeader?.contains("image/") == true) {
    Log.d("WebView", "拦截到图片请求: $url")
}

5. Preflight 请求

Preflight 请求是 CORS 请求的一部分,通常在跨域 POST、PUT、DELETE 请求前发送,以确定服务器是否允许该请求。Preflight 请求使用 OPTIONS 方法发起。

判断依据:

if (request.method == "OPTIONS" && headers.containsKey("Access-Control-Request-Method")) {
    Log.d("WebView", "拦截到 Preflight 请求: $url")
}

结合 URL 和 Accept 头进行判断

总结不同请求类型的判断依据:

请求类型URL 后缀Accept 头其他请求头信息
Script (JavaScript).jsapplication/javascripttext/javascript
XHR (AJAX)/api//ajax/application/jsonX-Requested-With: XMLHttpRequest
Font (字体).woff.woff2.ttf.otffont/*application/font-woff
Image (图片).png.jpg.jpeg.gifimage/*
Preflight (CORS)OPTIONS 方法,Access-Control-Request-Method

小结

返回的响应不符合预期的格式,怎么处理

出现 AxiosError: Network Error, Code: ERR_NETWORK 错误,通常是因为 WebView 在处理拦截的网络请求时,返回的响应不符合预期的格式,或者某些必要的头信息缺失。这里有几个常见的原因及解决办法:

问题可能的原因:

如何正确返回自定义响应

确保你使用 WebResourceResponse 构建响应时,包括了所有必要的头信息和流数据。

修改代码示例

override fun shouldInterceptRequest(
    view: WebView, 
    request: WebResourceRequest
): WebResourceResponse? {
    val url = request.url.toString()
    
    if (url.contains("your_api_endpoint")) {
        // 发起网络请求获取数据
        val response = fetchApiData(url)
        
        // 处理返回的响应流
        val byteStream = response.body?.byteStream() ?: ByteArrayInputStream(ByteArray(0))
        val contentType = response.header("Content-Type", "application/json") // 设置默认内容类型
        val contentLength = response.header("Content-Length", "-1").toLong() // 设置内容长度
        
        // 构建 WebResourceResponse 并返回给 WebView
        return WebResourceResponse(
            contentType, // 内容类型
            "utf-8",     // 编码格式
            200,         // HTTP 状态码
            "OK",        // 状态描述
            mapOf(        // 响应头
                "Access-Control-Allow-Origin" to "*",
                "Content-Length" to contentLength.toString()
            ),
            byteStream   // 响应内容的输入流
        )
    }

    return super.shouldInterceptRequest(view, request)
}

// 使用 OkHttp 发起网络请求
private fun fetchApiData(url: String): Response {
    val client = OkHttpClient()
    val request = Request.Builder().url(url).build()

    return client.newCall(request).execute() // 直接返回 Response 对象
}

关键修改点:

检查点

调试建议

shouldInterceptRequest 拦截POST请求的时候,如何获取POST 请求的 body 数据?

在 Android 的 WebViewClient 中通过 shouldInterceptRequest 拦截 POST 请求并构建自定义的 HTTP 请求时,首先需要手动构建请求体(如从 WebResourceRequest 中获取必要的请求信息),然后使用 OkHttp 或 HttpURLConnection 来发送这个 POST 请求。

WebResourceRequest 不直接提供 POST 请求体,因此要获取并手动构建 POST 请求体。这可以通过 requestHeaders 来构建,或在前端(H5)发送时提前传递 POST 数据。

处理 POST 请求并构建请求体的完整示例:

override fun shouldInterceptRequest(
    view: WebView,
    request: WebResourceRequest
): WebResourceResponse? {
    val url = request.url.toString()
    val method = request.method

    if (method == "POST") {
        Log.d("WebView", "拦截到 POST 请求: $url")

        // 自定义处理 POST 请求,构建请求体并发送
        val responseStream = performCustomHttpPost(url, request)

        // 返回自定义的 WebResourceResponse
        return WebResourceResponse(
            "application/json", // 假设返回的是 JSON 响应
            "UTF-8",
            200, // HTTP 状态码
            "OK", // HTTP 状态描述
            mapOf("Access-Control-Allow-Origin" to "*"), // 响应头
            responseStream // 响应数据的输入流
        )
    }

    return super.shouldInterceptRequest(view, request)
}

// 自定义处理 POST 请求的逻辑
fun performCustomHttpPost(url: String, request: WebResourceRequest): InputStream {
    // 构建 POST 请求体(假设 H5 端发送的 POST 数据是 JSON 格式)
    val postData = getPostData(request) // 假设这里可以提取请求体数据

    // 日志记录请求体数据
    Log.d("WebView", "POST 请求数据: $postData")

    // 使用 OkHttp 发送自定义的 POST 请求
    val client = OkHttpClient()
    val requestBody = RequestBody.create(
        MediaType.parse("application/json; charset=utf-8"), // 假设请求体是 JSON 数据
        postData ?: "" // POST 请求的数据
    )

    val customRequest = Request.Builder()
        .url(url)
        .post(requestBody)
        .build()

    // 发起请求并返回响应流
    val response = client.newCall(customRequest).execute()

    Log.d("WebView", "HTTP 响应码: ${response.code()}") // 日志记录响应状态码

    return response.body?.byteStream() ?: ByteArrayInputStream(ByteArray(0))
}

// 获取 POST 数据(需要通过前端传递或其他方式获取)
fun getPostData(request: WebResourceRequest): String? {
    // WebView 无法直接获取 POST body 数据,需要前端配合通过 evaluateJavascript 或其他方式传递
    // 假设我们从 requestHeaders 中获取部分数据(实际可以根据你的需求进行修改)
    val contentType = request.requestHeaders["Content-Type"]
    Log.d("WebView", "Content-Type: $contentType")

    // 这里返回模拟的 POST 数据,实际情况下需要处理真实的请求体
    return "{ \"key\": \"value\" }"
}
关键点:
获取请求体数据:Android WebView 本身不提供获取 POST 请求体的直接方式,需要通过前端协作(如 evaluateJavascript)来传递请求体,或者自行处理模拟请求体。

手动构建 POST 请求体:使用 OkHttp 的 RequestBody.create() 方法手动构建 POST 请求体。

记录日志:

通过 Log.d() 记录拦截的 URL 和请求体数据,便于调试。
记录 HTTP 响应状态码,查看请求是否成功。
处理异常情况:如果响应体为空,返回一个空的 ByteArrayInputStream(),以确保 WebView 不会崩溃。

关于获取真实的 POST 请求体
Android 的 WebView 没有内置机制直接获取 POST 请求体,因此需要通过 JavaScript 与 Android 通信,在 H5 端主动将 POST 数据发送给 WebView。例如:
// 在 H5 端获取 POST 请求体并传递给 Android
function sendPostDataToAndroid(data) {
    if (window.AndroidInterface) {
        window.AndroidInterface.sendPostData(JSON.stringify(data));
    }
}

然后在 Android 端通过 addJavascriptInterface 接收数据:

@JavascriptInterface
fun sendPostData(data: String) {
    // 处理从 H5 传递过来的 POST 数据
    Log.d("WebView", "收到的 POST 数据: $data")
}

最后

其实获取post请求体参数内容上述的方法可以尝试一下,当然,还有一种更简单的取巧的方法,如果H5 POST请求数量不是很多的话,可以和H5沟通好,直接把请求数据放在请求的url中,中间通过特定字符@隔开,然后我们拿到后进行处理,

            // 判断是否为 XHR 请求 (通过 URL 或 Accept 头判断)
            if ((url.contains("/api/") || acceptHeader?.contains("application/json") == true ||
                        headers["X-Requested-With"] == "XMLHttpRequest") && !url.endsWith("html")) {
                LogUtils.i( "拦截到 XHR 请求: $url")
                // 在此可以拦截或修改 XHR 请求
                var respone: Response? = null
                if(method == "POST"){
                    val params = url.split("@")
                    val test = params[0]                    // 获取 POST 请求的 body 数据
                    val postData = URLDecoder.decode(params[1], StandardCharsets.UTF_8.toString())
                    LogUtils.i("postData = $postData")
                    respone = fetchApiData2(test,method,postData)
                }else if(method == "GET"){
                    respone = fetchApiData(url,method,null)
                }
                val byteStream = respone?.body()?.byteStream() ?: ByteArrayInputStream(ByteArray(0))
                val contentType = respone?.header("Content-Type", "application/json") // 设置默认内容类型
                val contentLength = respone?.header("Content-Length", "-1")?.toLong() // 设置内容长度

                LogUtils.i("fetchApiData respone = ${respone.toString()}")
                return WebResourceResponse(
                    contentType, // 内容类型
                    "utf-8",     // 编码格式
                    200,         // HTTP 状态码
                    "OK",        // 状态描述
                    mapOf(        // 响应头
                        "Access-Control-Allow-Origin" to "*",
                        "Content-Length" to contentLength.toString()
                    ),
                    byteStream   // 响应内容的输入流
                )

//                return WebResourceResponse("application/json", "utf-8",respone)
            }
    private fun fetchApiData2(url: String, method: String, postData: String?): Response{
        LogUtils.i("fetchApiData2 = $url + $method + $postData")
        val client = UnsafeOkHttpClient.unsafeOkHttpClient.build()
        val requestBody = RequestBody.create(
            MediaType.parse("application/json; charset=utf-8"), // 假设请求体是 JSON 数据
            postData ?: "" // POST 请求的数据
        )
        val requestBuilder = Request.Builder()
            .url(url)
            .post(requestBody)
            .build()

        return client.newCall(requestBuilder).execute()
    }
    // 根据请求类型进行处理
    private fun fetchApiData(url: String, method: String, postData: Map<String, String>?): Response  {
        LogUtils.i("fetchApiData = $url + $method + $postData")
        val client = UnsafeOkHttpClient.unsafeOkHttpClient.build()
        val requestBuilder = Request.Builder().url(url)

        // 根据请求类型构建请求
        if (method == "POST" && postData != null) {
            val formBody = FormBody.Builder()
            postData.forEach { (key, value) -> formBody.add(key, value) }

            requestBuilder.post(formBody.build())
        }

        val request = requestBuilder.build()

        return client.newCall(request).execute() // 直接返回 Response 对象
}

总结 

到此这篇关于Android webview拦截H5的接口请求并返回处理好的数据的文章就介绍到这了,更多相关Android webview拦截H5接口请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文