webview拦截请求(转)

本文详细探讨了如何在Webview中通过重写WebViewClient的方法来拦截超链接请求与非超链接请求,包括修改URL指向其他地址的技术细节,并特别强调了本地页面加载时的特殊拦截情况。

http://blog.youkuaiyun.com/knxw0001/article/details/10903055

1. 拦截超链接请求:重写webclient的

 

[java]  view plain copy
  1. @Override  
  2.     public boolean shouldOverrideUrlLoading(WebView view, String url) {  
  3.         Log.e("cache""shouldoverride-url="+url);  
  4.         return super.shouldOverrideUrlLoading(view, url);  
  5.     }  

 

这个在这里可以修改url,指向其他地址。

 

2. 拦截非超链接请求:重写webclient的

 

[java]  view plain copy
  1. @Override  
  2.     public void onLoadResource(WebView view, String url) {  
  3.         Log.e("cache""onLoadResource-url="+url);  
  4.         super.onLoadResource(view, url);  
  5.     }  

这个url修改无效,因为webkeit没有提供相应的中断方法。

 

 

3. 如果webview加载的本地的页面,如webview.loadUrl("file:///android_asset/savehtml.html");以上两个拦截不到,只有webclient的

 

[java]  view plain copy
  1. @Override  
  2.     public void onPageStarted(WebView view, String url, Bitmap favicon) {  
  3.         Log.e("cache""pagestarted-url="+url);  
  4.         super.onPageStarted(view, url, favicon);  
  5.     }  


可以拿到,但无法修改url。

