鸿蒙APP文件跨设备共享:手机图片一键投送智慧屏
【摘要】 引言在万物互联的时代,跨设备文件共享已成为智能生活的基础需求。鸿蒙系统的分布式能力为实现无缝设备协作提供了强大支持。本文将深入探讨如何利用鸿蒙分布式技术实现手机图片一键投送到智慧屏的功能,涵盖技术原理、实现方案及完整代码示例。技术背景鸿蒙系统的分布式架构包含三大核心技术:分布式软总线:设备自动发现与连接分布式数据管理:跨设备数据共享分布式任务调度:能力虚拟化与跨设备调用这些技术共同构成了鸿蒙...
引言
技术背景
-
分布式软总线:设备自动发现与连接 -
分布式数据管理:跨设备数据共享 -
分布式任务调度:能力虚拟化与跨设备调用
应用场景
-
家庭娱乐:手机拍摄的照片/视频一键投屏分享 -
商务演示:移动端PPT/文档快速投射到大屏 -
教育场景:手机学习资料实时同步到教学大屏 -
智能家居控制:监控画面实时投送至客厅大屏
核心特性
-
设备自动发现与认证 -
安全加密传输 -
一键式极简操作 -
断点续传与进度显示 -
多格式媒体支持
原理流程图
graph TD
A[手机APP] -->|1. 发现设备| B(分布式设备管理)
B --> C{发现智慧屏?}
C -->|是| D[建立安全通道]
C -->|否| E[提示用户]
D --> F[选择图片文件]
F --> G[压缩优化]
G --> H[分块传输]
H --> I[智慧屏接收]
I --> J[解码显示]
J --> K[播放控制]
环境准备
-
DevEco Studio 3.1+ -
HarmonyOS SDK API 8+ -
两台HarmonyOS设备(手机+智慧屏) -
相同华为账号登录 -
设备开启"分布式协同"权限
代码实现
手机端代码 (Java)
1. 配置文件 (config.json)
{
"app": {
"bundleName": "com.example.mediashare",
"vendor": "example",
"version": {
"code": 1,
"name": "1.0.0"
}
},
"deviceConfig": {},
"module": {
"package": "com.example.mediashare",
"name": ".MyApplication",
"mainAbility": "com.example.mediashare.MainAbility",
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
],
"orientation": "unspecified",
"name": "com.example.mediashare.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "$string:entry_MainAbility",
"type": "page",
"launchType": "standard"
}
],
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "跨设备数据传输"
},
{
"name": "ohos.permission.USE_BLUETOOTH",
"reason": "设备发现"
},
{
"name": "ohos.permission.DISCOVER_DEVICES",
"reason": "设备发现"
}
]
}
}
2. 主界面布局 (ability_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical">
<Text
ohos:id="$+id:title_text"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="图片投屏分享"
ohos:text_size="30fp"
ohos:text_alignment="center"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="20vp"/>
<Button
ohos:id="$+id:btn_discover"
ohos:width="200vp"
ohos:height="50vp"
ohos:text="发现设备"
ohos:text_size="20fp"
ohos:background_element="#007DFF"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="40vp"/>
<ListContainer
ohos:id="$+id:device_list"
ohos:width="match_parent"
ohos:height="300vp"
ohos:top_margin="20vp"/>
<Button
ohos:id="$+id:btn_select"
ohos:width="200vp"
ohos:height="50vp"
ohos:text="选择图片"
ohos:text_size="20fp"
ohos:background_element="#007DFF"
ohos:text_color="white"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="20vp"
ohos:enabled="false"/>
<Progress
ohos:id="$+id:progress_bar"
ohos:width="match_parent"
ohos:height="50vp"
ohos:progress="0"
ohos:max="100"
ohos:visibility="hide"
ohos:top_margin="20vp"/>
<Text
ohos:id="$+id:status_text"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="就绪"
ohos:text_size="18fp"
ohos:text_alignment="center"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="10vp"/>
</DirectionalLayout>
3. 主逻辑代码 (MainAbilitySlice.java)
package com.example.mediashare.slice;
import com.example.mediashare.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.*;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.bundle.IBundleManager;
import ohos.data.distributed.common.*;
import ohos.distributedschedule.interwork.DeviceInfo;
import ohos.distributedschedule.interwork.DeviceManager;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;
import ohos.security.SystemPermission;
import ohos.utils.zson.ZSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MainAbilitySlice extends AbilitySlice {
private static final int PERMISSION_REQUEST_CODE = 1001;
private List<DeviceInfo> deviceList = new ArrayList<>();
private DeviceInfo selectedDevice;
private String selectedFilePath;
private ProgressBar progressBar;
private Text statusText;
private Button btnSelect;
// 线程池
private ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
r -> new Thread(r, "MediaShare-Thread")
);
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
// 初始化组件
initComponents();
// 检查权限
checkPermissions();
}
private void initComponents() {
Button btnDiscover = (Button) findComponentById(ResourceTable.Id_btn_discover);
btnDiscover.setClickedListener(component -> discoverDevices());
btnSelect = (Button) findComponentById(ResourceTable.Id_btn_select);
btnSelect.setClickedListener(component -> selectImage());
progressBar = (ProgressBar) findComponentById(ResourceTable.Id_progress_bar);
statusText = (Text) findComponentById(ResourceTable.Id_status_text);
ListContainer deviceListContainer = (ListContainer) findComponentById(ResourceTable.Id_device_list);
deviceListContainer.setItemProvider(new DeviceItemProvider(deviceList, this::onDeviceSelected));
}
private void checkPermissions() {
if (verifySelfPermission(SystemPermission.DISTRIBUTED_DATASYNC) != IBundleManager.PERMISSION_GRANTED ||
verifySelfPermission(SystemPermission.USE_BLUETOOTH) != IBundleManager.PERMISSION_GRANTED ||
verifySelfPermission(SystemPermission.DISCOVER_DEVICES) != IBundleManager.PERMISSION_GRANTED) {
requestPermissionsFromUser(new String[]{
SystemPermission.DISTRIBUTED_DATASYNC,
SystemPermission.USE_BLUETOOTH,
SystemPermission.DISCOVER_DEVICES
}, PERMISSION_REQUEST_CODE);
}
}
private void discoverDevices() {
executor.execute(() -> {
try {
// 获取设备管理器实例
DeviceManager deviceManager = DeviceManager.getInstance();
// 获取在线设备列表
List<DeviceInfo> devices = deviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
// 过滤出智慧屏设备
List<DeviceInfo> smartScreens = new ArrayList<>();
for (DeviceInfo device : devices) {
if (device.getDeviceType() == DeviceInfo.DeviceType.SMART_SCREEN) {
smartScreens.add(device);
}
}
// 更新UI
getUITaskDispatcher(TaskPriority.HIGH).asyncDispatch(() -> {
deviceList.clear();
deviceList.addAll(smartScreens);
ListContainer container = (ListContainer) findComponentById(ResourceTable.Id_device_list);
container.invalidate();
if (smartScreens.isEmpty()) {
showToast("未发现可用的智慧屏设备");
} else {
showToast("发现 " + smartScreens.size() + " 台智慧屏");
}
});
} catch (Exception e) {
showToast("设备发现失败: " + e.getMessage());
}
});
}
private void onDeviceSelected(DeviceInfo device) {
selectedDevice = device;
btnSelect.setEnabled(true);
showToast("已选择设备: " + device.getDeviceName());
}
private void selectImage() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction("android.intent.action.GET_CONTENT")
.withType("image/*")
.build();
intent.setOperation(operation);
startAbilityForResult(intent, 1001);
}
@Override
protected void onActive() {
super.onActive();
}
@Override
protected void onInactive() {
super.onInactive();
}
@Override
protected void onBackground() {
super.onBackground();
}
@Override
protected void onForeground(Intent intent) {
super.onForeground(intent);
}
private void showToast(String message) {
getUITaskDispatcher(TaskPriority.HIGH).asyncDispatch(() -> {
Text toast = new Text(getContext());
toast.setText(message);
toast.setTextSize(50);
toast.setLayoutConfig(new DirectionalLayout.LayoutConfig(
LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT));
toast.setAlignment(LayoutAlignment.CENTER, LayoutAlignment.CENTER);
addComponent(toast);
// 3秒后移除
getUITaskDispatcher(TaskPriority.LOW).delayDispatch(() -> removeComponent(toast), 3000);
});
}
}
4. 设备列表适配器 (DeviceItemProvider.java)
package com.example.mediashare.slice;
import com.example.mediashare.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.components.*;
import ohos.distributedschedule.interwork.DeviceInfo;
import java.util.List;
public class DeviceItemProvider extends BaseItemProvider {
private List<DeviceInfo> deviceList;
private DeviceSelectionListener listener;
private AbilitySlice slice;
public DeviceItemProvider(List<DeviceInfo> deviceList, DeviceSelectionListener listener) {
this.deviceList = deviceList;
this.listener = listener;
}
@Override
public int getCount() {
return deviceList.size();
}
@Override
public Object getItem(int position) {
return deviceList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
DirectionalLayout layout = new DirectionalLayout(slice.getContext());
layout.setLayoutConfig(new DirectionalLayout.LayoutConfig(
ComponentContainer.LayoutConfig.MATCH_PARENT,
ComponentContainer.LayoutConfig.MATCH_CONTENT));
layout.setPadding(32, 16, 32, 16);
layout.setOrientation(Component.VERTICAL);
DeviceInfo device = deviceList.get(position);
Text nameText = new Text(slice.getContext());
nameText.setText(device.getDeviceName());
nameText.setTextSize(24);
nameText.setTextColor(Color.BLACK);
layout.addComponent(nameText);
Text typeText = new Text(slice.getContext());
typeText.setText("类型: " + getDeviceTypeName(device.getDeviceType()));
typeText.setTextSize(18);
typeText.setTextColor(Color.GRAY);
layout.addComponent(typeText);
layout.setClickedListener(component -> {
if (listener != null) {
listener.onDeviceSelected(device);
}
});
return layout;
}
private String getDeviceTypeName(int type) {
switch (type) {
case DeviceInfo.DeviceType.SMART_PHONE: return "手机";
case DeviceInfo.DeviceType.SMART_PAD: return "平板";
case DeviceInfo.DeviceType.SMART_WATCH: return "手表";
case DeviceInfo.DeviceType.SMART_TV: return "电视";
case DeviceInfo.DeviceType.SMART_SCREEN: return "智慧屏";
default: return "未知设备";
}
}
interface DeviceSelectionListener {
void onDeviceSelected(DeviceInfo device);
}
}
智慧屏端代码 (Java)
1. 主服务 (ScreenServiceAbility.java)
package com.example.screenreceiver;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.data.distributed.common.*;
import ohos.distributedschedule.interwork.DeviceInfo;
import ohos.distributedschedule.interwork.DeviceManager;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;
import ohos.rpc.RemoteException;
import ohos.utils.zson.ZSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
public class ScreenServiceAbility extends Ability {
private KvManager kvManager;
private KvStore kvStore;
private EventHandler handler;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
initDistributedDB();
initEventHandler();
}
private void initDistributedDB() {
try {
// 创建KVManager配置
KvManagerConfig config = new KvManagerConfig(this);
kvManager = KvManagerFactory.getInstance().createKvManager(config);
// 创建KVStore
KvStoreType type = KvStoreType.SINGLE_VERSION;
KvStoreConfig storeConfig = new KvStoreConfig("screenShareStore", type);
kvStore = kvManager.createKvStore(storeConfig);
// 订阅数据变更
kvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, new KvStoreSyncCallback() {
@Override
public void syncCompleted(Map<String, Changes> map) {
handleFileTransfer(map);
}
});
} catch (KvStoreException e) {
e.printStackTrace();
}
}
private void initEventHandler() {
handler = new EventHandler(EventRunner.current()) {
@Override
protected void processEvent(InnerEvent event) {
super.processEvent(event);
if (event.eventId == 1) { // 显示图片事件
String filePath = (String) event.object;
displayImage(filePath);
}
}
};
}
private void handleFileTransfer(Map<String, Changes> changeMap) {
for (Map.Entry<String, Changes> entry : changeMap.entrySet()) {
Changes changes = entry.getValue();
for (Entry added : changes.getPutEntries()) {
if ("file_transfer".equals(added.getKey())) {
ZSONObject data = ZSONObject.stringToZSON((String) added.getValue().getValue());
String fileName = data.getString("fileName");
String fileData = data.getString("fileData");
saveAndDisplayImage(fileName, fileData);
}
}
}
}
private void saveAndDisplayImage(String fileName, String base64Data) {
try {
// 保存文件
byte[] fileBytes = Base64.getDecoder().decode(base64Data);
String path = getExternalFilesDir(null) + File.separator + fileName;
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(fileBytes);
}
// 发送显示事件
InnerEvent event = InnerEvent.get(1, path);
handler.sendEvent(event, 0, EventHandler.Priority.IMMEDIATE);
} catch (Exception e) {
e.printStackTrace();
}
}
private void displayImage(String filePath) {
// 在实际应用中,这里会将图片显示在智慧屏UI上
// 本示例使用日志输出代替
HiLog.info(LABEL_LOG, "显示图片: " + filePath);
}
}
运行结果
-
手机端成功发现智慧屏设备 -
选择图片后开始传输 -
智慧屏接收并显示图片 -
传输进度实时显示
测试步骤
-
在两台设备上安装应用 -
登录相同华为账号 -
开启蓝牙和位置服务 -
手机端点击"发现设备" -
选择智慧屏设备 -
点击"选择图片"按钮 -
选择要传输的图片 -
观察智慧屏是否显示图片
部署场景
-
家庭网络:所有设备在同一个Wi-Fi下 -
企业环境:通过VPN连接的分布式网络 -
公共场所:商场、机场等公共区域的设备共享
疑难解答
-
设备无法发现 -
检查设备是否登录同一华为账号 -
确认蓝牙和位置服务已开启 -
确保设备间距离在10米内
-
-
传输失败 -
检查网络连接稳定性 -
确认目标设备存储空间充足 -
尝试减小文件尺寸后重试
-
-
权限问题 -
检查应用是否已获得所有必要权限 -
在设置中手动授予权限
-
未来展望
-
多设备协同:支持手机、平板、PC等多设备同时投屏 -
AI增强:自动识别图片内容并添加AR效果 -
云同步:结合云服务实现跨地域设备共享 -
低功耗模式:优化传输算法降低能耗
技术趋势与挑战
-
端侧AI处理能力提升 -
5G/6G网络带来更高带宽 -
边缘计算减少云端依赖
-
异构设备兼容性 -
安全隐私保护 -
复杂网络环境下的稳定性
总结
-
使用DeviceManager进行设备发现 -
通过分布式数据库实现安全传输 -
采用分块传输和进度反馈优化体验 -
多线程处理保证UI流畅性
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)