服务与应用迁移


1 Service的基本用法

ServiceAbility是在后台运行无界面应用程序的解决方案。

1.1 创建服务

com.minwei.basicservice右键
  New
    Ability
      Empty Service Ability
        Service Name: ServiceAbility
        Package name: com.minwei.basicservice
        Enable background mode: Off

自动生成entry\src\main\java\com\minwei\basicservice\ServiceAbility.java文件,其中包含Ability类的子类ServiceAbility:

public class ServiceAbility extends Ability {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        return null;
    }

    @Override
    public void onDisconnect(Intent intent) {
    }

    @Override
    public void onBackground() {
        super.onBackground();
    }

    @Override
    public void onStop() {
        super.onStop();
    }
}

同时自动在config.json中添加相关配置:

{
  "name": "com.minwei.basicservice.ServiceAbility",
  "icon": "$media:icon",
  "description": "$string:serviceability_description",
  "type": "service" <- 这是一个服务,与一般应用"page"不同
}

1.2 启动和停止服务

1.2.1 启动服务

Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId("")
    .withBundleName(getBundleName())
    .withAbilityName(ServiceAbility.class.getName())
    .build();
intent.setOperation(operation);
startAbility(intent);

1.2.2 停止服务

Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId("")
    .withBundleName(getBundleName())
    .withAbilityName(ServiceAbility.class.getName())
    .build();
intent.setOperation(operation);
stopAbility(intent);

1.2.3 观察日志

      |启动服务
      v
  onStart()
      |
      v
 onCommand()
      |启动服务
      v
 onCommand()
      |停止服务
      v
onBackground()
      |
      v
   onStop()

1.2.4 生命周期

                  onStop
       初始态<----------------
         |onStart            |
         v                   |
      非活动态------------->后台态
onCommand|^   onBackground
         v|
       活动态

1.3 连接和断开服务

1.3.1 连接服务

定义远程对象类:

public class RemoteObject extends LocalRemoteObject {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101, RemoteObject.class.getCanonicalName());

    public RemoteObject() {
        HiLog.info(label, "RemoteObject()");
    }

    public void manipulateService() { <------------------------
        HiLog.info(label, "manipulateService()");            5|
    }                                                         |
}                                                             |

在ServiceAbility类的onConnect()回调方法中返回一个远程对象:

public class ServiceAbility extends Ability {                 |
    ...                                                       | OS
    @Override                                                 | 2|
    public IRemoteObject onConnect(Intent intent) { <------------|
        ...                                                   | 3|
        return new RemoteObject(); ----------------------------->|
    }                                                         |  |
    ...                                                       |  |
}                                                             |  |

创建一个连接对象:

IAbilityConnection connection = new IAbilityConnection() {    |  |
    @Override                                                 |  |
    public void onAbilityConnectDone(ElementName elementName, | 4|
        IRemoteObject iRemoteObject, int i) { <------------------|
        RemoteObject object = (RemoteObject)iRemoteObject;    |  |
        object.manipulateService(); ---------------------------  |
    }                                                            |
                                                                 |
    @Override                                                    |
    public void onAbilityDisconnectDone(                         |
        ElementName elementName, int i) {                        |
    }                                                            |
};                                                               |

以连接对象为参数连接服务:

Intent intent = new Intent();                                    |
Operation operation = new Intent.OperationBuilder()              |
    .withDeviceId("")                                            |
    .withBundleName(getBundleName())                             |
    .withAbilityName(ServiceAbility.class.getName())             |
    .build();                                                    |
intent.setOperation(operation);                                  |
connectAbility(intent, connection);                              |
                  \________/                                     |
                       |                                        1|
                       ----------------------------------------->|
                                                                OS

1.3.2 断开服务

disconnectAbility(connection);

1.3.3 观察日志

        |连接服务
        v
    onStart()
        |
        v
   onConnect()
        |
        v
  RemoteObject()
        |
        v
