鸿蒙应用迁移

3,434 阅读6分钟

鸿蒙分布式任务调度基于分布式软总线、分布式数据管理、分布式Profile等技术特性,构建统一的分布式服务管理(发现、同步、注册、调用)机制,支持对跨设备的应用进行远程启动、远程调用、远程连接以及迁移等操作,能够根据不同设备的能力、位置、业务运行状态、资源使用情况,以及用户的习惯和意图,选择合适的设备运行分布式任务。本文将借助分布式任务调度应用迁移构建第一个鸿蒙应用。

效果预览

demo运行效果如下: 在这里插入图片描述 若运行在手机中,则上图的图片会垂直排列。 点击图中的应用迁移右边的按钮则会出现权限申请弹框: 在这里插入图片描述 若用户在手机中安装了相同的应用,并且允许多设备协同访问,且设备校验是同一用户的话,手机上的应用则会迁移到电视中。 没有提供手机效果图是因为目前没有支持鸿蒙的手机,而且个人开发者无法申请Profile文件不能打.app格式包,所以无法提供效果图。

开发

  1. 开发工具。 鸿蒙应用开发有专门的IDE,本文开发使用的是HUAWEI DevEco Studio 2.0.10.201,若没有安装,请到官方网站下载安装。DevEco和Android Studio一样,都是基于IntelliJPlatform,两者之间没有太大区别,安卓开发者可无缝使用。相关开发环境配置请查看官方指导
  2. 创建项目。 打开DevEco Studio,点击File--->New--->New Project,出现如下选项: 在这里插入图片描述 创建新项目的时候出现多种设备选择及开发框架(java,js)选择,本文选择TV和Empty Feature Ability(Java),原因是大屏显示效果比较好,java开发比较熟悉。可供选择的设备目前没有手机因为目前还没有开放给个人开发者鸿蒙系统的手机,估计不久以后就会有的。 新的项目创建完成之后,将会出现如下项目配置: 在这里插入图片描述 项目目录结构和Android Studio的项目结构基本一致,其中entry>libs:用于存放entry模块的依赖文件,entry>src>main>Java:用于存放Java源码,entry>src>main>resources:用于存放应用所用到的资源文件,如图形、多媒体、字符串、布局文件等:
资源目录资源文件说明
base>element包括字符串、整型数、颜色、样式等资源的json文件。
base>graphicxml类型的可绘制资源,如SVG(Scalable Vector Graphics)可缩放矢量图形文件、基本的几何图形(如矩形、圆形、线等)shape资源等。
base>layoutxml格式的界面布局文件。
base>media多媒体文件,如图形、视频、音频等文件,支持的文件格式包括:.png、.gif、.mp3、.mp4等
rawfile用于存储任意格式的原始资源文件。区别在于rawfile不会根据设备的状态去匹配不同的资源,需要指定文件路径和文件名进行引用。

有些资源目录上图中没有创建,开发者可在需要使用的时候自行创建,具体项目说明请参考HommnyOS工程介绍。图中的MainAbility相当于Android的MainActivity。

  1. 创建视图(UI)。 创建视图的方式和Android一样,同时支持代码和xml创建,本文使用xml创建:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:orientation="horizontal">
        <Text
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:layout_alignment="center"
            ohos:text="应用迁移"
            ohos:text_color="#000000"
            ohos:text_size="48px"/>
        <Image
            ohos:id="$+id:ig_sync"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:image_src="$media:case"
            ohos:left_margin="20px"/>
    </DirectionalLayout>
    <AdaptiveBoxLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:top_margin="20px">
        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:orientation="vertical"
            ohos:margin="20vp">
            <Image
                ohos:id="$+id:dl_test0"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:image_src="$media:phone"/>
        </DirectionalLayout>
        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:orientation="vertical"
            ohos:margin="20vp">
            <Image
                ohos:id="$+id:dl_test1"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:image_src="$media:tv"/>
        </DirectionalLayout>
    </AdaptiveBoxLayout>
</DirectionalLayout>

DirectionalLayout相当于Android的LinearLayout,分水平和垂直,本文选择垂直布局。AdaptiveBoxLayout属于自适应布局,若设备屏幕是竖屏则会垂直显示,若是宽屏设备则会水平显示。其他元素的使用大致相同,其中id的申明和单位的使用需要注意一下,vp相当与Android的dp,其他的属性需要开发者自行扒一扒源码,也可以看看官方开发说明。DevEco Studio原计划在2020-10-30版本中提供即时预览功能(java和js),不知为何在最新版中没有看见。

  1. 功能实现。 在MainAbility中注入视图:
public class MainAbility extends Ability implements IAbilityContinuation, Component.ClickedListener, Component.KeyEventListener {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // super.setMainRoute(MainAbilitySlice.class.getName());
        super.setUIContent(ResourceTable.Layout_ability_main);

        Image sync = (Image) findComponentById(com.example.tvtest.ResourceTable.Id_ig_sync);
        sync.setClickedListener(this::onClick);
        sync.setKeyEventListener(this::onKeyEvent);

        Image test0 = (Image) findComponentById(com.example.tvtest.ResourceTable.Id_dl_test0);
        test0.setScaleMode(Image.ScaleMode.ZOOM_CENTER);

        Image test1 = (Image) findComponentById(com.example.tvtest.ResourceTable.Id_dl_test1);
        test1.setScaleMode(Image.ScaleMode.ZOOM_CENTER);

    }

setMainRoute是设置主页面的路由,MainAbilitySlice相当Fragment,super.setMainRoute(MainAbilitySlice.class.getName())执行的结果是进入应用后直接进入MainAbilitySlice,本文页面简单就直接在MainAbility处理相关逻辑。由于图片资源较大,所以图片使用setScaleMode(Image.ScaleMode.ZOOM_CENTER)设置图片居中缩放,否则电视屏幕无法全部显示图片。 setUIContent即绑定ability_main至MainAbility。findComponentById相当于findViewById。

  • 手机点击事件 sync 是应用迁移按钮,点击之后将会将手机中应用数据迁移到电视中,所以需要实现Component.ClickedListener接口:
    /**
     * 点击事件
     * @param component
     */
    @Override
    public void onClick(Component component) {
        switch (component.getId()){
            case com.example.tvtest.ResourceTable.Id_ig_sync:
                migrateAbility();
                break;
            default:
                break;
        }
    }

如果只有一个点击事件,可以不用component.getId()获取点击按钮。

  • 电视点击事件 因为代码同样运行在电视中,电视没有触屏,不像手机那样方便。电视控制一般使用遥控器,所以开发者还需要监听电视遥控器的按钮点击事件,需要MainAbility实现Component.KeyEventListener接口:
    /**
     * 遥控器中心按钮按下时开始迁移
     * @param component
     * @param keyEvent
     * @return
     */
    @Override
    public boolean onKeyEvent(Component component, KeyEvent keyEvent) {
        if (keyEvent.isKeyDown() && keyEvent.getKeyCode() == KeyEvent.KEY_DPAD_DOWN) {
            migrateAbility();
            return true;
        }
        return false;
    }

keyEvent.isKeyDown() && keyEvent.getKeyCode() == KeyEvent.KEY_DPAD_DOWN是按钮按下事件且是遥控器中心按钮。

  • 权限校验与申请 点击应用迁徙按钮执行migrateAbility方法开始迁移数据,迁移之前需要校验权限:
 /**
     * 应用迁徙
     */
    private void migrateAbility() {
        if (verifySelfPermission(SystemPermission.DISTRIBUTED_DATASYNC) == IBundleManager.PERMISSION_GRANTED) {
            this.continueAbility();
        } else {
            requestPermission(SystemPermission.DISTRIBUTED_DATASYNC);
        }
    }

如果没有相应权限,需要申请权限:

    /**
     * 申请权限
     *
     * @param permission
     */
    private void requestPermission(String permission) {
        if (canRequestPermission(permission)) {
            requestPermissionsFromUser(new String[]{permission}, 0x1001);
        }
    }

权限申请结果处理:


    /**
     * 权限申请结果
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
        if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) {
            return;
        }
        if (requestCode == 0x1001 && grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
            this.continueAbility();
        }
    }

0x1001:权限声明码,SystemPermission.DISTRIBUTED_DATASYNC:应用迁移权限。 应用迁移之前需要动态申请DISTRIBUTED_DATASYNC权限,同时需要在config.json中申请:

{
  "app": {
    "bundleName": "com.example.tvtest",
    "vendor": "example",
    "version": {
      "code": 1,
      "name": "1.0"
    },
    "apiVersion": {
      "compatible": 3,
      "target": 3
    }
  },
  "deviceConfig": {},
  "module": {
    "package": "com.example.tvtest",
    "name": ".tvtest",
    "reqCapabilities": [
      "video_support"
    ],
    "deviceType": [
      "tv"
    ],
    "distro": {
      ...
    },
    "abilities": [
    ....
    ],
    "reqPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      }
    ]
  }
}

权限申请reqPermissions的位置需要和abilities同级。deviceType则是声明了当前应用适用的终端是TV。config.json相当于AndroidManifest.xml,只是开发者初次使用不知该如何配置,不如AndroidManifest那样明确,详细配置说明请参考配置文件的元素

  • 应用迁移 应用迁移需要实现IAbilityContinuation接口:
public class MainAbility extends Ability implements IAbilityContinuation...

实现IAbilityContinuation接口后强制重写以下方法:

    @Override
    public boolean onStartContinuation() {
        return true;
    }

    @Override
    public boolean onSaveData(IntentParams intentParams) {
        return true;
    }

    @Override
    public boolean onRestoreData(IntentParams intentParams) {
        return true;
    }

    @Override
    public void onCompleteContinuation(int i) {
        this.terminateAbility();
    }

默认返回值为false,开发者需要将其改成true。若没有实现IAbilityContinuation接口,不会有continueAbility()方法,不调用此方法,无法迁移应用。onSaveData()返回ture则当应用迁移到其他设备时会打开至相同状态。

说明

  • 鸿蒙应用目前只针对鸿蒙设备,调试目前只提供模拟器调试,模拟器需要开发者登录申请。
  • 应用迁移需要不同设备的用户为同一用户,否则迁移失败。
  • 个人开发者无法申请一些签名文件,所以应用无法打包成.app格式包,所以无法提供手机效果图。
  • 部分方法尚在探索中,暂时不能进行详细解析。
  • 一套代码可运行在多种设备中,无需特殊处理。
  • 若有侵权或错误,请发送邮件至alphabetadata@163.com