本文以實際工程為例,快速上手 HarmonyOS 元服務 的文件預覽能力(PreviewKit),並配套一個後端用於提供示例文件。示例工程路徑:
- 客户端(HarmonyOS 端):
client - 後端(Node.js):
server
image-20251112090708795
image-20251112091151694
上圖是將 1個pdf文件和3個圖片一起預覽,那麼就只會現實第1個預覽窗口。
下圖是移除pdf文件,將3個同類型的圖片放在一起預覽
image-20251112091518239
為了方便演示功能,需要先將一些可以預覽的文件下載到元服務的沙箱內,是基於這個原因我們才需要引入後端來模擬這個下載的環境,所以元服務內需要先實現下載文件,存儲到沙箱,然後再使用預覽API filePreview.openPreview預覽沙箱內的文件。
1. 工程結構與目標
client/entry/src/main/ets/pages/Index.ets:演示併發下載 4 個文件(1.pdf、1.png、2.png、3.png)並一次性預覽。server/index.js與server/public/:提供靜態文件下載接口/file/:filename。
目標:
- 點擊“下載”按鈕,併發下載上述 4 個文件到應用沙箱目錄。
- 下載成功後點擊“預覽”,一次性打開最多 4 個文件的預覽窗口。
2. PreviewKit 的核心:filePreview.openPreview
HarmonyOS 提供了預覽能力包 @kit.PreviewKit。在 ETS 代碼中引入:
import { filePreview } from '@kit.PreviewKit';
import { fileUri } from '@kit.CoreFileKit';
核心調用是:
// 先準備多個文件的預覽信息
const prewList: filePreview.PreviewInfo[] = []
for (let i = 0; i < count; i++) {
const item = this.lastDownloadedList[i];
const fileInfo: filePreview.PreviewInfo = {
title: item.name, // 預覽標題
uri: fileUri.getUriFromPath(item.path), // 將沙箱路徑轉成 Uri
mimeType: item.mime || 'application/octet-stream', // MIME 類型
};
prewList.push(fileInfo)
}
// 一次性打開多個預覽窗口
filePreview.openPreview(uiContext, prewList)
.then(() => {
// 打開成功
})
.catch((err: BusinessError) => {
// 打開失敗處理
});
説明:
PreviewInfo至少需要title、uri、mimeType。uri使用fileUri.getUriFromPath(沙箱文件路徑)構造。- 支持一次性傳入一個
PreviewInfo[],實現多文件預覽。
圖片佔位:請補充一次性預覽 4 個文件的窗口布局截圖,標註窗口標題與 MIME 類型展示位置。
3. 併發下載與狀態反饋(客户端)
示例使用 Promise.allSettled 併發下載 4 個後端文件,並按項展示“成功/失敗”狀態:
// 計劃 + 狀態
@Local private plannedFiles: DownloadPlan[] = [];
@Local private itemStatuses: string[] = [];
@Local private isDownloading: boolean = false;
@Local private statusMessage: string = '';
// 初始化計劃(aboutToAppear)
this.plannedFiles = [
new DownloadPlan('1.pdf', `${this.serverBase}/1.pdf`),
new DownloadPlan('1.png', `${this.serverBase}/1.png`),
new DownloadPlan('2.png', `${this.serverBase}/2.png`),
new DownloadPlan('3.png', `${this.serverBase}/3.png`)
];
this.itemStatuses = ['未下載','未下載','未下載','未下載'];
// 點擊“下載”
this.isDownloading = true;
this.statusMessage = '下載中...';
this.itemStatuses = new Array(this.plannedFiles.length).fill('下載中...');
const promises: Promise<DownloadInfo>[] = this.plannedFiles.map(p => this.downloadFile(p.url));
const settled = await Promise.allSettled(promises);
// 彙總結果並一次性觸發 UI 刷新
const successes: DownloadInfo[] = [];
const nextStatuses: string[] = new Array(this.plannedFiles.length).fill('未下載');
for (let i = 0; i < settled.length; i++) {
const name = this.plannedFiles[i].name;
const r = settled[i];
if (r.status === 'fulfilled') {
successes.push(r.value);
nextStatuses[i] = `✓ 下載成功:${name}`;
} else {
nextStatuses[i] = `✗ 下載失敗:${name}(${this.errorToString(r.reason as Object)})`;
}
}
this.itemStatuses = nextStatuses; // 重新賦值以觸發 UI 刷新
this.lastDownloadedList = successes;
this.isDownloading = false;
UI 渲染建議:
- 使用
ForEach(this.plannedFiles, ...)動態渲染狀態行,避免硬編碼索引。 - 將與 UI 綁定的字段用
@Local或@State修飾,並“重新賦值數組”以觸發刷新(不要在原數組上就地修改元素)。
圖片佔位:請補充“下載中→成功/失敗”逐項狀態變化的截圖,便於讀者理解響應式刷新。
4. HTTP 下載的細節與 ArkTS 限制規避
- MIME 與擴展名:示例通過擴展名推斷 MIME,若擴展名缺失則從響應頭的
Content-Type推斷。 - ArkTS 限制:不建議直接
data.header['Content-Type']索引;示例使用序列化 + 正則方式提取避免 ArkTS 索引限制。
// 通過序列化響應頭並用正則提取 Content-Type
private tryGetContentTypeHeader(headerObj: Object | null): string {
if (!headerObj) return '';
try {
const json = JSON.stringify(headerObj);
if (!json) return '';
const match = json.match(/"content-type"\s*:\s*"([^"]+)"/i);
return match && match.length > 1 ? match[1] : '';
} catch (_) {
return '';
}
}
保存文件:
const filePath = `${this.filesDir}/${fileName}`;
if (fileIo.accessSync(filePath)) {
fileIo.unlinkSync(filePath);
}
const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
const bytesWritten = fileIo.writeSync(file.fd, fileBuffer);
fileIo.closeSync(file);
權限:
- 客户端需要在
entry/src/main/module.json5聲明ohos.permission.INTERNET才能進行網絡請求。
5. 後端:簡單的靜態文件下載接口
示例後端路徑:d:\code\atoStudy\server,目錄 public/ 放置 4 個演示文件。
核心路由:GET /file/:filename
後端的簡單目錄結構:
image-20251112092243514
// index.js(簡版示例)
const express = require('express');
const path = require('path');
const app = express();
app.get('/file/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(__dirname, 'public', filename);
res.sendFile(filePath); // 或根據需要設置 Content-Type
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000');
});
客户端請求地址示例:
private serverBase: string = "http://192.168.5.2:3000/file";
// 組合完整 URL 示例:`${this.serverBase}/1.pdf`
注意:請按真實局域網 IP 替換 192.168.5.2,並保證手機/模擬器與後端在同一網絡。
6. 快速運行與驗證
後端:
- 安裝依賴並啓動:
npm install && node index.js - 確認
public/下存在1.pdf、1.png、2.png、3.png
客户端:
- 在
module.json5中確保已聲明ohos.permission.INTERNET - 構建並安裝到設備/模擬器
- 點擊“下載”,觀察逐項狀態變化
- 下載成功後點擊“預覽”,驗證多窗口預覽是否正常
圖片佔位:請補充上述過程的關鍵截圖(如“權限聲明處”、“下載成功狀態”、“多窗口預覽”)。
7. 常見問題與排查
- 權限錯誤(如 code=201 / “Permission denied”):檢查
ohos.permission.INTERNET是否聲明;確認真機/模擬器的網絡可達性。 - 404 或下載失敗:確認後端路由
/file/:filename存在且文件確實在public/目錄內;檢查客户端serverBase地址是否正確。 - MIME 與擴展名錯配:優先使用後端返回的
Content-Type;如果缺失,則按擴展名推斷。 - UI 不刷新:在 ArkUI 中對數組進行“重新賦值”來觸發刷新,避免原地修改元素(例如使用
this.itemStatuses = [...nextStatuses])。
8. 小結
filePreview.openPreview 是 HarmonyOS 文件預覽能力的核心,支持一次性打開多文件預覽。結合簡單的後端靜態文件服務與併發下載、響應式狀態刷新,能夠快速搭建一個“下載即預覽”的演示工程。本文的示例工程完整覆蓋了從後端文件提供、客户端下載與保存、到預覽窗口打開的關鍵路徑,適合作為入門教程與二次擴展的基礎。