鸿蒙系统深度解读(二)

《鸿蒙系统深度解读》系列文章中,我们将针对鸿蒙系统分享基础知识、应用实例、前端JS/eTS部分、鸿蒙系统带给我们的思考、我们如何做这几部分内容,本篇文章介绍 应用实例(Java)

1 应用流转迁移

「 项目 」

harmonyos-tutorial\samples\ContinueRemoteFACollaboration

「 效果 」

图1

图2

如图,点击图1中 “迁移” 按钮,会打开 pad 设备应用,同时将数据迁移显示到 pad 对应 UI 处。

图2中可以继续编辑内容,点击 “迁移” 会再次迁移到图1设备中继续显示。

「 重点代码 」

/**
 * 设备迁移与回迁
 */
public class MainAbilitySlice extends AbilitySlice implements IAbilityContinuation {
    private static final String TAG = MainAbilitySlice.class.getSimpleName();
    private static final HiLogLabel LABEL_LOG =  new HiLogLabel(HiLog.LOG_APP, 0x00001, TAG);

    private static final String MESSAGE_KEY =   "com.waylau.hmos.continueremotefacollaboration.slice.MESSAGE_KEY";

    private String message;

    private boolean isContinued;

    private TextField messageTextField;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        HiLog.info(LABEL_LOG, "onStart");
        // 监听跨端迁移FA的事件
        Button buttonContinueRemoteFA = (
                Button) findComponentById(ResourceTable.Id_button_continue_remote_fa);
        buttonContinueRemoteFA.setClickedListener(listener -> continueRemoteFA());

        // 设置输入框内容
        messageTextField = (TextField) findComponentById(ResourceTable.Id_message_textfield);
        if (isContinued && message != null) {
            messageTextField.setText(message);
        }
    }

    private void continueRemoteFA() {
        HiLog.info(LABEL_LOG, "before startRemoteFA");

        String deviceId = DeviceUtils.getDeviceId();

        HiLog.info(LABEL_LOG, "get deviceId: %{public}s", deviceId);

        if (deviceId != null) {
            // 发起迁移流程
            //  continueAbility()是不可回迁的
            //  continueAbilityReversibly() 是可以回迁的
            continueAbility(deviceId);
        }
    }

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

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    @Override
    public boolean onStartContinuation() {
        HiLog.info(LABEL_LOG, "onStartContinuation");
        // 重写
        return true;
    }

    @Override
    public boolean onSaveData(IntentParams intentParams) {
        HiLog.info(LABEL_LOG, "onSaveData");
        // 重写
        // 保存回迁后恢复状态必须的数据
        intentParams.setParam(MESSAGE_KEY, messageTextField.getText());
        return true;
    }

    @Override
    public boolean onRestoreData(IntentParams intentParams) {
        HiLog.info(LABEL_LOG, "onRestoreData");
        // 重写
        // 传递此前保存的数据
        if (intentParams.getParam(MESSAGE_KEY) instanceof String) {
            message = (String) intentParams.getParam(MESSAGE_KEY);
            isContinued = true;
        }
        return true;
    }

    @Override
    public void onCompleteContinuation(int i) {
        HiLog.info(LABEL_LOG, "onCompleteContinuation");
        // 终止
        terminate();
    }

}

「 原理 」

  1. 实现

IAbilityContinuation

接口,重写 onSaveData、onRestoreData、onCompleteContinuation 等方法。其中,onSaveData 用于保存迁移传递必须的数据;onRestoreData 恢复获取传递此前保存的数据。onCompleteContinuation 方法用于终止 Page。

  1. 点击事件中获取组网设备ID,DeviceUtils.getDeviceId() 。

  1. 调用迁移 API,continueAbility (deviceId) 后会触发 IAbilityContinuation 相关回调方法。

  1. 应用层不需要处理联网协议,底层基于软总线 COAP 协议完成数据传递,实现数据迁移。

「 特点 」

应用层不需要处理组网、联网等复杂过程降低开发难度,简单 API 调用就可以完成近场设备间数据 UI 迁移。

「 应用场景 」

  • 在外时手机上编辑邮件,到家后迁移到平板上继续编辑

  • 在外时手机玩游戏,到家后迁移到平板上继续玩

  • 在家里智慧屏上看视频,出门时迁移到手机上继续观看

  • 手机视频通话迁移到智慧屏,更沉浸地视频聊天

  • 听歌,看电影,玩游戏,应用流转数据迁移至至电视继续播放,玩游戏

