Android WebView使用DownloadListener下载特定站点PDF文件崩溃如何解决?
问题原因
- 权限适配不到位:Android 10(API 29)以上废弃了
WRITE_EXTERNAL_STORAGE权限,未适配分区存储会导致写入文件失败;Android 13(API 33)以上需要POST_NOTIFICATIONS权限才能让DownloadManager发送下载通知,缺少该权限会直接触发崩溃。 - 下载请求类型不支持:该站点的PDF下载是POST表单提交触发的,而系统DownloadManager仅默认支持GET请求,直接使用onDownloadStart返回的URL发起GET请求无法拿到正确的文件流,甚至触发异常。
- 参数容错缺失:如果接口返回的mimeType、contentDisposition为空或者包含非法字符,
URLUtil.guessFileName可能生成非法文件名,调用setDestinationInExternalPublicDir时会触发崩溃。
修复方案
首先修改AndroidManifest.xml的权限与配置:
<!-- 基础网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Android 6.0-9.0 所需存储权限,更高版本无需声明 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- Android 13+ 所需通知权限,用于展示下载进度通知 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <application ... <!-- Android 10 分区存储兼容配置,可直接写入公共下载目录 --> android:requestLegacyExternalStorage="true"> ... </application>
然后修改运行时权限申请逻辑,适配不同系统版本:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ArrayList<String> needRequestPermissions = new ArrayList<>(); // Android 9及以下才需要申请存储权限 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { needRequestPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } // Android 13及以上需要申请通知权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { needRequestPermissions.add(Manifest.permission.POST_NOTIFICATIONS); } if (!needRequestPermissions.isEmpty()) { requestPermissions(needRequestPermissions.toArray(new String[0]), 1); } }
最后修改DownloadListener逻辑,增加异常捕获和参数容错:
web_view.setDownloadListener(new DownloadListener() { @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) { try { // 修复mimeType为空的问题,缺省值设置为PDF类型 String finalMimeType = mimeType; if (TextUtils.isEmpty(finalMimeType)) { finalMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url)); if (TextUtils.isEmpty(finalMimeType)) { finalMimeType = "application/pdf"; } } DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); request.setMimeType(finalMimeType); String cookies = CookieManager.getInstance().getCookie(url); if (!TextUtils.isEmpty(cookies)) { request.addRequestHeader("cookie", cookies); } request.addRequestHeader("User-Agent", userAgent); request.setDescription("正在下载文件..."); // 过滤文件名非法字符,避免创建文件失败 String fileName = URLUtil.guessFileName(url, contentDisposition, finalMimeType); fileName = fileName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); request.setTitle(fileName); request.allowScanningByMediaScanner(); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); dm.enqueue(request); Toast.makeText(getApplicationContext(), "正在下载文件,可从文件管理器的下载目录查看", Toast.LENGTH_LONG).show(); } catch (Exception e) { Log.e("DownloadError", "下载失败: ", e); Toast.makeText(getApplicationContext(), "下载失败,请稍后重试", Toast.LENGTH_SHORT).show(); } } });
注意:如果修改后仍无法下载该站点PDF,说明该下载请求是POST类型,系统DownloadManager本身不支持POST下载,需要自行实现网络请求逻辑,拦截WebView的POST请求拿到请求参数后,手动发起请求写入本地文件即可。
内容的提问来源于stack exchange,提问作者Rafiqul Islam Parvez
相关产品推荐
相关产品推荐