<think>我们需要在WebView拦截请求,判断是否是下载地址(通过URL的特征,如文件扩展名等),然后触发下载操作。 我们可以通过自定义WebViewClient,重写shouldOverrideUrlLoading方法,在加载URL之前进行判断。 此外,我们还应该考虑拦截页面内的资源请求(如点击链接、表单提交等)。对于下载链接,我们可能还需要拦截其他类型的请求,比如页面中有iframe加载了下载链接,我们可以通过重写shouldInterceptRequest方法(但注意,这个方法主要用于资源拦截,通常不用于处理下载)。 然而,对于下载链接的拦截,更常见的做法是: 1. 在shouldOverrideUrlLoading中拦截导航请求(用户点击链接或通过JavaScript触发的导航)。 2. 在onLoadResource中拦截资源加载?但这个方法已经被废弃,且不推荐使用。 3. 在shouldInterceptRequest中拦截所有请求,包括资源请求(图片、CSS、JS等),但这样会非常频繁,而且通常我们不希望将资源请求当作下载链接。 因此,我们通常只处理导航请求(shouldOverrideUrlLoading)和页面重新加载(通过WebViewClient的其他回调)。另外,我们还可以处理WebView下载事件(通过设置WebChromeClient的onDownloadStart,但这是针对用户点击下载链接的情况)。 这里提供两种方案: 方案一:通过WebViewClient的shouldOverrideUrlLoading拦截导航请求(适用于用户点击链接跳下载的情况) 方案二:通过WebChromeClient的onDownloadStart拦截用户点击下载链接的事件(这是专门用于处理下载的方法)。 我们分别介绍两种方案: 方案一:使用WebViewClient拦截 我们在之前的回答中已经展示过,通过重写shouldOverrideUrlLoading,判断URL是否为下载链接(根据文件扩展名),然后触发下载。 方案二:使用WebChromeClient的onDownloadStart 这是更推荐的方式,因为当用户点击一个下载链接时,系统会回调onDownloadStart方法,我们可以在这里处理下载。 下面我们给出两种方案的代码示例: ### 方案一:WebViewClient拦截(在shouldOverrideUrlLoading中处理) 已在之前的回答中给出,这里再稍微补充一下: ```kotlin webView.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { if (isDownloadLink(url)) { // 显示弹窗确认下载 showDownloadDialog(url) return true // 拦截,不再加载 } return false // 正常加载 } // 新版本API(针对Android 24+) override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { val url = request.url.toString() if (isDownloadLink(url)) { showDownloadDialog(url) return true } return false } } ``` ### 方案二:WebChromeClient的onDownloadStart(推荐) 这是专门用于处理下载链接的回调。当WebView检测到用户点击了下载链接(通常是<a>标签并且指向一个可下载资源,且没有设置target属性)时,会触发此回调。 ```kotlin webView.webChromeClient = object : WebChromeClient() { override fun onDownloadStart( url: String?, userAgent: String?, contentDisposition: String?, mimeType: String?, contentLength: Long ) { url?.let { // 直接开始下载,或者显示弹窗 showDownloadDialog(it) } } } ``` 注意:onDownloadStart方法在用户点击下载链接时调用,因此它更符合用户意图。而shouldOverrideUrlLoading会拦截所有导航请求,包括非下载的页面跳,如果我们错误地将一个普通页面链接判断为下载链接,就会导致无法正常浏览。 因此,我们通常结合两者: - 对于用户明确点击的下载链接,使用onDownloadStart处理(更准确,因为这是系统识别的下载行为)。 - 对于其他情况(比如通过JavaScript自动触发的下载),我们可以在shouldOverrideUrlLoading中处理。 但是,有些时候onDownloadStart可能不会被触发(比如下载链接是通过JavaScript动态生成的,或者有target="_blank"等属性),所以为了更全面的拦截,我们可以同时使用两种方法。 下面给出一个整合的示例: ### 整合方案:同时使用WebViewClient和WebChromeClient ```kotlin class MainActivity : AppCompatActivity() { private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView = findViewById(R.id.webView) setupWebView() } private fun setupWebView() { webView.settings.javaScriptEnabled = true // 设置WebViewClient拦截导航请求 webView.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { if (isDownloadLink(url)) { showDownloadDialog(url) return true } return false } @TargetApi(Build.VERSION_CODES.N) override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { val url = request.url.toString() if (isDownloadLink(url)) { showDownloadDialog(url) return true } return false } } // 设置WebChromeClient处理下载链接点击 webView.webChromeClient = object : WebChromeClient() { override fun onDownloadStart( url: String?, userAgent: String?, contentDisposition: String?, mimeType: String?, contentLength: Long ) { url?.let { showDownloadDialog(it) } } } } // 判断是否为下载链接(根据扩展名) private fun isDownloadLink(url: String): Boolean { // 常见的下载文件扩展名 val downloadExtensions = arrayOf( ".apk", ".zip", ".rar", ".tar", ".gz", ".7z", ".exe", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".mp3", ".wav", ".ogg", ".mp4", ".avi", ".mkv", ".txt", ".csv", ".log", ".xml", ".json", ".html", ".htm" ) return downloadExtensions.any { url.toLowerCase(Locale.ROOT).endsWith(it) } } private fun showDownloadDialog(url: String) { // 显示下载确认弹窗,用户确认后调用startDownload(url) // 这里简单起见,直接开始下载 startDownload(url) } private fun startDownload(url: String) { // 使用DownloadManager下载 val request = DownloadManager.Request(Uri.parse(url)) .setTitle("文件下载") .setDescription("正在下载文件") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, Uri.parse(url).lastPathSegment) .allowScanningByMediaScanner() val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager downloadManager.enqueue(request) } } ``` ### 注意事项 1. **扩展名判断的局限性**: - 某些下载链接可能没有扩展名(比如通过动态生成的下载链接),这时候需要结合MIME类型判断。 - 在onDownloadStart方法中,我们可以通过参数`mimeType`来判断,但在shouldOverrideUrlLoading中,我们无法直接获取MIME类型。 2. **动态请求的处理**: - 如果下载是通过JavaScript动态触发的(比如通过XMLHttpRequest下载),上述方法都无法拦截。这种情况需要前端配合,或者使用其他方案(如Service Worker等),但在移动端WebView中较难实现。 3. **权限问题**: - 确保在AndroidManifest.xml中声明了必要的权限: ```xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` - 在Android 6.0及以上,需要动态请求存储权限。 4. **Android 10+存储权限变更**: - 在Android 10及以上,应用默认只能访问自己的沙盒目录。如果要将文件保存到公共Downloads目录,需要添加权限: ```xml <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- 仅Android 9及以下需要 --> ``` - 对于Android 10及以上,可以使用`MediaStore` API,或者继续使用`DownloadManager`(它已经适配了作用域存储)。 ### 相关问题 §§相关问题§§: 1. 在Android WebView中如何处理没有文件扩展名的下载链接? 2. 如何通过MIME类型(Content-Type)来判断WebView中的下载链接? 3. WebView中如何实现文件上传功能? 4. 如何优化WebView下载大文件时的用户体验(如后台下载、断点续传等)? </think>### WebView 拦截下载链接实现方案 在 Android WebView 中精确拦截下载链接需要综合考虑多种识别方法,以下是一个完整的实现方案,结合了 URL 扩展名、MIME 类型和自定义请求头分析: #### 完整实现代码(Kotlin) ```kotlin class DownloadInterceptorWebViewClient : WebViewClient() { // 扩展名检测列表 private val downloadExtensions = setOf( "apk", "zip", "rar", "7z", "tar", "gz", "bz2", "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "mp3", "wav", "flac", "mp4", "avi", "mkv", "mov", "jpg", "jpeg", "png", "gif", "bmp", "webp", "exe", "dmg", "deb", "rpm", "ipa" ) override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { request?.url?.let { url -> val urlStr = url.toString() if (isDownloadLink(urlStr)) { handleDownload(view, urlStr) return true } } return super.shouldOverrideUrlLoading(view, request) } override fun shouldInterceptRequest( view: WebView?, request: WebResourceRequest? ): WebResourceResponse? { request?.let { // 通过Content-Type检测 it.requestHeaders["Content-Type"]?.let { contentType -> if (isDownloadContentType(contentType)) { handleDownload(view, it.url.toString()) return createEmptyResponse() } } // 通过Content-Disposition检测 it.requestHeaders["Content-Disposition"]?.let { disposition -> if (disposition.contains("attachment")) { handleDownload(view, it.url.toString()) return createEmptyResponse() } } } return super.shouldInterceptRequest(view, request) } // 检测是否为下载链接 private fun isDownloadLink(url: String): Boolean { // 方法1:文件扩展名检测 val path = Uri.parse(url).path ?: return false val extension = path.substringAfterLast('.', "").lowercase() if (downloadExtensions.contains(extension)) return true // 方法2:常见下载路径模式匹配 val downloadPatterns = listOf( "/download/", "/files/", "/attachments/", "/export?format=", "action=download" ) if (downloadPatterns.any { url.contains(it) }) return true return false } // 检测Content-Type是否为下载类型 private fun isDownloadContentType(contentType: String): Boolean { val downloadTypes = listOf( "application/octet-stream", "application/zip", "application/x-rar-compressed", "application/x-msdownload", // .exe "application/vnd.android.package-archive" // .apk ) // 匹配主要类型:application/* return contentType.startsWith("application/") && !contentType.startsWith("application/json") && !contentType.startsWith("application/xml") || downloadTypes.any { contentType.contains(it) } } // 创建空响应(用于阻断下载) private fun createEmptyResponse(): WebResourceResponse { return WebResourceResponse("text/plain", "UTF-8", null) } // 处理下载逻辑 private fun handleDownload(webView: WebView?, url: String) { webView?.context?.let { context -> AlertDialog.Builder(context) .setTitle("下载确认") .setMessage("检测到下载请求:\n${Uri.parse(url).lastPathSegment}") .setPositiveButton("下载") { _, _ -> startDownload(context, url) } .setNegativeButton("取消", null) .show() } } // 启动下载 private fun startDownload(context: Context, url: String) { val request = DownloadManager.Request(Uri.parse(url)) .setTitle("文件下载") .setDescription("正在下载 ${Uri.parse(url).lastPathSegment}") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) .setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, Uri.parse(url).lastPathSegment ) .allowScanningByMediaScanner() val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager downloadManager.enqueue(request) } } ``` #### 在 Activity 中使用 ```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val webView: WebView = findViewById(R.id.webView) webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true } // 设置自定义WebViewClient拦截下载 webView.webViewClient = DownloadInterceptorWebViewClient() // 加载网页 webView.loadUrl("https://your-website.com") } } ``` ### 实现原理详解 #### 1. 多维度下载链接识别 - **文件扩展名检测**: ```kotlin val extension = path.substringAfterLast('.', "").lowercase() if (downloadExtensions.contains(extension)) return true ``` 检测超过 30 种常见文件扩展名 - **URL 路径模式匹配**: ```kotlin val downloadPatterns = listOf("/download/", "/files/", ...) if (downloadPatterns.any { url.contains(it) }) return true ``` 识别包含 `/download/`、`/files/` 等常见下载路径 - **Content-Type 检测**: ```kotlin contentType.startsWith("application/") && !contentType.startsWith("application/json") ``` 拦截 `application/octet-stream` 等下载类型,排除 JSON/XML - **Content-Disposition 检测**: ```kotlin if (disposition.contains("attachment")) ``` 识别 `Content-Disposition: attachment` 响应头 #### 2. 双重拦截机制 1. **`shouldOverrideUrlLoading`**: - 处理页面导航请求 - 基于 URL 模式识别下载链接 2. **`shouldInterceptRequest`**: - 拦截所有资源请求(API 21+) - 基于 HTTP 头信息识别下载内容 - 返回空响应阻断下载: ```kotlin return WebResourceResponse("text/plain", "UTF-8", null) ``` #### 3. 下载处理流程 1. 显示确认对话框 2. 使用 DownloadManager 处理下载 3. 文件保存到 Downloads 目录 4. 显示通知栏进度 ### 高级优化建议 #### 1. 动态扩展名列表 ```kotlin // 从服务器获取扩展名列表 fun fetchDownloadExtensions() { val extensions = listOf("xyz", "newformat") // 从API获取 downloadExtensions.addAll(extensions) } ``` #### 2. MIME 类型白名单 ```kotlin private val allowedMimeTypes = setOf( "text/html", "text/css", "application/javascript", "image/*", "font/*", "application/json" ) private fun isAllowedContentType(contentType: String): Boolean { return allowedMimeTypes.any { pattern -> when { pattern.endsWith("/*") -> contentType.startsWith(pattern.substringBefore("/*")) else -> contentType == pattern } } } ``` #### 3. 下载管理器增强 ```kotlin private fun enhancedDownload(context: Context, url: String) { // 自定义文件名解析 val fileName = resolveFileName(url) ?: "download_${System.currentTimeMillis()}" // 创建下载目录 val downloadDir = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "MyAppDownloads" ).apply { mkdirs() } // 设置下载请求 val request = DownloadManager.Request(Uri.parse(url)) .setTitle("下载: $fileName") .setDestinationUri(Uri.fromFile(File(downloadDir, fileName))) .setRequiresCharging(false) // 充电时下载 .setAllowedOverMetered(true) // 允许移动数据 // 大文件警告 if (contentLength > 50 * 1024 * 1024) { // 50MB request.setDescription("警告:大文件下载 (${contentLength / (1024*1024)}MB)") } } ``` ### 注意事项 1. **动态权限处理**: - Android 6.0+ 需要请求 WRITE_EXTERNAL_STORAGE 权限 - Android 10+ 需要适配 Scoped Storage 2. **安全限制**: ```xml <application android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/network_security_config"> ``` 3. **性能优化**: - 避免在 shouldInterceptRequest 执行耗时操作 - 使用缓存机制减少重复判断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值