2 分布式数据库

「 项目 」

codelabs\DistributeDatabaseDraw

「 效果 」

如图,在一组互联的设备中,在其中一个设备上写字画画会实时同步至其他设备,不是投屏。

「 重点代码 」

MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final String TAG = MainAbilitySlice.class.getName();
    private static final int PERMISSION_CODE = 20201203;
    private static final int DELAY_TIME = 10;
    private static final String STORE_ID_KEY = "storeId";
    private static final String POINTS_KEY = "points";
    private static final String COLOR_INDEX_KEY = "colorIndex";
    private static final String IS_FORM_LOCAL_KEY = "isFormLocal";
    private static String storeId;
    private DependentLayout canvas;
    private Image transform;
    private KvManager kvManager;
    private SingleKvStore singleKvStore;
    private Text title;
    private DrawPoint drawl;
    private Button back;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        storeId = STORE_ID_KEY + System.currentTimeMillis();
        findComponentById();
        requestPermission();
        initView(intent);
        initDatabase();
        initDraw(intent);
    }

    private void initView(Intent intent) {
        boolean isLocal = !intent.getBooleanParam(IS_FORM_LOCAL_KEY, false);
        if (!isLocal) {
            storeId = intent.getStringParam(STORE_ID_KEY);
        }

        title.setText(isLocal ? "本地端" : "远程端");
        transform.setVisibility(isLocal ? Component.VISIBLE : Component.INVISIBLE);
    }

    private void requestPermission() {
        if (verifySelfPermission(DISTRIBUTED_DATASYNC) != IBundleManager.PERMISSION_GRANTED) {
            if (canRequestPermission(DISTRIBUTED_DATASYNC)) {
                requestPermissionsFromUser(new String[]{DISTRIBUTED_DATASYNC}, PERMISSION_CODE);
            }
        }
    }

    private void findComponentById() {
        if (findComponentById(ResourceTable.Id_canvas) instanceof DependentLayout) {
            canvas = (DependentLayout) findComponentById(ResourceTable.Id_canvas);
        }
        if (findComponentById(ResourceTable.Id_transform) instanceof Image) {
            transform = (Image) findComponentById(ResourceTable.Id_transform);
        }
        if (findComponentById(ResourceTable.Id_title) instanceof Text) {
            title = (Text) findComponentById(ResourceTable.Id_title);
        }
        if (findComponentById(ResourceTable.Id_back) instanceof Button) {
            back = (Button) findComponentById(ResourceTable.Id_back);
        }
        transform.setClickedListener(component -> {
            DeviceSelectDialog dialog = new DeviceSelectDialog(MainAbilitySlice.this);
            dialog.setListener(deviceIds -> {
                if (deviceIds != null && !deviceIds.isEmpty()) {
                    // 启动远程页面
                    startRemoteFas(deviceIds);
                    // 同步远程数据库
                    singleKvStore.sync(deviceIds, SyncMode.PUSH_ONLY);
                }
                dialog.hide();
            });
            dialog.show();
        });
    }

    /**
     * Initialize art boards
     *
     * @param intent Intent
     */
    private void initDraw(Intent intent) {
        int colorIndex = intent.getIntParam(COLOR_INDEX_KEY, 0);
        drawl = new DrawPoint(this, colorIndex);
        drawl.setWidth(MATCH_PARENT);
        drawl.setWidth(MATCH_PARENT);
        canvas.addComponent(drawl);

        drawPoints();

        drawl.setOnDrawBack(points -> {
            if (points != null && points.size() > 1) {
                String pointsString = GsonUtil.objectToString(points);
                LogUtils.info(TAG, "pointsString::" + pointsString);
                if (singleKvStore != null) {
                    singleKvStore.putString(POINTS_KEY, pointsString);
                }
            }
        });
        back.setClickedListener(component -> {
            List<MyPoint> points = drawl.getPoints();
            if (points == null || points.size() <= 1) {
                return;
            }
            points.remove(points.size() - 1);
            for (int i = points.size() - 1; i >= 0; i--) {
                if (points.get(i).isLastPoint()) {
                    break;
                }
                points.remove(i);
            }
            drawl.setDrawParams(points);
            String pointsString = GsonUtil.objectToString(points);
            if (singleKvStore != null) {
                singleKvStore.putString(POINTS_KEY, pointsString);
            }
        });
    }

    // 获取数据库中的点数据,并在画布上画出来
    private void drawPoints() {
        List<Entry> points = singleKvStore.getEntries(POINTS_KEY);
        for (Entry entry : points) {
            if (entry.getKey().equals(POINTS_KEY)) {
                List<MyPoint> remotePoints = GsonUtil.jsonToList(singleKvStore.getString(POINTS_KEY), MyPoint.class);
                getUITaskDispatcher().delayDispatch(() -> drawl.setDrawParams(remotePoints), DELAY_TIME);
            }
        }
    }

    /**
     * Receive database messages
     *
     * @since 2021-04-06
     */
    private class KvStoreObserverClient implements KvStoreObserver {
        @Override
        public void onChange(ChangeNotification notification) {
            LogUtils.info(TAG, "data changed......");
            drawPoints();
        }
    }

    private void initDatabase() {
        // 创建分布式数据库管理对象
        KvManagerConfig config = new KvManagerConfig(this);
        kvManager = KvManagerFactory.getInstance().createKvManager(config);
        // 创建分布式数据库
        Options options = new Options();
        options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
        singleKvStore = kvManager.getKvStore(options, storeId);
        // 订阅分布式数据变化
        KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
        singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
    }

    /**
     * Starting Multiple Remote Fas
     *
     * @param deviceIds deviceIds
     */
    private void startRemoteFas(List<String> deviceIds) {
        Intent[] intents = new Intent[deviceIds.size()];
        for (int i = 0; i < deviceIds.size(); i++) {
            Intent intent = new Intent();
            intent.setParam(IS_FORM_LOCAL_KEY, true);
            intent.setParam(COLOR_INDEX_KEY, i + 1);
            intent.setParam(STORE_ID_KEY, storeId);
            Operation operation = new Intent.OperationBuilder()
                    .withDeviceId(deviceIds.get(i))
                    .withBundleName(getBundleName())
                    .withAbilityName(MainAbility.class.getName())
                    .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
                    .build();
            intent.setOperation(operation);
            intents[i] = intent;
        }
        startAbilities(intents);
    }

    @Override
    protected void onStop() {
        super.onStop();
        kvManager.closeKvStore(singleKvStore);
    }
}

