鸿蒙APP文件跨设备共享:手机图片一键投送智慧屏

举报
鱼弦 发表于 2025/12/10 10:20:09 2025/12/10
【摘要】 引言在万物互联的时代,跨设备文件共享已成为智能生活的基础需求。鸿蒙系统的分布式能力为实现无缝设备协作提供了强大支持。本文将深入探讨如何利用鸿蒙分布式技术实现手机图片一键投送到智慧屏的功能,涵盖技术原理、实现方案及完整代码示例。技术背景鸿蒙系统的分布式架构包含三大核心技术:分布式软总线:设备自动发现与连接分布式数据管理:跨设备数据共享分布式任务调度:能力虚拟化与跨设备调用这些技术共同构成了鸿蒙...


引言

在万物互联的时代,跨设备文件共享已成为智能生活的基础需求。鸿蒙系统的分布式能力为实现无缝设备协作提供了强大支持。本文将深入探讨如何利用鸿蒙分布式技术实现手机图片一键投送到智慧屏的功能,涵盖技术原理、实现方案及完整代码示例。

技术背景

鸿蒙系统的分布式架构包含三大核心技术:
  • 分布式软总线:设备自动发现与连接
  • 分布式数据管理:跨设备数据共享
  • 分布式任务调度:能力虚拟化与跨设备调用
这些技术共同构成了鸿蒙设备间无缝协作的基础。

应用场景

  1. 家庭娱乐:手机拍摄的照片/视频一键投屏分享
  2. 商务演示:移动端PPT/文档快速投射到大屏
  3. 教育场景:手机学习资料实时同步到教学大屏
  4. 智能家居控制:监控画面实时投送至客厅大屏

核心特性

  • 设备自动发现与认证
  • 安全加密传输
  • 一键式极简操作
  • 断点续传与进度显示
  • 多格式媒体支持

原理流程图

graph TD
    A[手机APP] -->|1. 发现设备| B(分布式设备管理)
    B --> C{发现智慧屏?}
    C -->|是| D[建立安全通道]
    C -->|否| E[提示用户]
    D --> F[选择图片文件]
    F --> G[压缩优化]
    G --> H[分块传输]
    H --> I[智慧屏接收]
    I --> J[解码显示]
    J --> K[播放控制]

环境准备

  1. DevEco Studio 3.1+
  2. HarmonyOS SDK API 8+
  3. 两台HarmonyOS设备(手机+智慧屏)
  4. 相同华为账号登录
  5. 设备开启"分布式协同"权限

代码实现

手机端代码 (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);
    }
}

运行结果

  1. 手机端成功发现智慧屏设备
  2. 选择图片后开始传输
  3. 智慧屏接收并显示图片
  4. 传输进度实时显示

测试步骤

  1. 在两台设备上安装应用
  2. 登录相同华为账号
  3. 开启蓝牙和位置服务
  4. 手机端点击"发现设备"
  5. 选择智慧屏设备
  6. 点击"选择图片"按钮
  7. 选择要传输的图片
  8. 观察智慧屏是否显示图片

部署场景

  • 家庭网络:所有设备在同一个Wi-Fi下
  • 企业环境:通过VPN连接的分布式网络
  • 公共场所:商场、机场等公共区域的设备共享

疑难解答

  1. 设备无法发现
    • 检查设备是否登录同一华为账号
    • 确认蓝牙和位置服务已开启
    • 确保设备间距离在10米内
  2. 传输失败
    • 检查网络连接稳定性
    • 确认目标设备存储空间充足
    • 尝试减小文件尺寸后重试
  3. 权限问题
    • 检查应用是否已获得所有必要权限
    • 在设置中手动授予权限

未来展望

  1. 多设备协同:支持手机、平板、PC等多设备同时投屏
  2. AI增强:自动识别图片内容并添加AR效果
  3. 云同步:结合云服务实现跨地域设备共享
  4. 低功耗模式:优化传输算法降低能耗

技术趋势与挑战

趋势
  • 端侧AI处理能力提升
  • 5G/6G网络带来更高带宽
  • 边缘计算减少云端依赖
挑战
  • 异构设备兼容性
  • 安全隐私保护
  • 复杂网络环境下的稳定性

总结

本文详细介绍了基于鸿蒙分布式能力的图片跨设备共享实现方案。通过分布式设备管理、安全数据传输和轻量级UI设计,实现了手机到智慧屏的一键投屏功能。关键技术点包括:
  1. 使用DeviceManager进行设备发现
  2. 通过分布式数据库实现安全传输
  3. 采用分块传输和进度反馈优化体验
  4. 多线程处理保证UI流畅性
完整代码展示了从设备发现、文件选择到传输显示的整个流程,开发者可根据实际需求扩展更多功能,如视频投屏、多屏互动等。鸿蒙的分布式能力为跨设备协作提供了坚实基础,未来将在智能家居、移动办公等领域发挥更大价值。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。