Comunicación distribuida en HarmonyOS: implementación de RPC entre terminales

En un ecosistema donde móviles, tablets, televisores y wearables conviven, la colave entre ellos no puede depender de protocolos propietarios ni de pasos manuales complejos. HarmonyOS resuelve este desafío mediante su capa de RPC distribuido y el Soft-Bus, que permiten que cualquier aplicación invoque funciones o consuma datos alojados en otro dispositivo como si se tratara de una llamada local.

Casos de uso típicos

  • Smart Home: un teléfono detecta geolocalización y, al llegar a casa, enciende luces Philips, ajusta la temperatura del aire acondicionado Gree y cierra persianas Aqara, aunque cada fabricante haya compilado su propio hap.
  • Productividad: un portátil ejecuta un IDE pesado, pero la previsualización de la app se renderiza en la tablet del diseñador vía streaming de superficies gráficas.
  • Gaming: el mando es el smartphone; la GPU pesada corre en la consola; el audio sale por los auriculares del smartwatch. Los tres hablan por RPC sin latencia perceptible.

Activar el canal RPC + Soft-Bus

En config.json (o module.json a partir de API 12) basta con declarar:

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

El Soft-Bus se encarga de descubrimiento, emparejamiento y multiplexado de canales; el RPC driver gestiona la serialización binaria y la retransmisión ante pérdidas.

Identificando terminales con DeviceManager

Cada dispositivo posee un NetworkId único dentro del Super Device. El siguiente fragmento Java muestra cómo obtenerlo y suscribirse a eventos de red:

public final class DeviceRegistry {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "DeviceRegistry");
    private final DeviceManager mgr;

    public DeviceRegistry(Context ctx) throws DeviceManagerException {
        mgr = DeviceManager.getDeviceManager(ctx);
    }

    public String myNetworkId() throws DeviceManagerException {
        return mgr.getLocalDevice().getNetworkId();
    }

    public void listen(Consumer<RemoteDevice> onUp, Consumer<RemoteDevice> onDown) {
        DeviceManagerCallback callback = new DeviceManagerCallback() {
            @Override public void onDeviceOnline(RemoteDevice d)  { onUp.accept(d); }
            @Override public void onDeviceOffline(RemoteDevice d) { onDown.accept(d); }
        };
        try { mgr.registerDeviceCallback(callback); }
        catch (DeviceManagerException e) { HiLog.error(LABEL, "reg failed: %{public}s", e.getMessage()); }
    }
}

Definiendo un servicio remoto

Primero creamos la interfaz IRemoteBroker con AIDL simplificado:

public interface ISumService extends IRemoteBroker {
    int TRANSACTION_sum = IRemoteObject.FIRST_CALL_TRANSACTION;
    int sum(int x, int y) throws RemoteException;
}

El stub del lado servidor:

public class SumServiceStub extends RemoteObject implements ISumService {
    public SumServiceStub() { super("SumServiceStub"); }

    @Override public int sum(int x, int y) { return x + y; }

    @Override public boolean onRemoteRequest(int code, MessageParcel data,
                                             MessageParcel reply, MessageOption option) {
        if (code == TRANSACTION_sum) {
            int x = data.readInt();
            int y = data.readInt();
            reply.writeInt(sum(x, y));
            return true;
        }
        return super.onRemoteRequest(code, data, reply, option);
    }

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

Publicamos el servicio en el ServiceAbility:

@Override
protected IRemoteObject onConnect(Intent intent) {
    return new SumServiceStub();
}

Cliente que invoca la operación remota

public class SumClient {
    private final IRemoteObject remote;

    public SumClient(IRemoteObject remote) { this.remote = remote; }

    public int add(int a, int b) throws RemoteException {
        MessageParcel data = MessageParcel.obtain();
        MessageParcel reply = MessageParcel.obtain();
        MessageOption option = new MessageOption(MessageOption.TF_SYNC);
        try {
            data.writeInt(a);
            data.writeInt(b);
            remote.sendRequest(ISumService.TRANSACTION_sum, data, reply, option);
            return reply.readInt();
        } finally {
            data.reclaim();
            reply.reclaim();
        }
    }
}

Para obtener el IRemoteObject del dispositivo destino:

IRemoteObject proxy = DeviceManager.getDeviceManager(context)
                                   .getTrustedDeviceList()
                                   .stream()
                                   .filter(d -> d.getDeviceName().equals("MatePad"))
                                   .findFirst()
                                   .map(d -> connectAbility(intent, d.getNetworkId()))
                                   .orElseThrow(() -> new RemoteException("Device not found"));

Diagrama simplificado del flujo

Paso Acción
1 Cliente descubre dispositivo y obtiene NetworkId
2 Cliente invoca connectAbility() a través del Soft-Bus
3 HarmonyOS resuelve la dirección lógica y abre canal seguro
4 Cliente empaqueta MessageParcel y llama sendRequest()
5 Servidor ejecuta onRemoteRequest() y devuelve resultado
6 RPC driver entrega respuesta; cliente des-serializa y continúa

Con estos bloques se puede orquestar cualquier flujo multi-dispositivo: sincronizar bases de datos distribuidas, compartir lienzo gráfico, o delegar tareas pesadas al nodo más potente, todo sin que el usuario tenga que emparejar manualmente ni conocer direcciones IP.

Etiquetas: HarmonyOS RPC Soft-Bus DeviceManager NetworkId

Publicado el 8-20 18:23