DrawPoint.java

public class DrawPoint extends Component implements Component.DrawTask {
    private static final int STROKE_WIDTH = 15;
    private static final int TIME = 200;
    private static final int EVENT_MSG_STORE = 0x1000002;
    private final Color[] paintColors = new Color[]{Color.RED, Color.BLUE, Color.BLACK};
    private List<MyPoint> points = new ArrayList<>(0);
    private Paint paint;
    private OnDrawCallBack callBack;
    private Timer timer = null;
    private TimerTask timerTask = null;

    private final EventHandler handler = new EventHandler(EventRunner.current()) {
        @Override
        protected void processEvent(InnerEvent event) {
            if (EVENT_MSG_STORE == event.eventId) {
                callBack.callBack(points);
            }
        }
    };

    /**
     * Drawl constructor
     *
     * @param context context
     * @param colorIndex colorIndex
     */
    public DrawPoint(Context context, int colorIndex) {
        super(context);
        init(colorIndex);
    }

    public List<MyPoint> getPoints() {
        return points;
    }

    /**
     * SelectResult
     *
     * @since 2020-12-03
     */
    public interface OnDrawCallBack {
        /**
         * touchListener
         *
         * @param points points
         */
        void callBack(List<MyPoint> points);
    }

    /**
     * setPoints
     *
     * @param myPoints myPoints
     */
    public void setDrawParams(List<MyPoint> myPoints) {
        this.points = myPoints;
        invalidate();
    }

    /**
     * setOnDrawBack
     *
     * @param onCallBack onCallBack
     */
    public void setOnDrawBack(OnDrawCallBack onCallBack) {
        this.callBack = onCallBack;
    }

    private void init(int colorIndex) {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE_STYLE);
        paint.setStrokeWidth(STROKE_WIDTH);
        addDrawTask(this);
        Color color = getRandomColor(colorIndex);