manipulateService()
        |断开服务
        v
  onDisconnect()
        |
        v
  onBackground()
        |
        v
     onStop()

1.3.4 生命周期

                  onStop
       初始态<----------------
         |onStart            |
         v                   |
      非活动态------------->后台态
onConnect|^   onBackground
         v|onDisconnect
       活动态

例程:BasicService

...\BasicService\entry\src\main\resources\base\graphic\background_button_start.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#00a2e8"/>
</shape>

...\BasicService\entry\src\main\resources\base\graphic\background_button_stop.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#22b14c"/>
</shape>

...\BasicService\entry\src\main\resources\base\graphic\background_button_connect.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#ff7f27"/>
</shape>

...\BasicService\entry\src\main\resources\base\graphic\background_button_disconnect.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#ee1c24"/>
</shape>

...\BasicService\entry\src\main\resources\base\graphic\background_toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <solid ohos:color="#80000000"/>
    <corners ohos:radius="12vp"/>
</shape>

...\BasicService\entry\src\main\resources\base\layout\ability_main.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:left_padding="60vp"
    ohos:right_padding="60vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:btnStart"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:background_element="$graphic:background_button_start"
        ohos:text="启动服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnStop"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:top_margin="20vp"
        ohos:background_element="$graphic:background_button_stop"
        ohos:text="停止服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnConnect"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:top_margin="20vp"
        ohos:background_element="$graphic:background_button_connect"
        ohos:text="连接服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDisconnect"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:top_margin="20vp"
        ohos:background_element="$graphic:background_button_disconnect"
        ohos:text="断开服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\BasicService\entry\src\main\resources\base\layout\toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:left_padding="20vp"
    ohos:top_padding="5vp"
    ohos:right_padding="20vp"
    ohos:bottom_padding="5vp"
    ohos:background_element="$graphic:background_toast_dialog">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\BasicService\entry\src\main\java\com\minwei\basicservice\RemoteObject.java

public class RemoteObject extends LocalRemoteObject {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101, RemoteObject.class.getCanonicalName());

    public RemoteObject() {
        HiLog.info(label, "RemoteObject()");
    }

    public void manipulateService() {
        HiLog.info(label, "manipulateService()");
    }
}

...\BasicService\entry\src\main\java\com\minwei\basicservice\ServiceAbility.java

public class ServiceAbility extends Ability {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101, ServiceAbility.class.getCanonicalName());

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        HiLog.info(label, "onStart()");
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
        HiLog.info(label, "onCommand()");
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(label, "onConnect()");
        return new RemoteObject();
    }

    @Override
    public void onDisconnect(Intent intent) {
        HiLog.info(label, "onDisconnect()");
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(label, "onBackground()");
    }

    @Override
    public void onStop() {
        super.onStop();
        HiLog.info(label, "onStop()");
    }
}

