事情是这样的
前两天在家撸代码,突然想给我的小破app加个分享功能。本来想偷个懒,直接用现成的插件算了。结果一看,好家伙,都要收费。我寻思着这玩意儿能有多难?不就是调个系统API嘛,自己搞一个呗。
没想到这一入坑,还真挺有意思。鸟蒙的API设计还挺人性化的,虽然踩了不少坑,但学到的东西也不少。
文件咋放的
也没搞得很复杂,就几个文件:
cool-share/
├── utssdk/
├── interface.uts # 给别人用的接口
└── app-harmony/ # 鸿蒙专用代码
├── index.uts # 入口文件
└── share.ets # 干活的代码
先定个规矩
接口嘛,就是告诉别人怎么调用我这个东西:
export type ShareWithSystemOptions = {
type: string; // 分享啥类型的玩意儿
title?: string; // 起个标题
summary?: string; // 写点描述
href?: string; // 链接或者文件在哪儿
imageUrl?: string; // 图片视频的地址
success?: () => void; // 成功了干啥
fail?: (error: string) => void; // 失败了咋办
};
开始干活了
这块儿是重点,也是我掉坑最多的地方。
先把家伙事儿准备好
import { systemShare } from "@kit.ShareKit";
import { uniformTypeDescriptor as utd } from "@kit.ArkData";
import { common } from "@kit.AbilityKit";
import { fileUri } from "@kit.CoreFileKit";
import { UTSHarmony } from "@dcloudio/uni-app-x-runtime";
这些都是鸿蒙给咱准备的工具,分别管分享、识别文件类型、获取应用信息、处理文件路径这些活儿。
分享类型定义
enum ShareType {
TEXT = "text", // 纯文本
IMAGE = "image", // 图片
VIDEO = "video", // 视频
AUDIO = "audio", // 音频
FILE = "file", // 文件
LINK = "link" // 链接
}
分享图片咋整
图片分享最常用,咱先搞这个:
function createImageShareData(
imageUrl: string,
title: string,
summary: string
): systemShare.SharedData | null {
if (imageUrl === "") {
return null;
}
// 这里要注意,需要先获取正确的文件路径
const filePath = UTSHarmony.getResourcePath(imageUrl);
// 然后获取文件的数据类型标识符
const utdTypeId = getUtdTypeByPath(filePath, utd.UniformDataType.IMAGE);
// 最后创建分享数据对象
return new systemShare.SharedData({
utd: utdTypeId, // 数据类型
uri: fileUri.getUriFromPath(filePath), // 文件URI
title: title, // 标题
description: summary // 描述
});
}
怎么用这玩意儿
在你的页面里这么写就行:
import { shareWithSystem } from "@/uni_modules/cool-share";
// 分享个图片
shareWithSystem({
type: "image",
title: "我拍的照片",
summary: "今天拍的,还不错吧",
imageUrl: "https://cool-js.com/logo.png",
success: () => {
console.log("success");
},
fail: (error) => {
console.log(error);
}
});
// 分享点文字
shareWithSystem({
type: "text",
title: "今日心情",
summary: "今天天气不错,心情美美哒~",
success: () => {
console.log("success");
}
});
// 分享个链接
shareWithSystem({
type: "link",
title: "发现个好网站",
summary: "这网站挺有意思的,你们看看",
href: "https://cool-js.com/",
success: () => {
console.log("success");
}
});
UTD是个啥东西
鸿蒙用UTD(统一数据类型标识符)来识别文件类型。简单说就是告诉系统:"嘿,这是个图片!"或者"这是个视频!"
function getUtdTypeByPath(filePath: string, defaultType: string): string {
const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";
if (ext === "") {
return defaultType;
}
return utd.getUniformDataTypeByFilenameExtension("." + ext, defaultType);
}
这个函数就是看文件后缀名,.jpg就知道是图片,.mp4就知道是视频,就这么简单。
文件分享稍微麻烦点
文件分享比较复杂,得看是啥类型的文件:
function createFileShareData(
filePath: string,
title: string,
summary: string
): systemShare.SharedData | null {
if (filePath === "") {
return null;
}
const resourcePath = UTSHarmony.getResourcePath(filePath);
const ext = resourcePath?.split(".")?.pop()?.toLowerCase() ?? "";
// 根据文件扩展名确定数据类型
let utdType = utd.UniformDataType.FILE;
switch (ext) {
case "zip":
case "rar":
case "7z":
utdType = utd.UniformDataType.ARCHIVE; // 压缩包
break;
case "pdf":
utdType = utd.UniformDataType.PDF; // PDF文档
break;
case "doc":
case "docx":
utdType = utd.UniformDataType.WORD_DOC; // Word文档
break;
case "xls":
case "xlsx":
utdType = utd.UniformDataType.EXCEL; // Excel表格
break;
default:
utdType = utd.UniformDataType.FILE; // 普通文件
break;
}
const utdTypeId = utd.getUniformDataTypeByFilenameExtension("." + ext, utdType);
return new systemShare.SharedData({
utd: utdTypeId,
uri: fileUri.getUriFromPath(resourcePath),
title: title,
description: summary
});
}
最后一步,弹出分享框
数据都准备好了,现在可以叫出系统的分享面板了:
export function share(
type: string,
title: string,
summary: string,
href: string,
imageUrl: string,
success: () => void,
fail: (error: string) => void
): void {
// 先获取当前应用的上下文,这个是必须的
const uiContext: UIContext = UTSHarmony.getCurrentWindow()?.getUIContext();
const context: common.UIAbilityContext = uiContext.getHostContext() as common.UIAbilityContext;
// 根据不同类型创建对应的分享数据
let shareData: systemShare.SharedData | null = null;
let errorMsg = "";
switch (type) {
case ShareType.IMAGE:
shareData = createImageShareData(imageUrl, title, summary);
errorMsg = "图片路径不能为空";
break;
case ShareType.VIDEO:
shareData = createVideoShareData(imageUrl, title, summary);
errorMsg = "视频路径不能为空";
break;
case ShareType.LINK:
shareData = createLinkShareData(href, title, summary);
break;
default:
// 默认当文本处理
shareData = createTextShareData(title, summary);
break;
}
// 检查数据是否有效
if (shareData === null) {
fail(errorMsg);
return;
}
// 创建分享控制器
const controller: systemShare.ShareController = new systemShare.ShareController(shareData);
// 显示分享面板
controller
.show(context, {
selectionMode: systemShare.SelectionMode.SINGLE, // 单选模式
previewMode: systemShare.SharePreviewMode.DEFAULT // 默认预览
})
.then(() => {
success(); // 分享成功
})
.catch((error: BusinessError) => {
fail(error?.message ?? "分享失败"); // 分享失败
});
}
ETS开发小技巧
在写这个插件的过程中,我总结了一些ETS开发的小技巧:
1. 空值安全处理
ETS对空值检查很严格,要养成使用??操作符的习惯:
// 好习惯:使用空值合并操作符
const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";
// 而不是这样(可能报错)
const ext = filePath.split(".").pop().toLowerCase();
2. 类型断言要谨慎
尽量避免使用as进行强制类型转换,多用类型检查:
// 推荐的写法
if (context instanceof common.UIAbilityContext) {
// 安全地使用context
}
// 而不是直接断言
const context = uiContext.getHostContext() as common.UIAbilityContext;
3. 错误处理要完整
鸿蒙的异步操作都是Promise,记得处理catch:
controller
.show(context, options)
.then(() => {
success();
})
.catch((error: BusinessError) => {
// 一定要处理错误情况
const errorMessage = error?.message ?? "未知错误";
fail(errorMessage);
});
4. 文件路径处理
鸿蒙对文件路径很敏感,一定要用正确的API获取路径:
// 正确的做法
const filePath = UTSHarmony.getResourcePath(imageUrl);
const uri = fileUri.getUriFromPath(filePath);
// 而不是直接拼接路径
我踩过的那些坑
1. 分享框死活出不来
刚开始的时候,代码写好了,点击分享按钮啥反应都没有。搞了半天才发现是context获取有问题:
// 错误的做法 - 可能获取不到context
const context = UTSHarmony.getUniActivity();
// 正确的做法
const uiContext = UTSHarmony.getCurrentWindow()?.getUIContext();
const context = uiContext.getHostContext() as common.UIAbilityContext;
2. 文件跟我玩捉迷藏
本地文件分享老是失败,我还以为是代码逻辑有问题。结果折腾了一晚上才发现,是文件路径搞错了:
// 错误:直接使用相对路径
imageUrl: "./static/image.jpg";
// 正确:使用绝对路径或者让UTSHarmony处理
imageUrl: "/static/image.jpg";
const realPath = UTSHarmony.getResourcePath(imageUrl);
3. 文件类型识别翻车了
有时候文件类型识别不对,分享到微信QQ就会出现奇怪的问题:
// 保险的做法:先判断扩展名,再设置UTD类型
let utdType = utd.UniformDataType.FILE; // 默认类型
switch (ext) {
case "jpg":
case "jpeg":
case "png":
utdType = utd.UniformDataType.IMAGE;
break;
// 其他类型...
}
4. 异步操作坑死人
Promise的错误处理千万别偷懒,不然出了问题你都不知道哪里错了,我就吃过这个亏:
controller
.show(context, options)
.then(() => {
console.log("分享成功"); // 调试信息很重要
success();
})
.catch((error: BusinessError) => {
console.error("分享失败:", error); // 打印错误信息
fail(error?.message ?? "分享失败");
});
5. 权限这茬儿
有时候会遇到权限不够的情况,特别是读取文件的时候。记得检查app的权限配置,不然用户点了分享啥反应都没有。
一些常见问题
Q: 分享面板怎么是空白的?
A: 检查这几个地方:
- context获取对了没
- ShareData有没有创建成功(别返回null)
- 文件路径对不对
- UTD类型匹配不
Q: 为啥有些app收不到我分享的东西?
A: 估计是UTD类型不匹配,或者那个app不认这种数据格式。试试用通用一点的类型,比如utd.UniformDataType.FILE。
Q: 网络上的图片能直接分享吗?
A: 不行,得先下载到本地,然后分享本地文件。
Q: 分享大文件会卡吗?
A: 会的,特别是视频文件。建议加个转圈圈的loading,或者提前压缩一下。
Q: 能知道用户选了哪个app分享吗?
A: 目前鸿蒙的API不支持,只能知道用户是分享成功了还是取消了。
Q: 能自己做个分享面板吗?
A: 不行,只能用系统提供的。不过可以通过selectionMode和previewMode参数稍微调整一下样式。
测试这块儿
真机测试的时候发现了不少问题,建议你们也多试试:
- 各种文件类型:图片、视频、文档啥的都试试
- 文件大小:小图片秒传,大视频可能要等一会儿
- 不同app:微信、QQ、邮箱的接收效果都不一样
- 网络状况:网不好的时候网络图片可能加载不出来
调试的时候多打印,鸿蒙的报错信息有时候说得不够清楚。
总结
折腾了好几天,总算把这个分享功能搞定了。整体感觉鸿蒙的API还挺人性化的,比我想象中好用多了。
几个心得:
- 多翻文档:鸿蒙官方文档写得还可以,遇到问题先去翻翻
- 类型别偷懒:ETS的类型检查确实严格,但写出来的代码更稳定
- 错误要处理好:异步操作的错误处理千万别省,不然出了bug找都找不到
- 真机多测试:模拟器和真机表现可能不一样,都试试保险点
现在这个小插件基本能满足日常需要了。如果你也在搞类似的东西,希望我这些踩坑经验能帮到你。有问题咱们可以一起交流。
所有代码都在uni_modules/cool-share目录里,有兴趣的朋友可以看看。
顺便安利个东西
我们团队还做了个 Cool Unix 组件库,也是基于 Uni-App X 的,完全免费开源。里面有 Tailwind CSS、多主题、国际化这些实用功能,还有挺多现成的组件和页面模板。
最有意思的是,这个组件库配合AI生成鸿蒙页面效果特别好,以后还准备加上后端接口自动生成,真正做到一句话就能搞出个app。如果你也想快速开发鸿蒙应用,可以试试看。
最后感谢一下
感谢 uni-app x 团队做出这么棒的跨平台框架,让我们这些普通开发者也能轻松搞定多端开发。特别是UTS语言和鸿蒙支持,真的降低了不少门槛。
0 个评论
要回复文章请先登录或注册