        setTouchEventListener((component, touchEvent) -> {
            scheduledTask();
            int crtX = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
            int crtY = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getY();

            MyPoint point = new MyPoint(crtX, crtY);
            point.setPaintColor(color);

            switch (touchEvent.getAction()) {
                case TouchEvent.POINT_MOVE:
                    points.add(point);
                    break;
                case TouchEvent.PRIMARY_POINT_UP:
                    points.add(point);
                    point.setLastPoint(true);
                    callBack.callBack(points);
                    onTimerFinish();
                    break;
                default:
                    break;
            }
            invalidate();
            return true;
        });
    }

    /**
     * scheduled task start
     */
    public void scheduledTask() {
        if (timer == null && timerTask == null) {
            timer = new Timer();
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    handler.sendEvent(EVENT_MSG_STORE);
                }
            };
            timer.schedule(timerTask, 0, TIME);
        }
    }

    /**
     * Canceling a Scheduled Task
     */
    public void onTimerFinish() {
        timer.cancel();
        timer = null;
        timerTask = null;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        draw(points, canvas);
    }

    private void draw(List<MyPoint> myPoints, Canvas canvas) {
        if (myPoints == null || myPoints.size() <= 1) {
            return;
        }
        Point first = null;
        Point last = null;
        for (MyPoint myPoint : myPoints) {
            paint.setColor(myPoint.getPaintColor());
            float finalX = myPoint.getPositionX();
            float finalY = myPoint.getPositionY();
            Point finalPoint = new Point(finalX, finalY);
            if (myPoint.isLastPoint()) {
                first = null;
                last = null;
                continue;
            }
            if (first == null) {
                first = finalPoint;
            } else {
                if (last != null) {
                    first = last;
                }
                last = finalPoint;
                canvas.drawLine(first, last, paint);
            }
        }
    }

    private Color getRandomColor(int index) {
        return index > paintColors.length - 1 ? paintColors[0] : paintColors[index];
    }
}

「 原理 」

  1. 初始化分布式数据库 initDatabase(),订阅消息监听

  1. 点击共享后,获取设备列表,启动远程页面 startRemoteFas(deviceIds)

  1. 绘制过程中在 DrowPoint 中 TouchEventListener 收集坐标点信息

  1. 每过200ms触发一次任务回调中在 drawl.setOnDrawBack 回调中将绘图轨迹坐标写入分布式数据库 singleKvStore.putString(POINTS_KEY, pointsString)

  1. 其他设备,在消息监听订阅回调 KvStoreObserver 回调函数 onchange 中,获取分布式数据库中数据并完成绘制

  1. 分布式数据库底层通过最终一致性方案,完成数据同步传递

「 特点 」

多设备实时协同操作(不是投屏)。

「 应用场景 」

师生课堂互动。

3 分布式任务调度

「 项目 」

codelabs\RemoteInputDemo

「 效果 」

图3 视频播放器

图4 手机输入法

如图,图3为电视视频筛选页面,通过图4的手机输入法输入文本同步到图3视频搜索输入框。

「 重点代码 」

MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice implements PermissionBridge.OnPermissionStateListener {
    private static final String ABILITY_NAME = "com.huawei.codelab.RemoteInputAbility";
    private static final String MOVIE_PLAY_ABILITY = "com.huawei.codelab.MoviePlayAbility";
    private static final String MAIN_ABILITY = "com.huawei.codelab.MainAbility";
    private static final int FOCUS_PADDING = 8;
    private static final int SEARCH_PADDING = 3;
    private static final int LIST_INIT_SIZE = 16;
    private static final String TAG = MainAbilitySlice.class.getName();
    private static final Lock MOVE_LOCK = new ReentrantLock();
    private MainAbilitySlice.MyCommonEventSubscriber subscriber;
    private TextField tvTextInput;
    private ScrollView scrollView;
    private DirectionalLayout keyBoardLayout;
    private TableLayout movieTableLayout;
    private Size size;
    private final AbilityMgr abilityMgr = new AbilityMgr(this);

    // 搜索的到的影片
    private final List<ComponentPointData> movieSearchList = new ArrayList<>(LIST_INIT_SIZE);

    // 当前焦点所在位置
    private ComponentPointData componentPointDataNow;

    // 注册流转任务管理服务后返回的Ability token
    private int abilityToken;

    // 用户在设备列表中选择设备后返回的设备ID
    private String selectDeviceId;

    // 获取流转任务管理服务管理类
    private IContinuationRegisterManager continuationRegisterManager;

    // 设置监听FA流转管理服务设备状态变更的回调
    private final IContinuationDeviceCallback callback = new IContinuationDeviceCallback() {
        @Override
        public void onDeviceConnectDone(String deviceId, String s1) {
            selectDeviceId = deviceId;
            abilityMgr.openRemoteAbility(selectDeviceId, getBundleName(), ABILITY_NAME);
            getUITaskDispatcher().asyncDispatch(() -> continuationRegisterManager.updateConnectStatus(abilityToken, selectDeviceId,
                    DeviceConnectState.IDLE.getState(), null));
        }

        @Override
        public void onDeviceDisconnectDone(String deviceId) {
        }
    };

    // 设置注册FA流转管理服务服务回调
    private final RequestCallback requestCallback = new RequestCallback() {
        @Override
        public void onResult(int result) {
            abilityToken = result;
        }
    };

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);

        new PermissionBridge().setOnPermissionStateListener(this);

        // 全屏设置
        WindowManagerUtils.setWindows();

        // 初始化布局
        initComponent();

        // 初始化按钮组件
        initKeyBoardComponent(keyBoardLayout);

        // 初始化影片图片组件
        initMovieTableComponent(movieTableLayout);

        // 事件订阅
        subscribe();
    }

    private void registerTransService() {
        continuationRegisterManager = getContinuationRegisterManager();

        // 增加过滤条件
        ExtraParams params = new ExtraParams();
        String[] devTypes = new String[]{ExtraParams.DEVICETYPE_SMART_PAD, ExtraParams.DEVICETYPE_SMART_PHONE};
        params.setDevType(devTypes);

        // 注册FA流转管理服务
        continuationRegisterManager.register(getBundleName(), params, callback, requestCallback);
    }

    private void initMovieTableComponent(TableLayout tableLayout) {
        int index = 0;
        while (index < tableLayout.getChildCount()) {
            DirectionalLayout childLayout = null;
            if (tableLayout.getComponentAt(index) instanceof DirectionalLayout) {
                childLayout = (DirectionalLayout) tableLayout.getComponentAt(index);
            }
            ComponentPointData componentPointData = new ComponentPointData();
            Component component = null;
            int indexMovie = 0;
            while (childLayout != null && indexMovie < childLayout.getChildCount()) {
                Component comChild = childLayout.getComponentAt(indexMovie);
                indexMovie++;
                if (comChild instanceof Text) {
                    componentPointData = ComponentPointDataMgr
                            .getConstantMovie(((Text) comChild).getText()).orElse(null);
                    continue;
                }
                component = findComponentById(comChild.getId());
            }

            if (componentPointData != null && component != null) {
                componentPointData.setComponentId(component.getId());
            }
            ComponentPointDataMgr.getComponentPointDataMgrs().add(componentPointData);
            index++;
        }
    }

    private void initKeyBoardComponent(DirectionalLayout directionalLayout) {
        int index = 0;
        while (index < directionalLayout.getChildCount()) {
            DirectionalLayout childLayout = null;
            if (directionalLayout.getComponentAt(index) instanceof DirectionalLayout) {
                childLayout = (DirectionalLayout) directionalLayout.getComponentAt(index);
            }
            int indexButton = 0;
            while (childLayout != null && indexButton < childLayout.getChildCount()) {
                if (childLayout.getComponentAt(indexButton) instanceof Button) {
                    Button button = (Button) childLayout.getComponentAt(indexButton);
                    buttonInit(button);
                    indexButton++;
                }
            }
            index++;
        }
    }

    private void initComponent() {
        if (findComponentById(ResourceTable.Id_scrollview) instanceof ScrollView) {
            scrollView = (ScrollView) findComponentById(ResourceTable.Id_scrollview);
        }

        if (findComponentById(ResourceTable.Id_TV_input) instanceof TextField) {
            tvTextInput = (TextField) findComponentById(ResourceTable.Id_TV_input);

            // 初始化默认选中效果
            ComponentPointData componentPointData = new ComponentPointData();
            componentPointData.setComponentId(tvTextInput.getId());
            componentPointData.setPointX(0);
            componentPointData.setPointY(1);
            tvTextInput.requestFocus();
            componentPointDataNow = componentPointData;
            ComponentPointDataMgr.getComponentPointDataMgrs().add(componentPointDataNow);

            // 点击事件触发设备选取弹框
            tvTextInput.setClickedListener(component -> showDevicesDialog());
        }

        if (findComponentById(ResourceTable.Id_keyBoardComponent) instanceof DirectionalLayout) {
            keyBoardLayout = (DirectionalLayout) findComponentById(ResourceTable.Id_keyBoardComponent);
        }

        if (findComponentById(ResourceTable.Id_tableLayout) instanceof TableLayout) {
            movieTableLayout = (TableLayout) findComponentById(ResourceTable.Id_tableLayout);
        }

        if (findComponentById(ResourceTable.Id_image_ten) instanceof Image) {
            Image image = (Image) findComponentById(ResourceTable.Id_image_ten);
            size = image.getPixelMap().getImageInfo().size;
        }
    }

    private void showDevicesDialog() {
        ExtraParams extraParams = new ExtraParams();
        extraParams.setDevType(new String[]{ExtraParams.DEVICETYPE_SMART_TV,
            ExtraParams.DEVICETYPE_SMART_PHONE});
        extraParams.setDescription("远程遥控器");
        continuationRegisterManager.showDeviceList(abilityToken, extraParams, result -> LogUtils.info(TAG, "show devices success"));
    }

    private void buttonInit(Button button) {
        if (button.getId() == ResourceTable.Id_del) {
            findComponentById(button.getId()).setClickedListener(component -> {
                if (tvTextInput.getText().length() > 0) {
                    tvTextInput.setText(tvTextInput.getText().substring(0, tvTextInput.getText().length() - 1));
                }
            });
        } else if (button.getId() == ResourceTable.Id_clear) {
            findComponentById(button.getId()).setClickedListener(component -> tvTextInput.setText(""));
        } else {
            findComponentById(button.getId()).setClickedListener(component -> tvTextInput.append(button.getText()));
        }
    }

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

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    @Override
    public void onPermissionGranted() {
        registerTransService();
    }

    @Override
    public void onPermissionDenied() {
        terminate();
    }

    /**
     * MyCommonEventSubscriber
     *
     * @since 2020-12-03
     */
    class MyCommonEventSubscriber extends CommonEventSubscriber {
        MyCommonEventSubscriber(CommonEventSubscribeInfo info) {
            super(info);
        }

        @Override
        public void onReceiveEvent(CommonEventData commonEventData) {
            Intent intent = commonEventData.getIntent();
            int requestType = intent.getIntParam("requestType", 0);
            String inputString = intent.getStringParam("inputString");
            if (requestType == ConnectManagerIml.REQUEST_SEND_DATA) {
                tvTextInput.setText(inputString);
            } else if (requestType == ConnectManagerIml.REQUEST_SEND_SEARCH) {
                // 如果当前选中的是文本框则搜索影片;如果当前选中的是影片则播放影片;X轴坐标为0当前选中文本框;X轴大于1当前选中影片
                if (componentPointDataNow.getPointX() == 0) {
                    // 调用大屏的搜索方法
                    searchMovies(tvTextInput.getText());
                    return;
                }

                // 播放影片
                abilityMgr.playMovie(getBundleName(), MOVIE_PLAY_ABILITY);
            } else {
                // 移动方向
                String moveString = intent.getStringParam("move");
                MainCallBack.movePoint(MainAbilitySlice.this, moveString);
            }
        }
    }

    /**
     * goBack
     */
    public void goBack() {
        clearLastBackg();
        scrollView.scrollTo(0, 0);
        componentPointDataNow = ComponentPointDataMgr.getMoviePoint(0, 1).orElse(null);
        findComponentById(ResourceTable.Id_TV_input).requestFocus();
        abilityMgr.returnMainAbility(getBundleName(), MAIN_ABILITY);
    }

    /**
     * move
     *
     * @param pointX pointX
     * @param pointY pointY
     */
    public void move(int pointX, int pointY) {
        MOVE_LOCK.lock();
        try {
            // 设置焦点滚动
            if (pointX == 0 && componentPointDataNow.getPointX() > 0) {
                scrollView.fluentScrollByY(pointY * size.height);
            }
            if (componentPointDataNow.getPointX() == 0 && pointX == 1) {
                scrollView.scrollTo(0, 0);
            }

            // 设置背景
            if (componentPointDataNow.getPointX() + pointX == 0) {
                setBackGround(componentPointDataNow.getPointX() + pointX, 1);
            } else {
                setBackGround(componentPointDataNow.getPointX() + pointX, componentPointDataNow.getPointY() + pointY);
            }
        } finally {
            MOVE_LOCK.unlock();
        }
    }

    private void setBackGround(int pointX, int pointY) {
        ComponentPointData componentPointDataNew = ComponentPointDataMgr.getMoviePoint(pointX, pointY).orElse(null);
        if (componentPointDataNew == null) {
            return;
        }

        // 清除上次选中的效果
        clearLastBackg();
        componentPointDataNow = componentPointDataNew;
        if (findComponentById(componentPointDataNow.getComponentId()) instanceof Image) {
            Image newImage = (Image) findComponentById(componentPointDataNow.getComponentId());
            newImage.setPadding(FOCUS_PADDING, FOCUS_PADDING, FOCUS_PADDING, FOCUS_PADDING);
        } else {
            Component component = findComponentById(componentPointDataNow.getComponentId());
            component.requestFocus();
        }
    }

    private void clearLastBackg() {
        Component component = null;
        if (findComponentById(componentPointDataNow.getComponentId()) instanceof TextField) {
            TextField textField = (TextField) findComponentById(componentPointDataNow.getComponentId());
            textField.clearFocus();
        } else {
            component = findComponentById(componentPointDataNow.getComponentId());
            component.setPadding(0, 0, 0, 0);
        }

        // 如果是搜索出来的还是保持搜索到的背景
        for (ComponentPointData componentPointData : movieSearchList) {
            if (component != null && componentPointData.getComponentId() == component.getId()) {
                component.setPadding(SEARCH_PADDING, SEARCH_PADDING, SEARCH_PADDING, SEARCH_PADDING);
            }
        }
    }

    private void searchMovies(String text) {
        if (text == null || "".equals(text)) {
            return;
        }

        // 清空上次搜索结果及背景效果
        clearHistroyBackGround();

        for (ComponentPointData componentPointData : ComponentPointDataMgr.getComponentPointDataMgrs()) {
            if (MovieSearchUtils.isContainMovie(componentPointData.getMovieName(), text)
                    || MovieSearchUtils.isContainMovie(componentPointData.getMovieFirstName(), text)) {
                movieSearchList.add(componentPointData);
                Component component = findComponentById(componentPointData.getComponentId());
                component.setPadding(SEARCH_PADDING, SEARCH_PADDING, SEARCH_PADDING, SEARCH_PADDING);
            }
        }

        if (movieSearchList.size() > 0) {
            componentPointDataNow = movieSearchList.get(0);
            Component component = findComponentById(componentPointDataNow.getComponentId());
            component.setPadding(FOCUS_PADDING, FOCUS_PADDING, FOCUS_PADDING, FOCUS_PADDING);
        } else {
            Component component = findComponentById(componentPointDataNow.getComponentId());
            component.requestFocus();
        }
    }

    private void clearHistroyBackGround() {
        // 清空上次搜索的结果
        for (ComponentPointData componentPointData : movieSearchList) {
            Component component = findComponentById(componentPointData.getComponentId());
            component.setPadding(0, 0, 0, 0);
        }
        movieSearchList.clear();

        // 去掉当前焦点背景
        clearLastBackg();
    }

    private void subscribe() {
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent(EventConstants.SCREEN_REMOTE_CONTROLL_EVENT);
        matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
        subscriber = new MainAbilitySlice.MyCommonEventSubscriber(subscribeInfo);
        try {
            CommonEventManager.subscribeCommonEvent(subscriber);
        } catch (RemoteException e) {
            LogUtils.error("", "subscribeCommonEvent occur exception.");
        }
    }

    private void unSubscribe() {
        try {
            CommonEventManager.unsubscribeCommonEvent(subscriber);
        } catch (RemoteException e) {
            LogUtils.error(TAG, "unSubscribe Exception");
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        unSubscribe();
    }
}