...\BasicService\entry\src\main\java\com\minwei\basicservice\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice
    implements ClickedListener {
    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnStart)
            .setClickedListener(this);
        findComponentById(ResourceTable.Id_btnStop)
            .setClickedListener(this);
        findComponentById(ResourceTable.Id_btnConnect)
            .setClickedListener(this);
        findComponentById(ResourceTable.Id_btnDisconnect)
            .setClickedListener(this);
    }
    ...
    @Override
    public void onClick(Component component) {
        switch (component.getId()) {
            case ResourceTable.Id_btnStart:
                startService();
                break;

            case ResourceTable.Id_btnStop:
                stopService();
                break;

            case ResourceTable.Id_btnConnect:
                connectService();
                break;

            case ResourceTable.Id_btnDisconnect:
                disconnectService();
                break;
        }
    }

    private void startService() {
        showToast("启动服务");

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(getBundleName())
            .withAbilityName(ServiceAbility.class.getName())
            .build();
        intent.setOperation(operation);
        startAbility(intent);
    }

    private void stopService() {
        showToast("停止服务");

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(getBundleName())
            .withAbilityName(ServiceAbility.class.getName())
            .build();
        intent.setOperation(operation);
        stopAbility(intent);
    }

    private IAbilityConnection connection = new IAbilityConnection() {
        @Override
        public void onAbilityConnectDone(ElementName elementName,
            IRemoteObject iRemoteObject, int i) {
            RemoteObject object = (RemoteObject)iRemoteObject;
            object.manipulateService();
        }

        @Override
        public void onAbilityDisconnectDone(
            ElementName elementName, int i) {
        }
    };

    private void connectService() {
        showToast("连接服务");

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(getBundleName())
            .withAbilityName(ServiceAbility.class.getName())
            .build();
        intent.setOperation(operation);
        connectAbility(intent, connection);
    }

    private void disconnectService() {
        showToast("断开服务");

        disconnectAbility(connection);
    }

    private void showToast(String text) {
        Component component = LayoutScatter.getInstance(this).parse(
            ResourceTable.Layout_toast_dialog, null, false);
        ((Text)component.findComponentById(ResourceTable.Id_txt))
            .setText(text);
        new ToastDialog(this)
            .setContentCustomComponent(component)
            .setSize(LayoutConfig.MATCH_CONTENT,
                LayoutConfig.MATCH_CONTENT)
            .setDuration(5000)
            .setAlignment(LayoutAlignment.BOTTOM)
            .setOffset(0, AttrHelper.vp2px(60, this))
            .show();
    }
}

运行效果如下图所示:

2 Service的高级用法

2.1 前台Service

应用程序一旦进入后台,出于节约资源的考虑,系统会杀死由该应用程序启动的所有Service。如果其中某个Service正在执行任务,如播放音乐或导航等,该任务即随之中断,给用户带来不好的使用体验。

为此,可将某些Service设置为前台Service,即使启动该Service的应用程序已经退至后台,该Service也不会被杀死,而是继续运行,持久地为用户提供服务,直到用户主动关闭该应用程序。

前台Service也是应用程序处于后台时保活的重要技术手段。同时,为了防止某些应用程序利用前台Service恶意侵占系统资源,威胁用户的信息安全,非法监控用户的行为,前台Service必须显示在通知栏中,保持对用户可见。

com.minwei.foregroundservice右键
  New
    Ability
      Empty Service Ability
        Service Name: ServiceAbility
        Package name: com.minwei.foregroundservice
        Enable background mode: On <- 在后台保持运行的Service叫做前台Service
        选择应用场景:On

自动生成entry\src\main\java\com\minwei\foregroundservice\ServiceAbility.java文件,其中包含Ability类的子类ServiceAbility:

public class ServiceAbility extends Ability {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        return null;
    }

    @Override
    public void onDisconnect(Intent intent) {
    }

    @Override
    public void onBackground() {
        super.onBackground();
    }

    @Override
    public void onStop() {
        super.onStop();
    }
}

同时自动在config.json中添加相关配置:

{
  "backgroundModes": [
    "dataTransfer" <- 在后台保持运行的Service叫做前台Service
  ],
  "name": "com.minwei.foregroundservice.ServiceAbility",
  "icon": "$media:icon",
  "description": "$string:serviceability_description",
  "type": "service" <- 这是一个服务,与一般应用"page"不同
}

在module中添加"保持后台运行"权限请求:

"reqPermissions": [
  {
    "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
  }
]

在ServiceAbility类的onStart()方法中发布通知并保持后台运行:

NotificationNormalContent normal =
    new NotificationNormalContent()
	.setTitle("前台服务")
	.setText("我还在运行……")
	.setAdditionalText("常驻后台");

NotificationContent content =
    new NotificationContent(normal);

NotificationRequest request =
    new NotificationRequest(1001)
	.setContent(content);

keepBackgroundRunning(1001, request);

启动服务:

Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId("")
    .withBundleName(getBundleName())
    .withAbilityName(ServiceAbility.class.getName())
    .build();
intent.setOperation(operation);
startAbility(intent);

