Android WebView中JS生成文件下载崩溃的解决方法
Android WebView下载JS生成文件的解决方案
崩溃原因
你当前使用的DownloadListener仅能处理真实网络URL或本地文件路径,而JavaScript生成的Blob URL是临时内存地址,DownloadManager无法解析这类URL,直接导致应用崩溃。正确的处理方式是通过JS与Java交互接口,让JS直接将文件内容传递到Java层,由Java负责写入文件。
具体实现步骤
1. 创建JS交互接口类
在MainActivity中添加内部类作为JS调用Java的桥梁,注意方法必须添加@JavascriptInterface注解:
public class FileDownloadInterface { @JavascriptInterface public void downloadFile(String fileContent, String fileName) { // 在子线程执行文件写入,避免阻塞UI线程引发ANR new Thread(() -> { try { // Android 10+推荐使用应用私有目录,无需动态存储权限 File downloadDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (!downloadDir.exists()) { downloadDir.mkdirs(); } File file = new File(downloadDir, fileName); FileWriter writer = new FileWriter(file); writer.write(fileContent); writer.flush(); writer.close(); // 回到UI线程提示下载结果 runOnUiThread(() -> Toast.makeText(MainActivity.this, "文件已保存至:" + file.getAbsolutePath(), Toast.LENGTH_LONG).show()); } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存失败:" + e.getMessage(), Toast.LENGTH_SHORT).show()); } }).start(); } }
2. 给WebView注册JS接口
在MainActivity的onCreate方法中,开启JavaScript后添加接口注册:
// 原有代码:开启JavaScript支持 mywebsettings.setJavaScriptEnabled(true); // 新增:注册JS交互接口,"AndroidDownloader"是JS中调用的对象名 web.addJavascriptInterface(new FileDownloadInterface(), "AndroidDownloader");
3. 修改HTML中的下载逻辑
移除原有的Blob和a标签下载逻辑,直接调用Java接口传递文件内容:
<html> <div id="disp"></div> <script> function objectsToCSV(arr) { const array = [Object.keys(arr[0])].concat(arr) return array.map(row => { return Object.values(row).map(value => { return typeof value === 'string' ? JSON.stringify(value) : value }).toString() }).join('\n') } var items = [ { "name": "Item 1", "color": "Green", "size": "X-Large" }, { "name": "Item 2", "color": "Green", "size": "X-Large" }, { "name": "Item 3", "color": "Green", "size": "X-Large" }]; var csv = objectsToCSV(items); document.getElementById("disp").innerHTML = csv; // 修改下载函数:直接调用Android端接口 function downloadCSV(csv) { window.AndroidDownloader.downloadFile(csv, 'my_data.csv'); } const downloadButton = document.createElement('button'); downloadButton.textContent = 'Download CSV'; downloadButton.addEventListener('click', () => { downloadCSV(csv); }); document.body.appendChild(downloadButton); </script> </html>
4. 清理无用代码
移除原有的DownloadListener代码,同时如果目标SDK为Android 10+,可以删除动态申请WRITE_EXTERNAL_STORAGE权限的代码(应用私有目录无需此权限)。
完整修改后的MainActivity代码
package com.fun.fun; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Environment; import android.view.View; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class MainActivity extends AppCompatActivity { private WebView web; String webUrl = "file:///android_asset/indexi.html"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); web = findViewById(R.id.myweb); web.loadUrl(webUrl); WebSettings mywebsettings = web.getSettings(); mywebsettings.setJavaScriptEnabled(true); // 注册JS交互接口 web.addJavascriptInterface(new FileDownloadInterface(), "AndroidDownloader"); web.setWebViewClient(new WebViewClient()); // WebView性能优化代码保留 web.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH); web.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); web.getSettings().setAppCacheEnabled(true); web.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); mywebsettings.setDomStorageEnabled(true); mywebsettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); mywebsettings.setUseWideViewPort(true); mywebsettings.setSavePassword(true); mywebsettings.setSaveFormData(true); mywebsettings.setEnableSmoothTransition(true); } // JS交互接口类 public class FileDownloadInterface { @JavascriptInterface public void downloadFile(String fileContent, String fileName) { new Thread(() -> { try { File downloadDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (!downloadDir.exists()) { downloadDir.mkdirs(); } File file = new File(downloadDir, fileName); FileWriter writer = new FileWriter(file); writer.write(fileContent); writer.flush(); writer.close(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "文件已保存:" + file.getAbsolutePath(), Toast.LENGTH_LONG).show()); } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存失败:" + e.getMessage(), Toast.LENGTH_SHORT).show()); } }).start(); } } @Override public void onBackPressed() { if(web.canGoBack()){ web.goBack(); } else { super.onBackPressed(); } } }
内容的提问来源于stack exchange,提问作者Abednego Nasila
相关产品推荐
相关产品推荐

