【flutter】webview下载文件方法集锦

说明:android的webview是不支持下载的!!!

所以我们需要监听下载接口 然后手动执行下载操作,分为三种类型

  1. 直接打开浏览器下载(最简单),但是一些下载接口需要cookie信息时不能满足

     /**
         * 下载文件选择指定应用下载
         */
        webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
                Uri uri = Uri.parse(url);
                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                activity.startActivity(intent);
            }
        });
  1. 在后台内置下载(此时还是在监听里面执行下载操作,还不算复杂)
//在webview属性设置处添加
        webView.setDownloadListener(new DownloadListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
              if(URLUtil.isNetworkUrl(url)){
                    // 解析 contentDisposition 以获取文件名
                    String fileName = contentDisposition.split("filename=")[1].trim().replace("\"", "");
                    try {
                        fileName = URLDecoder.decode(fileName, "UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    downloadUrl(url, fileName);
                } 
            }

        });
        //下载方法
      private void downloadUrl(String url, final String fileName) {
      //获取cookie
        CookieManager cookieManager = CookieManager.getInstance();
        String cookie = cookieManager.getCookie(url);
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setTitle(fileName); // 使用文件名作为标题
        request.setDescription("Downloading " + fileName + "...");
        request.setVisibleInDownloadsUi(true); // 在下载管理器中显示下载
        if (cookie != null) {
            // 处理获取到的 Cookie
//            Log.d("Cookie", "Cookie for URL " + url + ": " + cookie);
            request.addRequestHeader("Cookie", cookie);
        } else {
            Log.d("Cookie", "No cookie found for URL " + url);
        }
        request.setMimeType("application/octet-stream");
        // 设置下载路径和文件名
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
        // 获取下载管理器并开始下载
        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        downloadManager.enqueue(request);
        final Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, "下载完成,请到通知栏点击安装或打开文件", Toast.LENGTH_SHORT).show();
            }
        });
    }
        
  1. 监听下载已经无法满足下载:如果浏览器是以下载接口请求后生成 blob:https:下载地址,去获取浏览器存储的文件,在监听里便无法直接下载原文件,因为无法获取到响应头信息,因此无法获取文件名来保存文件。当然如果知道下载的文件类型 可以 通过自定义文件名方式来下载blob流文件。

    1. 下载blob流文件:有固定的文件类型 或者通过魔法字符获取文件类型,仍在下载监听内下载
      实现思路:
      (1)判断为blob下载
      webView.setDownloadListener(new DownloadListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
                if (url.startsWith("blob:")) {
                    // 获取文件名
                    String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);

                    // 获取 blob 数据并写入文件 已经在拦截地方下载
                    downloadBlobFile(url, fileName);
                }
            }

        });

(2)定义下载事件,通过调用js请求 blob接口,获取返回数据 回传给android (需要先设置监听才能收到返回数据)

	//下载方法定义
 	@RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void downloadBlobFile(String blobUrl, final String fileName) {
        // 由于 WebView 不能直接处理 blob URL,我们需要使用 JavaScript 接口或其他方法来获取 blob 数据
        // 以下代码是一个示例,展示了如何使用 WebView 获取 blob 数据并写入文件
        webView.evaluateJavascript("console.log('开始请求blob...');" +
                "var xhr = new XMLHttpRequest();" +
                "xhr.open('GET', '"+blobUrl+"', true);" +
                "xhr.responseType = 'blob';" +
                "xhr.onload = function() {" +
                "  if (this.status === 200) {" +
                "    const blob = this.response;" +
                "    var reader = new FileReader();" +
                "    reader.readAsDataURL(blob);" +
                "    reader.onloadend = function() {" +
                "      var base64data = reader.result.split(',')[1];" +
                "      console.log('base64data:'+base64data);" +
                "      Android.androidCallback( base64data);" +
                "    };" +
                "  } else {" +
                "    console.error('Failed to fetch blob data');" +
                "  }" +
                "};" +
                "xhr.onerror = function() {" +
                "  console.error('Error fetching blob data');" +
                "};" +
                "xhr.send();", null);
    }

