Caminho de Desenvolvimento do HarmonyOS: Comunicação Bidirecional – Chamando Capacidades Nativas do HarmonyOS do Flutter

【Flutter x HarmonyOS】Quarta Parte: Comunicação Bidirecional – Acesso a Funcionalidades Nativas do HarmonyOS

Após dominar a arquitetura e integração de UI entre Flutter e HarmonyOS, hoje exploramos o mecanismo de comunicação bidirecional, que é a tecnologia central para que aplicativos Flutter utilizem funcionalidades nativas do sistema HarmonyOS. Através de canais como MethodChannel, podemos integrar profundamente plataformas cruzadas, aproveitando ao máximo as características do HarmonyOS.

I. Canal de Plataforma: A Ponte de Comunicação entre Flutter e HarmonyOS

O Canal de Plataforma é um mecanismo de comunicação cross-platform fornecido oficialmente pelo Flutter, permitindo que código Dart interaja com plataformas nativas (Android/iOS/HarmonyOS) para chamadas de método e troca de dados. No ambiente HarmonyOS, este mecanismo também é aplicável e apresenta desempenho otimizado.

1.1 Tipos de Canais de Comunicação

O Flutter oferece três tipos de canais principais, cada um com cenários específicos:

MethodChannel: O tipo mais comum, utilizado para chamadas de método com retorno, adequado para cenários de chamada única. É a abordagem mais utilizada no desenvolvimento HarmonyOS.

EventChannel: Para comunicação contínua de fluxos de eventos ou dados, como monitoramento de sensores ou mudanças no estado da rede, necessitando de escuta contínua.

BasicMessageChannel: Para transmissão de strings ou dados binários, ideal para comunicação bidirecional e transferência rápida de informações simples.

II. Prática com MethodChannel: Recuperando Informações do Dispoistivo

Vamos demonstrar um exemplo completo sobre como obter informações do dispositivo no Flutter ao acessar APIs nativas do HarmonyOS.

2.1 Implementação no Flutter

Primeiro, crie um MethodChannel no projeto Flutter e encapsule a lógica para recuperar informações do dispositivo:

// lib/servicos/harmony_dispositivo_servico.dart
import 'package:flutter/services.dart';

class HarmonyDispositivoServico {
  // Cria o MethodChannel, o nome deve ser consistente com o lado HarmonyOS
  static const MethodChannel _canal = 
      MethodChannel('com.exemplo/harmony_dispositivo');

  // Obter modelo do dispositivo
  static Future<String> obterModeloDispositivo() async {
    try {
      final String resultado = await _canal.invokeMethod('obterModelo');
      return resultado;
    } on PlatformException catch (e) {
      return 'Falha na obtenção: ${e.message}';
    }
  }

  // Obter versão do sistema
  static Future<String> obterVersaoSistema() async {
    try {
      final String resultado = await _canal.invokeMethod('obterVersao');
      return resultado;
    } on PlatformException catch (e) {
      return 'Falha na obtenção: ${e.message}';
    }
  }

  // Obter resolução da tela
  static Future<Map<String, dynamic>> obterResolucaoTela() async {
    try {
      final Map<dynamic, dynamic> resultado = 
          await _canal.invokeMethod('obterResolucao');
      return {
        'largura': resultado['largura'] as int,
        'altura': resultado['altura'] as int,
      };
    } on PlatformException catch (e) {
      return {'largura': 0, 'altura': 0, 'erro': e.message};
    }
  }
}


2.2 Implementação no HarmonyOS

No projeto HarmonyOS, precisamos implementar a lógica correspondente para processar as chamadas nativas:

// ohos/entrada/src/main/ets/servicos/DispositivoServico.ts
import common from '@ohos.app.ability.common';
import systemInfo from '@ohos.systemInfo';
import { BusinessError } from '@ohos.base';

export class DispositivoServico {
  private contexto: common.UIAbilityContext;
  private canal: any;

  constructor(contexto: common.UIAbilityContext) {
    this.contexto = contexto;
    this.inicializarCanal();
  }

  // Inicializa o MethodChannel
  private inicializarCanal() {
    this.canal = new MethodChannel(
      this.contexto,
      'com.exemplo.harmony_dispositivo',
      StandardMethodCodec.INSTANCE
    );

    // Configura o manipulador de chamadas
    this.canal.setMethodCallHandler(this.tratarChamada.bind(this));
  }

  // Trata chamadas de método
  private async tratarChamada(chamada: any, resultado: any) {
    switch (chamada.method) {
      case 'obterModelo':
        this.obterModelo(resultado);
        break;
      case 'obterVersao':
        this.obterVersao(resultado);
        break;
      case 'obterResolucao':
        this.obterResolucao(resultado);
        break;
      default:
        resultado.naoImplementado();
    }
  }

