HTML打包APK(安卓APP)中下载功能常见问题和详细介绍

近期有朋友咨询HTML一键打包APK工具中的文件下载功能, 他在开发一个离线版的工具APP, 发现工具处理完成后, 无法正常下载处理完成的结果. 这个问题很有代表性, 我们这里可以详细讨论分析下.

如果您不了解HTML一键打包APK工具, 可以查看我们之前的文章:

HTML一键打包APK工具(安卓应用APP)_一键打包apk工具安卓版-CSDN博客

HTML一键打包APK工具2026最新版本(解压密码1234)资源-CSDN下载

定位问题

首先找到HTML里面对应的下载代码如下:

javascript 复制代码
// 这段代码在浏览器中正常,但在HTML打包APK生成的APP中失效
dom.downloadBtn.onclick = () => {
    const a = document.createElement('a');
    a.href = URL.createObjectURL(finalPdfBlob);
    a.download = `文件_${new Date().getTime()}.pdf`;
    a.click();
};

上述代码在普通PC浏览器里面时可以正常工作的, 但是在打包后的APP里面, 点击后无法触发下载动作, 主要原因是Blob 数据存储在 JavaScript 引擎的内存中, 而APP下载用到的DownloadManager 运行在 Java 层, Blob URL 只是 JavaScript 内存地址,Java 层无法访问, 因此无法正常触发下载动作

问题修复

修复这个问题也比较简单, 只要改写网页里面的代码即可:

javascript 复制代码
// 原始代码(无法正常下载)
dom.downloadBtn.onclick = () => {
    const a = document.createElement('a');
    a.href = URL.createObjectURL(finalPdfBlob);  // Blob URL
    a.download = `文件_${Date.now()}.pdf`;
    a.click();
};

// 修复后代码(兼容HTML一键打包工具生成的APP)
dom.downloadBtn.onclick = () => {
    const reader = new FileReader();
    reader.onload = () => {
        const a = document.createElement('a');
        // 转换为 data:application/pdf;base64,<base64数据>
        a.href = reader.result;  // Data URL
        a.download = `文件_${Date.now()}.pdf`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    };
    // 将 Blob 读取为 Base64 编码的 Data URL
    reader.readAsDataURL(finalPdfBlob);
};

这边还准备了一份HTML下载文件的示例代码, 可以用于学习和测试HTML打包APK生成的APP的下载功能:

javascript 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>下载功能测试</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            padding: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 10px;
            font-size: 24px;
        }
        .subtitle {
            text-align: center;
            color: #666;
            margin-bottom: 30px;
            font-size: 14px;
        }
        .test-section {
            margin-bottom: 30px;
            padding: 20px;
            background: #f8f9fa;
            border-radius: 10px;
            border: 2px solid #e9ecef;
        }
        .test-section h2 {
            font-size: 18px;
            color: #495057;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
        }
        .test-section h2::before {
            content: "📦";
            margin-right: 8px;
            font-size: 20px;
        }
        .test-section p {
            color: #6c757d;
            font-size: 13px;
            margin-bottom: 15px;
            line-height: 1.5;
        }
        .btn-primary:active {
            transform: scale(0.98);
            opacity: 0.9;
        }
        .btn-success {
            background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
            color: white;
        }
        .btn-success:active {
            transform: scale(0.98);
            opacity: 0.9;
        }
        .btn-info {
            background: linear-gradient(135deg, #2193b0 0%, #6dd5ed 100%);
            color: white;
        }
        .btn-info:active {
            transform: scale(0.98);
            opacity: 0.9;
        }
        .btn-warning {
            background: linear-gradient(135deg, #f46b45 0%, #eea849 100%);
            color: white;
        }
        .btn-warning:active {
            transform: scale(0.98);
            opacity: 0.9;
        }
        .status {
            margin-top: 10px;
            padding: 8px 12px;
            border-radius: 5px;
            font-size: 13px;
            display: none;
        }
        .status.success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .status.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .footer {
            text-align: center;
            margin-top: 20px;
            padding-top: 20px;
            border-top: 2px solid #e9ecef;
            color: #6c757d;
            font-size: 12px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>下载功能测试</h1>
        <p class="subtitle">测试各种下载场景</p>

        <!-- 测试1: Blob下载(小文本) -->
        <div class="test-section">
            <h2>Blob下载 - 小文本</h2>
            <p>创建一个包含文本内容的Blob对象,转为Data URL下载</p>
            <button class="btn btn-primary" onclick="downloadTextBlob()">下载文本文件 (test.txt)</button>
            <div id="status1" class="status"></div>
        </div>

        <!-- 测试2: Blob下载(JSON) -->
        <div class="test-section">
            <h2>Blob下载 - JSON数据</h2>
            <p>生成JSON格式数据并下载,测试中文文件名</p>
            <button class="btn btn-success" onclick="downloadJsonBlob()">下载JSON文件 (测试数据.json)</button>
            <div id="status2" class="status"></div>
        </div>

        <!-- 测试3: Blob下载(图片) -->
        <div class="test-section">
            <h2>Blob下载 - Base64图片</h2>
            <p>从Canvas生成PNG图片,测试二进制文件下载</p>
            <button class="btn btn-info" onclick="downloadImageBlob()">下载图片 (test-image.png)</button>
            <div id="status3" class="status"></div>
        </div>

        <!-- 测试4: HTTP直链下载 -->
        <div class="test-section">
            <h2>HTTP直链下载</h2>
            <p>直接下载网络文件(需要网络连接)</p>
            <button class="btn btn-warning" onclick="downloadHttpFile()">下载网络图片 (via HTTP)</button>
            <div id="status4" class="status"></div>
        </div>

        <!-- 测试5: 大文件Blob下载 -->
        <div class="test-section">
            <h2>Blob下载 - 大文本文件</h2>
            <p>生成1MB文本数据,测试大文件处理能力</p>
            <button class="btn btn-primary" onclick="downloadLargeBlob()">下载大文件 (1MB.txt)</button>
            <div id="status5" class="status"></div>
        </div>

        <div class="footer">
            <p>✅ 成功案例会显示绿色提示</p>
            <p>❌ 失败案例会显示红色错误</p>
            <p>📁 文件保存在:<strong>手机下载目录</strong></p>
        </div>
    </div>

    <script>
        // 显示状态消息
        function showStatus(id, message, isSuccess) {
            const statusEl = document.getElementById('status' + id);
            statusEl.textContent = message;
            statusEl.className = 'status ' + (isSuccess ? 'success' : 'error');
            statusEl.style.display = 'block';
            setTimeout(() => {
                statusEl.style.display = 'none';
            }, 3000);
        }

        // 通用下载函数(将Blob转为Data URL)
        function downloadBlob(blob, fileName, statusId) {
            try {
                const reader = new FileReader();
                reader.onload = () => {
                    // 在data URL头部附加文件名,便于安卓端解析
                    const raw = reader.result;
                    const insertPos = raw.indexOf(';base64');
                    const withName = insertPos > 0
                        ? raw.slice(0, insertPos) + `;name=${encodeURIComponent(fileName)}` + raw.slice(insertPos)
                        : raw;

                    const a = document.createElement('a');
                    a.href = withName; // data:...;name=xxx;base64,...
                    a.download = fileName;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    showStatus(statusId, '✅ 下载已开始:' + fileName, true);
                };
                reader.onerror = () => {
                    showStatus(statusId, '❌ 转换失败', false);
                };
                reader.readAsDataURL(blob);
            } catch (err) {
                showStatus(statusId, '❌ 下载失败:' + err.message, false);
            }
        }

        // 测试1: 下载文本Blob
        function downloadTextBlob() {
            const content = `这是一个测试文本文件
创建时间: ${new Date().toLocaleString('zh-CN')}
内容: Hello World from App!
测试项目: Blob下载功能

功能说明:
- 文本编码: UTF-8
- 文件格式: .txt
- 创建方式: JavaScript Blob API
`;
            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            downloadBlob(blob, 'test.txt', 1);
        }

        // 测试2: 下载JSON Blob
        function downloadJsonBlob() {
            const data = {
                title: "测试数据",
                timestamp: new Date().toISOString(),
                items: [
                    { id: 1, name: "项目A", value: 100 },
                    { id: 2, name: "项目B", value: 200 },
                    { id: 3, name: "项目C", value: 300 }
                ],
                metadata: {
                    version: "1.0",
                    author: "测试",
                    description: "这是一个JSON格式的测试文件"
                }
            };
            const content = JSON.stringify(data, null, 2);
            const blob = new Blob([content], { type: 'application/json;charset=utf-8' });
            downloadBlob(blob, '测试数据.json', 2);
        }

        // 测试3: 下载图片Blob
        function downloadImageBlob() {
            // 创建Canvas并绘制图案
            const canvas = document.createElement('canvas');
            canvas.width = 400;
            canvas.height = 300;
            const ctx = canvas.getContext('2d');
            
            // 绘制渐变背景
            const gradient = ctx.createLinearGradient(0, 0, 400, 300);
            gradient.addColorStop(0, '#667eea');
            gradient.addColorStop(1, '#764ba2');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 400, 300);
            
            // 绘制文字
            ctx.fillStyle = 'white';
            ctx.font = 'bold 24px Arial';
            ctx.textAlign = 'center';
            ctx.fillText('下载测试', 200, 150);
            ctx.font = '16px Arial';
            ctx.fillText(new Date().toLocaleString('zh-CN'), 200, 180);
            
            // 转换为Blob
            canvas.toBlob((blob) => {
                downloadBlob(blob, 'test-image.png', 3);
            }, 'image/png');
        }

        // 测试4: HTTP直链下载
        function downloadHttpFile() {
            try {
                const a = document.createElement('a');
                // 使用一个公开的测试图片URL
                a.href = 'https://www.h5pack.com/zb_users/upload/2023/06/20230606140955168603179558373.png';
                a.download = 'network-image.png';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                showStatus(4, '✅ 下载已开始(HTTP)', true);
            } catch (err) {
                showStatus(4, '❌ 下载失败:' + err.message, false);
            }
        }

        // 测试5: 下载大文件Blob
        function downloadLargeBlob() {
            try {
                showStatus(5, '⏳ 正在生成1MB数据...', true);
                
                // 生成1MB的文本数据
                const lineCount = 20000;
                let content = '=== 大文件下载测试 ===\n';
                content += '生成时间: ' + new Date().toLocaleString('zh-CN') + '\n';
                content += '总行数: ' + lineCount + '\n';
                content += '======================\n\n';
                
                for (let i = 1; i <= lineCount; i++) {
                    content += `Line ${i}: This is a test line with some random data - ${Math.random().toString(36).substring(2)}\n`;
                }
                
                const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
                const sizeMB = (blob.size / 1024 / 1024).toFixed(2);
                
                setTimeout(() => {
                    downloadBlob(blob, '1MB.txt', 5);
                    showStatus(5, `✅ 下载已开始:1MB.txt (${sizeMB}MB)`, true);
                }, 500);
            } catch (err) {
                showStatus(5, '❌ 生成失败:' + err.message, false);
            }
        }
    </script>
</body>
</html>

把上面代码保存成test-download.html, 然后使用HTML一键打包APK工具打包生成APP, 即可测试效果:

相关推荐
颜酱2 小时前
前端算法必备:双指针从入门到很熟练(快慢指针+相向指针+滑动窗口)
前端·后端·算法
lichenyang4532 小时前
从零开始:使用 Docker 部署 React 前端项目完整实战
前端
明月_清风2 小时前
【开源项目推荐】Biome:让前端代码质量工具链快到飞起来
前端
愈努力俞幸运2 小时前
vue3 demo教程(Vue Devtools)
前端·javascript·vue.js
持续前行2 小时前
在 Vue3 中使用 LogicFlow 更新节点名称
前端·javascript·vue.js
Anita_Sun2 小时前
Underscore.js 整体设计思路与架构分析
前端·javascript
程序员Agions2 小时前
AI 写的代码有 48% 在"胡说八道":那些你 npm install 的包,可能根本不存在
前端·ai编程
ycgg2 小时前
深入理解CSS transform矩阵:从底层原理到实战应用
前端
Java陈序员2 小时前
告别手写礼簿!一款开源免费的电子红白喜事礼簿系统!
javascript·css·html