Comunidade

Rejourney Flutter SDK: Abertura Beta

A Rejourney Flutter SDK está em fase beta aberta no pub.dev, suportando versões recentes de Flutter e Dart. O pacote utiliza um método único para comunicação entre o código nativo (iOS e Android) e Dart, permitindo uma captura precisa das telas do aplicativo mesmo em casos complexos como a captura de pixels GPU em dispositivos Android.

Compartilhar
Rejourney Replay Workbench showing a captured Flutter session with synchronized evidence

O Rejourney Flutter SDK está agora em aberto beta no pub.dev. A versão atual é 0.3.1. Ele suporta Flutter 3.22+, Dart 3.3+, iOS 15.1+ e Android API 24+.

flutter pub add rejourney

// Configure primeiro. A gravação começa apenas quando start() é chamado.
await Rejourney.init('pk_live_your_public_key');
after await Rejourney.start();

01 // A BRIDGE

Código Nativo e Dart para Tradução

O Rejourney já tinha registradores nativos para iOS e Android. Escrever novamente seu ciclo de vida, upload, recuperação de crash e lógica de captura em Dart criaria um terceiro registrador com comportamentos levemente diferentes. O pacote Flutter usa uma única método channel, co.rejourney.flutter/methods, como uma fronteira estreita ao redor dos núcleos nativos.

Dart possui as partes que apenas o Flutter pode descrever bem: alterações de rota, erros do framework, limites de widget e a camada render retida. O código nativo possui o estado da sessão, política remota de gravação, temporizador de captura, codificação, persistência e upload. A fronteira transporta mapas simples, listas, arrays de bytes e valores escalares. Rejourney._channelValue rejeita qualquer coisa que o canal da plataforma não possa transportar antes de chegar a Kotlin ou Swift.

init() também faz menos do que seu nome sugere. Ele valida a chave do projeto e as opções, então configura ambos os lados nativos. A captura aguarda uma chamada explícita start(). Essa divisão dá ao aplicativo um lugar limpo para esperar pela concordância. Também permite que o amostramento remoto e o interruptor de gravação remota funcionem antes do início da leitura de framebuffer.

Fronteira plataforma

abstract class RejourneyPlatform extends PlatformInterface {
  Future<T?> invoke<T>( 
    String method,
    [Map<String, Object?>? arguments],
  );

  Stream<Map<String, Object?>> get events;
}

02 // CAPTURA DE GPU ANDROID

PixelCopy & Problemas de Captura de GPU do Flutter Resolvidos

Uma tela Flutter em Android é geralmente apresentada através de um FlutterSurfaceView. Chamando draw() na hierarquia de visualização Android circundante obtém o contêiner, mas não os pixels GPU nessa superfície. Usamos PixelCopy na superfície Flutter para a rota normal.

As primeiras versões beta expuseram um modo de falha pior. Em algumas combinações de renderizador e dispositivo, PixelCopy retornou SUCCESS e nos entregou uma bitmap corretamente dimensionada em preto. Um toast poderia fazer a janela inteira parecer não vazia, deixando um replay com um pequeno overlay nativo flutuando sobre um aplicativo Flutter preto. Verificar o código de resultado não era suficiente.

O registrador agora amostra uma grade 24 por 24 e classifica o resultado. O detector verifica a proporção quase-preta, faixa luma e quantos pixels contêm um sinal significativo. Uma ramificação requer pelo menos 98,5% de amostras quase-preto com máxima luma abaixo de 48. Outra captura o caso overlay escasso. O classificador não tem dependência gráfica Android, então seus casos de borda funcionam como testes normais JVM.

Classificação de sucesso falso

val nearBlackRatio = nearBlack.toDouble() / visible.toDouble()
return (nearBlackRatio >= 0.985 && maximumLuma < 48) ||
    (nonBlack <= meaningfulSignalFloor && maximumLuma - minimumLuma < 12) ||
    (nearBlackRatio >= 0.975 && nonBlack <= sparseOverlayFloor)
Também identificamos o Flutter por sua hierarquia de visual tipada FlutterView. A correspondência de uma string de nome da classe funcionou em builds de depuração e falhou após a minificação do Android alterar o nome. Isso foi um pequeno bug com um resultado local muito convincente.