RemoteInputAbilitySlice.java

public class RemoteInputAbilitySlice extends AbilitySlice {
    private static final int SHOW_KEYBOARD_DELAY = 800;
    private static final int INIT_SIZE = 8;

    private ConnectManager connectManager;

    private TextField textField;

    private String deviceIdConn;

    private Component okButton;

    private Component leftButton;

    private Component rightButton;

    private Component upButton;

    private Component downButton;

    private Component goBackButton;

    private Component closeButton;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_remote_input);

        // 获取大屏ID用于配对
        deviceIdConn = intent.getStringParam("localDeviceId");

        // 全屏设置
        WindowManagerUtils.setWindows();
        initView();
        initListener();
        showKeyBoard();
        initConnManager();
    }

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

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    private void initView() {
        if (findComponentById(ResourceTable.Id_remote_input) instanceof TextField) {
            textField = (TextField) findComponentById(ResourceTable.Id_remote_input);
            textField.requestFocus();
        }
        okButton = findComponentById(ResourceTable.Id_ok_button);
        leftButton = findComponentById(ResourceTable.Id_left_button);
        rightButton = findComponentById(ResourceTable.Id_right_button);
        upButton = findComponentById(ResourceTable.Id_up_button);
        downButton = findComponentById(ResourceTable.Id_down_button);
        goBackButton = findComponentById(ResourceTable.Id_go_back);
        closeButton = findComponentById(ResourceTable.Id_close_fa);
    }

    private void initListener() {
        // 监听文本变化,远程同步
        textField.addTextObserver((ss, ii, i1, i2) -> {
            Map<String, String> map = new HashMap<>(INIT_SIZE);
            map.put("inputString", ss);
            connectManager.sendRequest(ConnectManagerIml.REQUEST_SEND_DATA, map);
        });
        okButton.setClickedListener(component -> {
            // 点击OK按钮
            buttonClickSound();
            String searchString = textField.getText();
            Map<String, String> map = new HashMap<>(INIT_SIZE);
            map.put("inputString", searchString);
            connectManager.sendRequest(ConnectManagerIml.REQUEST_SEND_SEARCH, map);
        });
        leftButton.setClickedListener(component -> {
            // 点击左键按钮
            sendMoveRequest(Constants.MOVE_LEFT);
        });
        rightButton.setClickedListener(component -> {
            // 点击右键按钮
            sendMoveRequest(Constants.MOVE_RIGHT);
        });
        upButton.setClickedListener(component -> {
            // 点击向上按钮
            sendMoveRequest(Constants.MOVE_UP);
        });
        downButton.setClickedListener(component -> {
            // 点击向下按钮
            sendMoveRequest(Constants.MOVE_DOWN);
        });
        goBackButton.setClickedListener(component -> {
            // 返回大屏主页
            sendMoveRequest(Constants.GO_BACK);
        });
        closeButton.setClickedListener(component -> {
            // 返回主页
            sendMoveRequest(Constants.GO_BACK);
            terminateAbility();
        });
    }

    private void sendMoveRequest(String direction) {
        buttonClickSound();
        Map<String, String> map = new HashMap<>(INIT_SIZE);
        map.put("move", direction);
        connectManager.sendRequest(ConnectManagerIml.REQUEST_SEND_MOVE, map);
    }

    private void showKeyBoard() {
        getUITaskDispatcher().delayDispatch(() -> textField.simulateClick(), SHOW_KEYBOARD_DELAY);
    }

    private void initConnManager() {
        connectManager = ConnectManagerIml.getInstance();
        connectManager.connectPa(this, deviceIdConn);
    }

    private void buttonClickSound() {
        // 步骤1:实例化对象
        SoundPlayer soundPlayer = new SoundPlayer("packageName");

        // 步骤2:播放键盘敲击音,音量为1.0
        soundPlayer.playSound(SoundPlayer.SoundType.KEY_CLICK, 1.0f);
    }
}

