近期有朋友咨询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, 即可测试效果:
