You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 13:35:21