停止服务:

Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId("")
    .withBundleName(getBundleName())
    .withAbilityName(ServiceAbility.class.getName())
    .build();
intent.setOperation(operation);
stopAbility(intent);

例程:ForegroundService

...\ForegroundService\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "reqPermissions": [
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
      }
    ],
    ...
  }
}

...\ForegroundService\entry\src\main\resources\base\graphic\background_button_start.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#00a2e8"/>
</shape>

...\ForegroundService\entry\src\main\resources\base\graphic\background_button_stop.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#22b14c"/>
</shape>

...\ForegroundService\entry\src\main\resources\base\graphic\background_toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <solid ohos:color="#80000000"/>
    <corners ohos:radius="12vp"/>
</shape>

...\ForegroundService\entry\src\main\resources\base\layout\ability_main.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:left_padding="60vp"
    ohos:right_padding="60vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:btnStart"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:background_element="$graphic:background_button_start"
        ohos:text="启动服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnStop"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:top_margin="20vp"
        ohos:background_element="$graphic:background_button_stop"
        ohos:text="停止服务"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\ForegroundService\entry\src\main\resources\base\layout\toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:left_padding="20vp"
    ohos:top_padding="5vp"
    ohos:right_padding="20vp"
    ohos:bottom_padding="5vp"
    ohos:background_element="$graphic:background_toast_dialog">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\ForegroundService\entry\src\main\java\com\minwei\foregroundservice\ServiceAbility.java

public class ServiceAbility extends Ability {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101, ServiceAbility.class.getCanonicalName());

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        HiLog.info(label, "onStart()");

        NotificationNormalContent normal =
            new NotificationNormalContent()
                .setTitle("前台服务")
                .setText("保持运行……")
                .setAdditionalText("常驻后台");

        NotificationContent content =
            new NotificationContent(normal);

        NotificationRequest request =
            new NotificationRequest(1001)
                .setContent(content);

        keepBackgroundRunning(1001, request);
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
        HiLog.info(label, "onCommand()");
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(label, "onConnect()");
        return null;
    }

    @Override
    public void onDisconnect(Intent intent) {
        HiLog.info(label, "onDisconnect()");
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(label, "onBackground()");
    }

    @Override
    public void onStop() {
        super.onStop();
        HiLog.info(label, "onStop()");
    }
}

...\ForegroundService\entry\src\main\java\com\minwei\foregroundservice\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice
    implements ClickedListener {
    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnStart)
            .setClickedListener(this);
        findComponentById(ResourceTable.Id_btnStop)
            .setClickedListener(this);
    }
    ...
    @Override
    public void onClick(Component component) {
        switch (component.getId()) {
            case ResourceTable.Id_btnStart:
                startService();
                break;

            case ResourceTable.Id_btnStop:
                stopService();
                break;
        }
    }

    private void startService() {
        showToast("启动服务");

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(getBundleName())
            .withAbilityName(ServiceAbility.class.getName())
            .build();
        intent.setOperation(operation);
        startAbility(intent);
    }

    private void stopService() {
        showToast("停止服务");

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withDeviceId("")
            .withBundleName(getBundleName())
            .withAbilityName(ServiceAbility.class.getName())
            .build();
        intent.setOperation(operation);
        stopAbility(intent);
    }

    private void showToast(String text) {
        Component component = LayoutScatter.getInstance(this).parse(
            ResourceTable.Layout_toast_dialog, null, false);
        ((Text)component.findComponentById(ResourceTable.Id_txt))
            .setText(text);
        new ToastDialog(this)
            .setContentCustomComponent(component)
            .setSize(LayoutConfig.MATCH_CONTENT,
                LayoutConfig.MATCH_CONTENT)
            .setDuration(5000)
            .setAlignment(LayoutAlignment.BOTTOM)
            .setOffset(0, AttrHelper.vp2px(60, this))
            .show();
    }
}

运行效果如下图所示:

2.2 JS UI调用Service