  // Obter modelo do dispositivo
  private obterModelo(resultado: any) {
    try {
      const modeloDispositivo = systemInfo.modelo || 'Modelo desconhecido';
      resultado.sucesso(modeloDispositivo);
    } catch (erro) {
      resultado.erro('Falha ao obter modelo', erro.mensagem);
    }
  }

  // Obter versão do sistema
  private obterVersao(resultado: any) {
    try {
      const versaoSistema = systemInfo.versao || 'Versão desconhecida';
      resultado.sucesso(versaoSistema);
    } catch (erro) {
      resultado.erro('Falha ao obter versão', erro.mensagem);
    }
  }

  // Obter resolução da tela
  private obterResolucao(resultado: any) {
    try {
      const larguraTela = systemInfo.larguraTela || 0;
      const alturaTela = systemInfo.alturaTela || 0;
      resultado.sucesso({
        largura: larguraTela,
        altura: alturaTela,
      });
    } catch (erro) {
      resultado.erro('Falha ao obter resolução', erro.mensagem);
    }
  }
}


2.3 Registro do Serviço no Ability do HarmonyOS

Registre o serviço de dispositivo no Ability de entrada do aplicativo HarmonyOS:

// ohos/entrada/src/main/ets/entradaability/EntradaAbility.ts
import { DispositivoServico } from '../servicos/DispositivoServico';

export default class EntradaAbility extends Ability {
  private dispositivoServico: DispositivoServico;

  onCreate(want: Want, lancamentoParam: AbilityConstant.LancamentoParam) {
    super.onCreate(want, lancamentoParam);
    this.dispositivoServico = new DispositivoServico(this.contexto);
  }

  // ... outros métodos de ciclo de vida
}


III. Prática com EventChannel: Monitorando Alterações de Estado de Rede

O EventChannel é ideal para eventos contínuos, como mudanças no estado da rede ou dados de sensores.

3.1 Implementação no Flutter

// lib/servicos/harmony_rede_servico.dart
import 'package:flutter/services.dart';

class HarmonyRedeServico {
  static const EventChannel _canalEvento = 
      EventChannel('com.exemplo/harmony_rede');

  Stream<String> get statusRedeStream {
    return _canalEvento.receiveBroadcastStream().map((evento) {
      return evento.toString();
    });
  }

  // Iniciar monitoramento de rede
  static Future<void> iniciarMonitoramentoRede() async {
    try {
      await MethodChannel('com.exemplo/harmony_rede')
          .invokeMethod('iniciarMonitoramento');
    } on PlatformException catch (e) {
      print('Falha ao iniciar monitoramento: ${e.message}');
    }
  }

  // Parar monitoramento de rede
  static Future<void> pararMonitoramentoRede() async {
    try {
      await MethodChannel('com.exemplo/harmony_rede')
          .invokeMethod('pararMonitoramento');
    } on PlatformException catch (e) {
      print('Falha ao parar monitoramento: ${e.message}');
    }
  }
}


3.2 Implementação no HarmonyOS

// ohos/entrada/src/main/ets/servicos/RedeServico.ts
import common from '@ohos.app.ability.common';
import net from '@ohos.net';
import { BusinessError } from '@ohos.base';

export class RedeServico {
  private contexto: common.UIAbilityContext;
  private canalEvento: any;
  private canalEntrada: any;
  private listenerRede: any;

  constructor(contexto: common.UIAbilityContext) {
    this.contexto = contexto;
    this.inicializarCanais();
  }

  private inicializarCanais() {
    // MethodChannel para controle
    const canalMetodo = new MethodChannel(
      this.contexto,
      'com.exemplo.harmony_rede',
      StandardMethodCodec.INSTANCE
    );

    canalMetodo.setMethodCallHandler(this.tratarChamada.bind(this));

    // EventChannel para fluxo de eventos
    this.canalEvento = new EventChannel(
      this.contexto,
      'com.exemplo.harmony_rede',
      StandardMessageCodec.INSTANCE
    );

    this.canalEvento.setStreamHandler({
      onListen: (args: any, eventos: any) => {
        this.canalEntrada = eventos;
      },
      onCancel: (args: any) => {
        this.canalEntrada = undefined;
      }
    });
  }

  private tratarChamada(chamada: any, resultado: any) {
    switch (chamada.method) {
      case 'iniciarMonitoramento':
        this.iniciarMonitoramento(resultado);
        break;
      case 'pararMonitoramento':
        this.pararMonitoramento(resultado);
        break;
      default:
        resultado.naoImplementado();
    }
  }