「 原理 」

  1. MainAbilitySlice#onStart 生命周期中会做事件注册,类似 Android 中注册广播接收 subscribe() 。

  1. 在事件接收者 onReceiveEvent 中处理来自手机输入法输入的数据/请求查询/播放等命令。如果是数据则显示在电视端文本输入框中,并搜索该影片。

  1. MainAbilitySlice#callback 当监听到设备组网连接成功后,打开 RemoteInputAbilitySlice 该页面就是手机端输入页面,在 initConnManager 中会打开 PA 类似 Android 中 service,在 IAbilityConnection 中回拿到远端电视端的引用。

IRemoteObject 接口,类似 Android 中拿到 Ibinder 接口。后续会用该接口给电视端发送数据。

  1. 手机端输入页面监听用户输入文本变化后会 connectManager.sendRequest(ConnectManagerIml.REQUEST_SEND_DATA, map),通过代理最终通过 IRemoteObject 接口给电视端发数据。后续到2电视 onReceiveEvent 接收到数据后处理对应命令。

  1. 按照华为官网描述该过程基于分布式任务调度和公共事件,将远端移动操作请求发送给TV端,类比 Android 通过 binder 机制完成跨进程 Service 通信,鸿蒙分布式调度底层通过极简D2D传输协议栈。

「 特点 」

跨设备分布式任务调度,硬件互助。

「 应用场景 」

冰箱通过手机输入法输入,家电使用手机相机作为输入等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值