(3)保存文件

	//在webview设置处设置监听
	webView.addJavascriptInterface(new WebAppInterface(context), "Android");
	
	//定义APP监听接口 获取传回的数据
	public class WebAppInterface {
        Context mContext;

        WebAppInterface(Context c) {
            mContext = c;
        }

        @RequiresApi(api = Build.VERSION_CODES.O)
        @JavascriptInterface
        public void androidCallback(String message) {
            // 在这里处理从 JavaScript 传递过来的数据
            Toast.makeText(mContext, "Received message: " + message, Toast.LENGTH_SHORT).show();
            String base64Data = message;
            //保存成文件
            saveBase64ToFile(base64Data, "downloaded_file");
        }
    }
    
	//保存文件方法定义
	@RequiresApi(api = Build.VERSION_CODES.O)
    private void saveBase64ToFile(String base64Data, String fileName) {
        try {
            byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
            File file = new File(context.getFilesDir(), fileName);
            FileOutputStream outputStream = new FileOutputStream(file);
            outputStream.write(decodedBytes);
            outputStream.close();
            //在通知栏显示下载内容方便用户打开文件
            showDownloadNotification(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

(4)显示下载通知

    //通知方法定义
	  @RequiresApi(api = Build.VERSION_CODES.O)
    private void showDownloadNotification(File file) throws IOException {
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("file_open", "File Open", NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

// 创建一个 Intent 来打开文件
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String mimeType = Files.probeContentType(Paths.get(file.getPath()));
// 创建一个文件提供者 URI
        Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".flutterWebviewPluginProvider", file);
        intent.setDataAndType(fileUri, mimeType); // 设置文件的 MIME 类型
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予临时权限

// 创建 PendingIntent
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        long[] pattern = {0, 100, 200, 300};
        Notification notification = new NotificationCompat.Builder(context, "file_open")
                .setSmallIcon(R.drawable.ic_download)
                .setContentTitle(file.getName())
                .setContentText("下载完成,点击打开文件")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setContentIntent(pendingIntent) // 设置点击通知后执行的操作
                .setAutoCancel(true) // 点击后自动取消通知
                .build();

        notificationManager.notify(1, notification);
    }
	

2、 监听所有接口请求,过滤出下载接口,自定义下载
(1)设置setWebViewClientwebView.setWebViewClient(webViewClient);
(2)定义webViewClient
(3)过滤出下载接口后,先发起请求获取文件名信息,再进行下载操作

  	 webViewClient = new BrowserClient() {
 			/*监听所有接口请求,过滤所有下载请求*/
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                String url = request.getUrl().toString();
                if (isDownloadUrl(url)) {
                 	Map<String, String> requestHeaders= request.getRequestHeaders();
                    String acceptHeaderValue = requestHeaders.get("Accept");
                    if(acceptHeaderValue != null && acceptHeaderValue.contains("image/")){//忽略页面上img地址为下载接口
                        return super.shouldInterceptRequest(view, request); // 拦截请求
                    }
                    // 处理下载请求
                    startDownloadWithDisposition(url);
                    return null; // 拦截请求
                }
                return super.shouldInterceptRequest(view, request);
            }
             private boolean isDownloadUrl(String url) {
                // 检查 URL 是否是下载 URL,例如检查文件扩展名或 MIME 类型
                return url.contains("downloadFile"); // 示例:检查接口地址
            }
            /*先请求获取响应头获取文件名后再进行下载操作*/
            private void startDownloadWithDisposition(String urlString) {
                try {
                    URL url = new URL(urlString);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET"); // 只请求头部信息 HEAD请求的响应头数据不全 所以用GET

                    // 获取 Content-Disposition 头
                    String contentDisposition = connection.getHeaderField("Content-Disposition");
                    // 解析文件名
                    String fileName = parseFileNameFromContentDisposition(contentDisposition);
                    fileName = URLDecoder.decode(fileName, "UTF-8");
                    // 断开连接
                    connection.disconnect();
                    downloadUrl(urlString,fileName);


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
              /*获取文件名*/
            private String parseFileNameFromContentDisposition(String contentDisposition) {
                if (contentDisposition == null) {
                    return null;
                }
                String[] parts = contentDisposition.split(";");
                for (String part : parts) {
                    if (part.trim().startsWith("filename=")) {
                        return part.substring("filename=".length()).trim().replace("\"", "");
                    }
                }
                return null;
            }
	};
注意:获取到的文件名需转码为utf8
fileName = URLDecoder.decode(fileName, "UTF-8");

补充:
(1)okhttp引入报红时,需要在你的webview插件导入 okhttp包

//webview_plugin\android\build.gradle中
	dependencies {
    	implementation 'com.squareup.okhttp3:okhttp:3.11.0'
    }

(2)消息通知引入 自定义图标.setSmallIcon(R.drawable.ic_download)
在webview插件的资源部中引入webview_plugin\android\src\main\res\
在这里插入图片描述

(3)通过请求头accept 来判断是否img等资源文件下载还是接口请求 资源文件下载要忽略掉不进行下载操作

 				Map<String, String> requestHeaders= request.getRequestHeaders();
                    String acceptHeaderValue = requestHeaders.get("Accept");
                    if(acceptHeaderValue != null && acceptHeaderValue.contains("image/")){//忽略页面上img地址为下载接口
                        return super.shouldInterceptRequest(view, request); // 拦截请求
                    }

通常情况下:

  1. 加载API接口:API接口一般返回JSON、XML或其他结构化数据格式,所以Accept头部可能会包含类似application/json、application/xml等值。
  2. 加载图片:对于图片资源,Accept头部则会指向图像相关的MIME类型,如image/jpeg、image/png、image/gif等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936514.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一行一行出字的视频怎么做?简单的操作方法

在视频制作中&#xff0c;逐行出现的字幕效果不仅能够增强视觉冲击力&#xff0c;还能让观众更加专注于内容&#xff0c;特别适合用于教育视频、书单推荐、诗歌朗诵等多种场景。下面&#xff0c;我们将详细介绍如何影忆&#xff0c;来制作这种逐行出字的视频效果。 1.字幕逐行…

专题三:简单多状态 dp 问题

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是记忆化搜索&#xff0c;并且掌握记忆化搜索算法。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早…

海外组网之优解:外贸 SD-WAN 跨境组网专线赋能企业全球互联

在全球化商业进程中&#xff0c;众多企业积极拓展海外业务&#xff0c;设立国内外分支&#xff0c;然而分支组网难题也随之而来。外贸 SD-WAN 跨境专线成为解决此类问题的优选方案&#xff0c;为企业提供高效稳定的网络连接。 一、SD-WAN 跨境组网专线优势 &#xff08;一&…

【机器人】轨迹规划 之 spline 规划

在轨迹规划中&#xff0c;使用 spline &#xff08;通常是指通过样条曲线进行轨迹规划&#xff09;可以实现平滑、连续的路径。以下是使用样条&#xff08;如B样条、三次样条插值&#xff09;的具体方法和步骤&#xff0c;结合一个简单的例子说明&#xff1a; 示例场景&#xf…

Python生成对抗神经网络GAN预测股票及LSTMs、ARIMA对比分析ETF金融时间序列可视化

全文链接&#xff1a;https://tecdat.cn/?p38528 本文聚焦于利用生成对抗网络&#xff08;GANs&#xff09;进行金融时间序列的概率预测。介绍了一种新颖的基于经济学驱动的生成器损失函数&#xff0c;使 GANs 更适用于分类任务并置于监督学习环境中&#xff0c;能给出价格回…

【渗透测试】信息收集二

其他信息收集 在渗透测试中&#xff0c;历史漏洞信息收集是一项重要的工作&#xff0c;以下是相关介绍&#xff1a; 历史漏洞信息收集的重要性 提高效率&#xff1a;通过收集目标系统或应用程序的历史漏洞信息&#xff0c;可以快速定位可能存在的安全问题&#xff0c;避免重复…

【泛微系统】流程发起次数报表

流程发起次数报表 应用场景: 查询所有发起过业务流程的员工的信息,可作为绩效考核、系统使用情况等依据; 如何使用该SQL生成系统在线报表,实时查询最新的发起数据? 1、数据库创建视图,并定义一个视图名称如;view_test1 2、系统后台建模引擎-表单-右键创建一个虚拟表单…

k8s中用filebeat文件如何收集不同service的日志

以下是一个详细的从在 Kubernetes 集群中部署 Filebeat&#xff0c;到实现按web-oper、web-api微服务分离日志并存储到不同索引的完整方案&#xff1a; 理解需求&#xff1a;按服务分离日志索引 在 Kubernetes 集群中&#xff0c;有web-oper和web-api两种微服务&#xff0c;希…

【LeetCode】2406、将区间分为最少组数

【LeetCode】2406、将区间分为最少组数 文章目录 一、数据结构-堆、贪心1.1 数据结构-堆、贪心1.2 多语言解法 二、扫描线2.1 扫描线 一、数据结构-堆、贪心 1.1 数据结构-堆、贪心 题目已知一些区间, 需要尽量合并, 使 组 最少. 可以用图解画一下 因为尽量合并, 为了紧凑, …

【现代服务端架构】传统服务器 对比 Serverless

在现代开发中&#xff0c;选择合适的架构是至关重要的。两种非常常见的架构模式分别是 传统服务器架构 和 Serverless。它们各有优缺点&#xff0c;适合不同的应用场景。今天&#xff0c;我就带大家一起对比这两种架构&#xff0c;看看它们的差异&#xff0c;并且帮助你选择最适…

数据结构——栈的模拟实现

大家好&#xff0c;今天我要介绍一下数据结构中的一个经典结构——栈。 一&#xff1a;栈的介绍 与顺序表和单链表不同的是&#xff1a; 顺序表和单链表都可以在头部和尾部插入和删除数据&#xff0c;但是栈的结构就锁死了&#xff08;栈的底部是堵死的&#xff09;栈只能从…

Harmony编译与打包

1、hvigor编译脚本文件 hvigorconfig.ts:位于项目根目录,默认不存在(可以自行创建),执行的时机较早,可用于在hvigor生命周期刚开始时操作某些数据。hvigorfile.ts:项目根目录和每个模块目录下都有,在此文件中可以注册插件、任务以及生命周期hook等操作(类似Android的b…

健康的玉米叶病害数据集,玉米识别数据集,对原始图片进行yolov,coco,voc格式标注

健康的玉米叶病害数据集 对原始图片进行yolov&#xff0c;coco&#xff0c;voc格式标注&#xff0c;可识别玉米叶子是否健康 数据集分割 训练组100&#xff05; 442图片 有效集&#xff05; 0图片 测试集&#xff05; 0图片 预处理 自动定向&#xff1a;…

MobileLLM开发安卓AI的体验

MobileLLM是一个在安卓端跑的大语言模型&#xff0c;关键它还有调动api的能力 https://github.com/facebookresearch/MobileLLM 项目地址是这个。 看了下&#xff0c;似乎还是中国人团队 article{liu2024mobilellm, title{MobileLLM: Optimizing Sub-billion Parameter Langua…

IIS部署程序https是访问出现403或ERR_HTTP2_PROTOCOL_ERROR

一、说明 在windows server 2016中的IIS程序池里部署一套系统&#xff0c;通过https访问站点&#xff0c;同时考虑到安全问题以及防攻击等行为&#xff0c;就用上了WAF云盾功能&#xff0c;能有效的抵挡部分攻击&#xff0c;加强网站的安全性和健壮性。 应用系统一直能够正常…

2024小迪安全信息收集第三课

目录 一、Web应用-架构分析-WAF&蜜罐识别 二、Web应用-架构分析-框架组件指纹识别 #Web架构 开源CMS 前端技术 开发语言 框架组件 Web服务器 应用服务器 数据库类型 操作系统信息 应用服务信息 CDN信息 WAF信息 蜜罐信息 其他组件信息 #指纹识别 #WAF识别…

计算机网络技术基础:3.计算机网络的拓扑结构

网络拓扑结构是指用传输媒体互连各种设备的物理布局&#xff0c;即用什么方式把网络中的计算机等设备连接起来。将工作站、服务站等网络设备抽象为点&#xff0c;称为“节点”&#xff1b;将通信线路抽象为线&#xff0c;称为“链路”。由节点和链路构成的抽象结构就是网络拓扑…

【数据结构】基数排序的原理及实现

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在准备26考研 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章…

opencv-python的简单练习

一、编程题 读取一张彩色图像并将其转换为灰度图。 import cv2# 读取彩色图像 image_path path_to_your_image.jpg # 替换为你的图像路径 color_image cv2.imread(image_path)# 检查图像是否成功加载 if color_image is None:print("图像加载失败&#xff0c;请检查图…

Python鼠标轨迹算法(游戏防检测)

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…