创建基于JS UI的应用,修改UI。

创建服务:

com.minwei.computingservice右键
  New
    Ability
      Empty Service Ability
        Service Name: ServiceAbility
        Package name: com.minwei.computingservice
        Enable background mode: Off

定义远程代理类:

public class RemoteBroker extends RemoteObject implements IRemoteBroker {
    public RemoteBroker() {
        super("");
    }

    @Override
    public boolean onRemoteRequest(int code, MessageParcel data,
        MessageParcel reply, MessageOption option) {
        // 请求代码
        switch (code) {
            case 1001:
                // 获取参数
                ZSONObject params = ZSONObject.stringToZSON(
                    data.readString());
                int x = params.getInteger("x");
                int y = params.getInteger("y");

                // 加法计算
                int z = x + y;

                // 输出结果
                ZSONObject result = new ZSONObject();
                result.put("code", 1002);
                result.put("z", z);
                reply.writeString(ZSONObject.toZSONString(result));
                break;

            default:
                reply.writeString("无效的请求代码");
                return false;
        }

        return true;
    }

    @Override
    public IRemoteObject asObject() {
        return this;
    }
}

在ServiceAbility类的onConnect()方法中创建并返回远程代理对象:

public IRemoteObject onConnect(Intent intent) {
    return new RemoteBroker().asObject();
}

在index.js中调用ServiceAbility:

export default {
    data: {
        x: 123,
        y: 456,
        z: "?"
    },
    async onClick() {
        var action = {
            "bundleName"  : "com.minwei.computingservice",
            "abilityName" : "com.minwei.computingservice.ServiceAbility",
            "abilityType" : 0,
            "syncOption"  : 0,
            "messageCode" : 1001,
            "data"        : {"x": this.x, "y": this.y}
        };

        var result = JSON.parse(await FeatureAbility.callAbility(action));

        if (result.code == 1002)
            this.z = result.z;
    }
}

例程:ComputingService

...\ComputingService\entry\src\main\js\default\pages\index\index.hml

<div class="container">
    <text class="title" onclick="onClick" >
        {{ x }} + {{ y }} = {{ z }}
    </text>
</div>

...\ComputingService\entry\src\main\js\default\pages\index\index.css

.container {
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.title {
    font-size: 40px;
    color: #00a2e8;
    opacity: 1.0;
}

@media screen and (device-type: tablet) and (orientation: landscape){
    .title {
        font-size: 100px;
    }
}

@media screen and (device-type: wearable) {
    .title {
        font-size: 28px;
        color: #FFFFFF;
    }
}

@media screen and (device-type: tv) {
    .container {
        background-image: url("../../common/images/Wallpaper.png");
        background-size: cover;
        background-repeat: no-repeat;
        background-position: center;
    }
    .title {
        font-size: 100px;
        color: #FFFFFF;
    }
}

@media screen and (device-type: phone) and (orientation: landscape) {
    .title {
        font-size: 60px;
    }
}

...\ComputingService\entry\src\main\js\default\pages\index\index.js

export default {
    data: {
        x: 123,
        y: 456,
        z: "?"
    },
    async onClick() {
        var action = {
            "bundleName"  : "com.minwei.computingservice",
            "abilityName" : "com.minwei.computingservice.ServiceAbility",
            "abilityType" : 0,
            "syncOption"  : 0,
            "messageCode" : 1001,
            "data"        : {"x": this.x, "y": this.y}
        };

        var result = JSON.parse(await FeatureAbility.callAbility(action));

        if (result.code == 1002)
            this.z = result.z;
    }
}

...\ComputingService\entry\src\main\java\com\minwei\computingservice\RemoteBroker.java

public class RemoteBroker extends RemoteObject implements IRemoteBroker {
    public RemoteBroker() {
        super("");
    }

    @Override
    public boolean onRemoteRequest(int code, MessageParcel data,
        MessageParcel reply, MessageOption option) {
        // 请求代码
        switch (code) {
            case 1001:
                // 获取参数
                ZSONObject params = ZSONObject.stringToZSON(
                    data.readString());
                int x = params.getInteger("x");
                int y = params.getInteger("y");

                // 加法计算
                int z = x + y;

                // 输出结果
                ZSONObject result = new ZSONObject();
                result.put("code", 1002);
                result.put("z", z);
                reply.writeString(ZSONObject.toZSONString(result));
                break;

            default:
                reply.writeString("无效的请求代码");
                return false;
        }

        return true;
    }

    @Override
    public IRemoteObject asObject() {
        return this;
    }
}

...\ComputingService\entry\src\main\java\com\minwei\computingservice\ServiceAbility.java

public class ServiceAbility extends Ability {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101, ServiceAbility.class.getCanonicalName());

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        HiLog.info(label, "onStart()");
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
        HiLog.info(label, "onCommand()");
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        HiLog.info(label, "onConnect()");
        return new RemoteBroker().asObject();
    }

    @Override
    public void onDisconnect(Intent intent) {
        HiLog.info(label, "onDisconnect()");
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(label, "onBackground()");
    }

    @Override
    public void onStop() {
        super.onStop();
        HiLog.info(label, "onStop()");
    }
}

