【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:
- Uso principal do MethodChannel: Realizar chamadas de método bidirecionais entre Flutter e HarmonyOS
- Escuta de eventos com EventChannel: Tratar comunicação de fluxos contínuos
- Comunicação rápida com BasicMessageChannel: Ideal para transimssão de dados simples
- Mecanismos de tratamento de erros completos: Garantir estabilidade e confiabilidade na comunicação
- 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.