Android 11(API 29)应用生产环境文件下载失效问题求助
问题背景
应用上线2年,通过<a href>标签结合PHP readfile()函数实现从Web服务器下载文件的功能。Android 11版本前运行正常,但升级至Android 11后,生产环境触发下载无任何响应,但通过Android Studio调试安装、或上传至Google Play内部测试渠道时功能正常。
已在Manifest中配置相关权限及requestLegacyExternalStorage,配置如下:
<uses-permission android:maxSdkVersion="28" android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.RECORD_VIDEO"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-feature android:name="android.hardware.location.gps" android:required="true"/> <application android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:usesCleartextTraffic="true">
可能原因及解决方案
1. ProGuard/R8 混淆破坏下载逻辑
生产环境通常会开启代码混淆,若WebView下载相关的类或回调被误混淆,会导致下载流程失效,而调试/内部测试版本可能未开启混淆,因此功能正常。
解决方法:在ProGuard规则文件(proguard-rules.pro)中添加以下规则,保护WebView下载相关组件:
-keep class android.webkit.DownloadListener { *; } -keep class android.webkit.WebChromeClient { *; } -keep class android.webkit.WebViewClient { *; }
若自定义了下载回调类,也要添加对应的-keep规则。
2. PHP服务器响应头不规范
Android 11+的WebView对下载请求的响应头要求更严格,生产环境服务器的响应头配置可能与测试环境不一致,导致下载被拦截。
解决方法:检查PHP端的响应头设置,确保包含以下关键参数:
Content-Disposition: 必须设置为attachment; filename="文件名.后缀",明确标识为附件下载Content-Length: 准确设置文件大小Content-Type: 匹配文件的MIME类型(如application/pdf、image/jpeg等)
示例PHP代码修正:
$filePath = 'path/to/your/file'; $fileName = 'downloaded_file.pdf'; header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="' . $fileName . '"'); header('Content-Length: ' . filesize($filePath)); readfile($filePath); exit;
可通过curl -I 下载链接命令对比测试环境与生产环境的响应头差异。
3. Google Play签名导致Manifest属性失效
部分情况下,Google Play App Signing流程可能意外修改Manifest中的requestLegacyExternalStorage属性,导致生产环境无法启用旧存储模式。
解决方法:下载生产环境的APK包,通过反编译工具查看Manifest文件,确认requestLegacyExternalStorage="true"属性是否存在。若缺失,需重新打包并确保该属性在Manifest中正确配置后再上传。
4. 运行时权限未正确获取
虽然Manifest中声明了READ_EXTERNAL_STORAGE权限,但Android 10+(含11)需要动态申请该权限。调试时IDE可能自动授予权限,而生产环境用户可能未授权,导致下载路径无法访问。
解决方法:在触发下载前,检查并请求READ_EXTERNAL_STORAGE权限:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_STORAGE); } else { // 执行下载逻辑 }
5. 切换至DownloadManager实现下载
依赖WebView的<a href>跳转下载在Android 11+的存储规范下兼容性较差,改用系统DownloadManager是更稳定的方案,无需依赖WebView,且自动适配Scoped Storage。
示例代码:
String downloadUrl = "https://your-server.com/file/download"; String fileName = "target_file.ext"; DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl)); request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE); request.setTitle("文件下载"); request.setDescription("正在下载文件"); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); // 下载到公共Download目录,无需WRITE权限 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); if (downloadManager != null) { downloadManager.enqueue(request); }
内容的提问来源于stack exchange,提问作者alexowhile