Retorno da cena Flutter retida

final phase = SchedulerBinding.instance.schedulerPhase;
if (phase == SchedulerPhase.transientCallbacks ||
    phase == SchedulerPhase.midFrameMicrotasks ||
    phase == SchedulerPhase.persistentCallbacks) {
  await SchedulerBinding.instance.endOfFrame;
}

final layer = renderView.layer as OffsetLayer;
final image = await layer.toImage(bounds, pixelRatio: scale);
final bytes = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
Android transforma os bytes retornados em uma bitmap, compõe qualquer raiz de folha nativa, aplica a censura e envia o trabalho JPEG para seu único executor de codificação. A solicitação de camada retida usa metade da largura e altura normal do replay. Isso reduz a leitura de volta a um quarto dos pixels nos dispositivos já lutando com o caminho normal. A captura compatível também roda menos frequentemente. Seu batimento cardíaco em ociosidade é de 15 segundos. Uma mudança visual de alta importância pode solicitar um quadro após um intervalo mínimo de 5 segundos, e a navegação aguarda 2,5 segundos para que o destino se estabilize. Um guarda em voo descarta batidas sobrepostas. Um tempo limite de cinco segundos libera o guarda se Dart nunca responder. A FlutterSurfaceView em uso permanece no lugar durante todo o processo. A fonte ativa é visível por meio das métricas do SDK. Os contadores lastCaptureSource, flutterBlackFrameFallbackCount e os tempos de leitura da camada retida nos dizem se um relatório veio do caminho rápido de cópia de superfície ou do caminho compatível. Sem esses contadores, um replay pode parecer fixo enquanto o dispositivo paga muito mais por cada quadro. RejourneyMask é um RenderProxyBox. Ele não cobre o widget na aplicação em uso. Antes de pintar seu filho, ele mede os limites globais do filho e envia esse retângulo para a linha de montagem nativa de captura. Android converte pixels lógicos Flutter com a densidade da atividade; iOS pode usar as coordenadas lógicas diretamente. Relatar o último retângulo parece suficiente até que o widget role ou uma rota em saída anime para fora. As chamadas de retorno de transformação do Flutter e a cópia de pixel nativa são assíncronas. Um quadro capturado pode conter a posição anterior após Dart já ter relatado a nova.

Enquanto um widget mascarado está se movendo, o SDK envia a união de seus retângulos anteriores e atuais. Depois que o movimento se estabiliza, a máscara encolhe para os limites atuais. A primeira colocação usa uma janela de segurança mais curta de 750 ms; movimentos posteriores usam dois segundos. Quando o objeto render deteta-se, seu último retângulo permanece registrado por mais dois segundos porque o Flutter ainda pode apresentar pixels cacheados da rota em saída.

Máscara de movimento conservadora

final safeRect = _reportedRect?.expandToInclude(rect) ?? rect;
if (safeRect != _reportedRect) {
  _reportedRect = safeRect;
  _sendRegion(safeRect);
}

_settleTimer = Timer(safetyWindow, () {
  _reportedRect = rect;
  _sendRegion(rect);
});

Esta escolha ocasionalmente deixa um retângulo branco estalado no replay por um momento. Preferimos esse artefato a uma única frame de um número de cartão ou mensagem privada. Testes movem um widget mascarado entre pinturas e verificam que a área varrida permanece coberta.

05 // NAVEGAÇÃO

Monitoramento de rotas aguarda a tela que nomeia

O RejourneyNavigatorObserver lida com push, pop, replace e remove. Ele resolve um nome de uma chamada de retorno da aplicação, das configurações da rota ou do tipo runtime da rota, então descarta duplicatas consecutivas. O evento de tela é registrado imediatamente.

O marcador visual correspondente é atrasado até o próximo callback pós-frame. Sem essa atraso, um evento de rota nomeado /checkout pode apontar para o quadro final de /cart. No caminho retido-layer do Android, código nativo adiciona o atraso de estabilização e agrupa solicitações rápidas. Uma chamada de retorno de navegação se torna uma solicitação pelo primeiro frame útil do destino, não um comando para ler pixels exatamente nesse instante.