运行效果如下图所示:

 

3 分布式任务调度

3.1 鸿蒙系统的分布式能力

3.1.1 FA的分布式能力

3.1.1.1 打开和关闭远程设备上的FA

打开和关闭远程设备上的FA与操作本地FA的方法基本相同,需要为Intent对象设置参数,并分别通过startAbility()和stopAbility()方法打开和关闭FA。但是,操作远程设备上的FA需要额外注意以下三点:

Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId(<远程设备的UDID>)
    .withBundleName(<包名>)
    .withAbilityName(<FA名>)
    .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
    .build();
intent.setOperation(operation);
startAbility(intent);
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
    .withDeviceId(<远程设备的UDID>)
    .withBundleName(<包名>)
    .withAbilityName(<FA名>)
    .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
    .build();
intent.setOperation(operation);
stopAbility(intent);
3.1.1.2 应用迁移

应用迁移无需借助Intent对象,可以将设备中的某个FA直接迁移到另一个设备上运行,是应用程序流转的主要实现方法。

3.1.2 PA的分布式能力

3.1.2.1 远程调用ServiceAbility

与FA一样,调用远程设备上的ServiceAbility也需要Intent对象参与。不同的是,不仅可以通过startAbility()和stopAbility()方法打开和关闭远程ServiceAbility,还可以通过connectAbility()和disconnectAbility()方法连接和断开远程ServiceAbility。操作远程设备上的ServiceAbility需要额外注意以下三点:

3.1.2.2 远程调用DataAbility

调用远程设备上的DataAbility无需借助Intent对象,只要将远程设备的UDID加入访问DataAbility的URI中即可。操作远程设备上的DataAbility需要额外注意以下两点:

3.1.3 流转与协同

无论流转还是协同,都需要目标设备(设备2)上装有能够处理特定数据的适当应用。如果该设备上没有该应用,则需要从应用商店下载。

因此运行在目标设备上的,参与流转或协同的应用,其字节数最好不要超过10M,以提供更加流畅的用户体验。

3.2 分布式组网与远程设备信息获取

3.2.1 分布式组网的条件

能够实现分布式组网的设备必须满足以下三个条件:

  1. 均安装鸿蒙操作系统
  2. 在同一网络内且登录同一华为账号
             华为账号
   _____________|_____________
  /             |             \
手表 <-蓝牙-> 手机 <-WIFI-> 智慧屏
  \_____________|_____________/
                |
     基于分布式软总线多设备组网
  1. 打开多设备协同选项
    设置
      更多连接
        多设备协同
          多设备协同:On

3.2.2 获取在线设备信息

List<DeviceInfo> devices = DeviceManager.getDeviceList(
    DeviceInfo.FLAG_GET_ONLINE_DEVICE);
for (DeviceInfo device : devices) {
    HiLog.info(label, "Device ID    : " + device.getDeviceID());
    HiLog.info(label, "Device Name  : " + device.getDeviceName());
    HiLog.info(label, "Device Type  : " + device.getDeviceType());
    HiLog.info(label, "Device State : " + device.getDeviceState());
}

能够获取远程设备信息的应用程序,必须具备GET_DISTRIBUTED_DEVICE_INFO权限。该权限为非敏感权限,在config.json文件中配置即可。

3.3 应用迁移

运行在源设备中应用程序,通过Ability或AbilitySlice上下文对象的如下方法,即可实现应用迁移:

可被迁移FA的Ability和AbilitySlice需要实现IAbilityContinuation接口。

sequenceDiagram participant usr as 用户 participant src as 源设备 participant dst as 目标设备 autonumber usr->>+src: Ability.continueAbility() src->>src: IAbilityContinuation.onStartContinuation() src->>src: IAbilityContinuation.onSaveData() src->>+dst: 分布式数据管理 dst->>dst: IAbilityContinuation.onRestoreData() dst->>dst: Ability.onStart() dst->>-src: 分布式数据管理 src->>src: IAbilityContinuation.onCompleteContinuation() src->>-usr: Ability.terminateAbility()

例程:ContinueAbility

...\ContinueAbility\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "reqPermissions": [
      {
        "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      }
    ]
  }
}

...\ContinueAbility\entry\src\main\resources\base\graphic\background_button_continue.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#00a2e8"/>
</shape>

...\ContinueAbility\entry\src\main\resources\base\layout\ability_main.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:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="60fp"
        ohos:text_color="#ff7f27"
        />

    <Button
        ohos:id="$+id:btn"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="$ohos:float:button_radius"
        ohos:top_margin="20vp"
        ohos:background_element="$graphic:background_button_continue"
        ohos:text="迁移"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\ContinueAbility\entry\src\main\java\com\minwei\continueability\MainAbility.java

public class MainAbility extends Ability
    implements 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) {
    }
}

...\ContinueAbility\entry\src\main\java\com\minwei\continueability\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice
    implements IAbilityContinuation {
    private int data = 1;

    @Override
    public void onStart(Intent intent) {
        ...
        ((Text)findComponentById(ResourceTable.Id_txt))
            .setText(String.valueOf(data));

        requestPermissions();

        findComponentById(ResourceTable.Id_btn)
            .setClickedListener(component -> {
                List<String> deviceIds = getDeviceIds();
                if (!deviceIds.isEmpty())
                    continueAbility(deviceIds.get(0));
            });
    }
    ...
    @Override
    public boolean onStartContinuation() {
        return true;
    }

    @Override
    public boolean onSaveData(IntentParams intentParams) {
        intentParams.setParam("data", data);
        return true;
    }

    @Override
    public boolean onRestoreData(IntentParams intentParams) {
        data = (int)intentParams.getParam("data") + 1;
        return true;
    }

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

    private void requestPermissions() {
        List<String> reqPermissions = new ArrayList<>();

        String[] permissions = {
            "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
            "ohos.permission.DISTRIBUTED_DATASYNC"};
        for (String permission : permissions)
            if (verifySelfPermission(permission) != 0 &&
                canRequestPermission(permission))
                reqPermissions.add(permission);

        requestPermissionsFromUser(
            reqPermissions.toArray(new String[0]), 0);
    }

    private List<String> getDeviceIds() {
        List<String> deviceIds = new ArrayList<>();

        List<DeviceInfo> devices = DeviceManager.getDeviceList(
            DeviceInfo.FLAG_GET_ONLINE_DEVICE);
        for (DeviceInfo device : devices)
            deviceIds.add(device.getDeviceId());

        return deviceIds;
    }
}

运行效果如下图所示:

更多精彩,敬请期待……


达内集团C++教学部 2021年9月23日