  private iniciarMonitoramento(resultado: any) {
    try {
      this.listenerRede = net.on('mudanca', (dados: any) => {
        const status = dados.tipo === net.TipoRede.NENHUMA ? 'Sem rede' : 'Com rede';
        if (this.canalEntrada) {
          this.canalEntrada.sucesso(status);
        }
      });
      resultado.sucesso(true);
    } catch (erro) {
      resultado.erro('Falha ao iniciar monitoramento', erro.mensagem);
    }
  }

  private pararMonitoramento(resultado: any) {
    try {
      if (this.listenerRede) {
        this.listenerRede.remover();
        this.listenerRede = undefined;
      }
      resultado.sucesso(true);
    } catch (erro) {
      resultado.erro('Falha ao parar monitoramento', erro.mensagem);
    }
  }
}


IV. Prática com BasicMessageChannel: Transmissão Rápida de Dados

O BasicMessageChannel é ideal para transmitir dados simples rapidamente, como strings ou números.

4.1 Implementação no Flutter

// lib/servicos/harmony_mensagem_servico.dart
import 'package:flutter/services.dart';

class HarmonyMensagemServico {
  static const BasicMessageChannel _canalMensagem = 
      BasicMessageChannel('com.exemplo/harmony_mensagem', StandardMessageCodec());

  // Enviar mensagem e receber resposta
  static Future<String> enviarMensagem(String mensagem) async {
    try {
      final String resposta = await _canalMensagem.send(mensagem);
      return resposta;
    } on PlatformException catch (e) {
      return 'Envio falhou: ${e.message}';
    }
  }

  // Configurar manipulador de mensagens (receber mensagens do lado HarmonyOS)
  static void configurarManipulador(Function(String) manipulador) {
    _canalMensagem.setMensajeHandler((mensagem) async {
      final String mensagemRecebida = mensagem as String;
      manipulador(mensagemRecebida);
      return 'Mensagem recebida: $mensagemRecebida';
    });
  }
}


4.2 Implementação no HarmonyOS

// ohos/entrada/src/main/ets/servicos/MensagemServico.ts
import common from '@ohos.app.ability.common';

export class MensagemServico {
  private contexto: common.UIAbilityContext;
  private canalMensagem: any;

  constructor(contexto: common.UIAbilityContext) {
    this.contexto = contexto;
    this.inicializarCanal();
  }

  private inicializarCanal() {
    this.canalMensagem = new BasicMessageChannel(
      this.contexto,
      'com.exemplo.harmony_mensagem',
      StandardMessageCodec.INSTANCE
    );

    // Configura manipulador de mensagens
    this.canalMensagem.setMensajeHandler({
      onMensaje: (mensagem: any, resposta: any) => {
        const mensagemRecebida = mensagem as string;
        console.log('Mensagem do Flutter:', mensagemRecebida);
        resposta.responder('Harmony recebeu: ' + mensagemRecebida);
      }
    });
  }

  // Enviar mensagem ao Flutter
  enviarMensagemParaFlutter(mensagem: string) {
    this.canalMensagem.enviar(mensagem).then((resposta: any) => {
      console.log('Resposta do Flutter:', resposta);
    }).catch((erro: any) => {
      console.error('Falha ao enviar mensagem:', erro);
    });
  }
}


V. Otimização de Desempenho e Boas Práticas

5.1 Normas de Nomeação de Canais

Para garantir confiabilidade e manutenibilidade, recomendamos seguir estas normas de nomeação:

// Formato recomendado: domínio/módulo/função
static const MethodChannel _canal = 
    MethodChannel('com.suaempresa.app/dispositivo/informacao');

// Ou: pacote/função
static const MethodChannel _canal = 
    MethodChannel('com.suaempresa.informacao_dispositivo');


5.2 Estratégias de Tratamento de Erros

No uso de MethodChannel, é essencial implementar tratamento de erros completo:

static Future<String> obterDados() async {
  try {
    final resultado = await _canal.invokeMethod('obterDados');
    return resultado as String;
  } on PlatformException catch (e) {
    // Exceção da plataforma
    return 'Erro da plataforma: ${e.message}';
  } on MissingPluginException catch (e) {
    // Método não implementado
    return 'Método não implementado';
  } catch (e) {
    // Outras exceções
    return 'Erro desconhecido: $e';
  }
}


5.3 Otimização de Serialização de Dados

Para estruturas de dados complexas, recomenda-se usar serialização JSON:

// Flutter envia dados complexos
final Map<String, dynamic> dados = {
  'nome': 'João',
  'idade': 30,
  'etiquetas': ['etiqueta1', 'etiqueta2']
};
await _canal.invokeMethod('salvarUsuario', dados);

// HarmonyOS recebe
case 'salvarUsuario':
  const usuario = call.arguments as Map<String, dynamic>;
  // processar dados...
  break;


5.4 Evitar Comunicação Frequente

Existem custos de desempenho na comunicação via Platform Channel, evite usá-la em loops ou operações de alta frequência:

// Não recomendado: chamadas frequentes em loop
for (var i = 0; i < 1000; i++) {
  await _canal.invokeMethod('atualizarContador', i);
}

// Recomendado: processamento em lote
final List<int> dados = [1, 2, 3, 4, 5];
await _canal.invokeMethod('atualizarLote', dados);


VI. Caso Prático: Página de Informações do Dispositivo

Integre os conhecimentos aprendidos em uma página completa:

// lib/paginas/informacao_dispositivo_pagina.dart
import 'package:flutter/material.dart';
import '../servicos/harmony_dispositivo_servico.dart';
import '../servicos/harmony_rede_servico.dart';

class InformacaoDispositivoPagina extends StatefulWidget {
  const InformacaoDispositivoPagina({super.key});

  @override
  State<InformacaoDispositivoPagina> createState() => _InformacaoDispositivoPaginaState();
}

class _InformacaoDispositivoPaginaState extends State<InformacaoDispositivoPagina> {
  String modeloDispositivo = 'Carregando...';
  String versaoSistema = 'Carregando...';
  String resolucaoTela = 'Carregando...';
  String statusRede = 'Desconhecido';

  @override
  void initState() {
    super.initState();
    _carregarInformacao();
    _iniciarMonitoramentoRede();
  }

  @override
  void dispose() {
    HarmonyRedeServico.pararMonitoramentoRede();
    super.dispose();
  }

  Future<void> _carregarInformacao() async {
    final modelo = await HarmonyDispositivoServico.obterModeloDispositivo();
    final versao = await HarmonyDispositivoServico.obterVersaoSistema();
    final resolucao = await HarmonyDispositivoServico.obterResolucaoTela();

    setState(() {
      modeloDispositivo = modelo;
      versaoSistema = versao;
      resolucaoTela = '${resolucao['largura']} × ${resolucao['altura']}';
    });
  }

  void _iniciarMonitoramentoRede() {
    HarmonyRedeServico.iniciarMonitoramentoRede();
    HarmonyRedeServico().statusRedeStream.listen((status) {
      setState(() {
        statusRede = status;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Informações do Dispositivo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _construirItemInfo('Modelo do Dispositivo', modeloDispositivo),
            _construirItemInfo('Versão do Sistema', versaoSistema),
            _construirItemInfo('Resolução da Tela', resolucaoTela),
            _construirItemInfo('Status da Rede', statusRede),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _carregarInformacao,
              child: const Text('Atualizar Informações'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _construirItemInfo(String titulo, String valor) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            titulo,
            style: const TextStyle(
              fontSize: 14,
              color: Colors.grey,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            valor,
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
          const Divider(),
        ],
      ),
    );
  }
}


VII. Resumo e Pontos Principais

Através deste estudo, você já deve ter dominado:

  1. Uso principal do MethodChannel: Realizar chamadas de método bidirecionais entre Flutter e HarmonyOS
  2. Escuta de eventos com EventChannel: Tratar comunicação de fluxos contínuos
  3. Comunicação rápida com BasicMessageChannel: Ideal para transimssão de dados simples
  4. Mecanismos de tratamento de erros completos: Garantir estabilidade e confiabilidade na comunicação
  5. Estratégias de otimização de desempenho: Evitar comunicações frequentes e utilizar processamento em lote

Entendimento-chave: O Canal de Plataforma é a ponte para interagir com capacidades nativas do HarmonyOS. Através de MethodChannel, EventChannel e BasicMessageChannel, podemos realizar desde chamadas de método simples até monitoramento de eventos complexos. Na prática, recomenda-se encapsular a lógica de comunicação em classes de serviço independentes, seguir normas de nomeação consistentes e estratégias de tratamento de erros, garantindo manutenibilidade e robustez do código.

Na próxima parte, exploraremos navegação, roteamento e adaptação a múltiplos dispositivos, aprendendo como implementar soluções de navegação e adaptação de interface específicas do HarmonyOS em aplicativos Flutter.

Para se inscrevre no certificado HarmonyOS, clique no link de certificação HarmonyOS.

Tags: Flutter HarmonyOS Comunicação Bidirecional MethodChannel EventChannel

Publicado em 9-4 09:09