O ecossistema Router do Flutter tem mais de uma fonte da verdade, então o observador é opcional. Aplicativos usando um pacote Router podem chamar trackScreen() de sua própria chamada de retorno de navegação e ainda obter a mesma linha do tempo nativa da sessão.

06 // ERROS

Captura de erros e manipuladores

Falhas do framework Flutter chegam através de FlutterError.onError. Erros não capturados no root-isolate chegam através de PlatformDispatcher.instance.onError. O SDK salva ambos os manipuladores existentes antes de instalar seus wrappers e chama-os após registrar o incidente. Descartar a captura do manuseio restaura os originais.

Não colocamos runApp() em uma nova zona durante a instalação normal. O Flutter espera que inicialização de vinculação e runApp() compartilhem uma zona, e esconder uma mudança de zona dentro de um SDK é uma boa maneira de produzir avisos no aplicativo anfitrião. Equipes que já usam runZonedGuarded podem continuar fazendo isso; o pacote expõe um auxiliar protegido para uso explícito.

Todo incidente Dart recebe uma ID com timestamp e aleatória criptograficamente antes de atravessar o canal. A ID sobrevive a ambas as pontes nativas. Caminhos de recuperação de falhas e ANR usam o mesmo esquema de ID de incidente, então o backend pode mesclar transportes duplicados sem inferir do texto da pilha. Tipos Dart obfuscados em lançamento adicionam outra camada: nomes como _A não são categorias úteis, então o SDK recorre ao nome de um Error ou Exception encontrado na mensagem.

07 // REDE E PARAR

Medindo a Resposta ao Chamador

A rede Dart não passa por URLSession ou OkHttp de uma maneira que um interceptador nativo sempre possa ver. O RejourneyHttpClient envolve o package:http. Ele registra método, URL, status, tipo de conteúdo, contagem de bytes e cronometragem. Os corpos da solicitação e resposta nunca são registrados.

Uma resposta em fluxo não está concluída quando chegam os cabeçalhos. O envoltório conta pedaços conforme o chamador os consome e emite seu evento na conclusão do fluxo ou erro. Isso faz com que a duração e o tamanho da resposta descrevam a transferência que a aplicação experimentou. A hospedeira SDK e os padrões de ignora configurados são filtrados antes que o evento cruze o canal, o que também impede que o Rejourney observe suas próprias uploads.

O desligamento tem um limite separado. O stop() dá ao gravador nativo 10 segundos por padrão para esvaziar e finalizar. Se essa data limite expirar, o Dart retorna uma parada local bem-sucedida com uploadSuccess: false e um aviso native_flush_timeout. A chamada nativa continua rodando e seu caminho de persistência pode salvar trabalho pendente. Um SDK de análise offline não deve transformar um deslogamento ou alteração de consentimento em uma tela congelada.

08 // BETA ABERTO

O que queremos aprender no beta

O gravador e o formato de ingestão usam as mesmas fundações nativas dos outros SDKs móveis do Rejourney. A etiqueta beta aberto se refere à superfície de integração Flutter e ao escopo das aplicações que estão em cima dela. Estamos procurando sinais nessas áreas:

  • Renderizadores e combinações de dispositivos fora da nossa matriz de emulador Android e dispositivo.
  • Impeller, Flame, views de plataforma e renderização personalizada durante transições animadas.
  • Pacotes de roteamento cujo modelo de navegação não mapeia limpo para um NavigatorObserver raiz.
  • Ciclos de vida de adicionar ao app e multi-engine onde a atividade ativa do Android altera a propriedade.
  • Máscaras de cronometragem em torno de slivers complexos, sobreposições e navegadores aninhados.

Um lançamento Flutter é marcado apenas após testes Dart e benchmarks, testes nativos Android, integração com o emulador, uma execução no simulador iOS, um build consumidor independente e uma passagem pub publish --dry-run. O CI verifica a versão no pubspec, API Dart, pacote Android, CocoaPods spec e ambas as constantes de tempo de execução nativo. A marca então publica no pub.dev com um token OIDC de vida curta.

Instale o beta com flutter pub add rejourney. O pacote inclui um exemplo executável, métricas de captura para a rota de fallback Android e ganchos de falha e ANR (Aplicações Não Respondem) apenas para depuração para validar um projeto antes do lançamento.

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa