赞
踩
赞
踩
《鸿蒙系统深度解读》系列文章中,我们将针对鸿蒙系统分享基础知识、应用实例、前端JS/eTS部分、鸿蒙系统带给我们的思考、我们如何做这几部分内容,本篇文章介绍 应用实例(Java)。
「 项目 」
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(); } }
「 原理 」
实现
IAbilityContinuation
接口,重写 onSaveData、onRestoreData、onCompleteContinuation 等方法。其中,onSaveData 用于保存迁移传递必须的数据;onRestoreData 恢复获取传递此前保存的数据。onCompleteContinuation 方法用于终止 Page。
点击事件中获取组网设备ID,DeviceUtils.getDeviceId() 。
调用迁移 API,continueAbility (deviceId) 后会触发 IAbilityContinuation 相关回调方法。
应用层不需要处理联网协议,底层基于软总线 COAP 协议完成数据传递,实现数据迁移。
「 特点 」
应用层不需要处理组网、联网等复杂过程降低开发难度,简单 API 调用就可以完成近场设备间数据 UI 迁移。
「 应用场景 」
在外时手机上编辑邮件,到家后迁移到平板上继续编辑
在外时手机玩游戏,到家后迁移到平板上继续玩
在家里智慧屏上看视频,出门时迁移到手机上继续观看
手机视频通话迁移到智慧屏,更沉浸地视频聊天
听歌,看电影,玩游戏,应用流转数据迁移至至电视继续播放,玩游戏
「 项目 」
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];
- }
- }

「 原理 」
初始化分布式数据库 initDatabase(),订阅消息监听
点击共享后,获取设备列表,启动远程页面 startRemoteFas(deviceIds)
绘制过程中在 DrowPoint 中 TouchEventListener 收集坐标点信息
每过200ms触发一次任务回调中在 drawl.setOnDrawBack 回调中将绘图轨迹坐标写入分布式数据库 singleKvStore.putString(POINTS_KEY, pointsString)
其他设备,在消息监听订阅回调 KvStoreObserver 回调函数 onchange 中,获取分布式数据库中数据并完成绘制
分布式数据库底层通过最终一致性方案,完成数据同步传递
「 特点 」
多设备实时协同操作(不是投屏)。
「 应用场景 」
师生课堂互动。
「 项目 」
「 效果 」
图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);
- }
- }

「 原理 」
MainAbilitySlice#onStart 生命周期中会做事件注册,类似 Android 中注册广播接收 subscribe() 。
在事件接收者 onReceiveEvent 中处理来自手机输入法输入的数据/请求查询/播放等命令。如果是数据则显示在电视端文本输入框中,并搜索该影片。
MainAbilitySlice#callback 当监听到设备组网连接成功后,打开 RemoteInputAbilitySlice 该页面就是手机端输入页面,在 initConnManager 中会打开 PA 类似 Android 中 service,在 IAbilityConnection 中回拿到远端电视端的引用。
IRemoteObject 接口,类似 Android 中拿到 Ibinder 接口。后续会用该接口给电视端发送数据。
手机端输入页面监听用户输入文本变化后会 connectManager.sendRequest(ConnectManagerIml.REQUEST_SEND_DATA, map),通过代理最终通过 IRemoteObject 接口给电视端发数据。后续到2电视 onReceiveEvent 接收到数据后处理对应命令。
按照华为官网描述该过程基于分布式任务调度和公共事件,将远端移动操作请求发送给TV端,类比 Android 通过 binder 机制完成跨进程 Service 通信,鸿蒙分布式调度底层通过极简D2D传输协议栈。
「 特点 」
跨设备分布式任务调度,硬件互助。
「 应用场景 」
冰箱通过手机输入法输入,家电使用手机相机作为输入等。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。