Metrike / Ads SDK / Android

Metrike Ads SDK — Android

Guia de integração para publishers. Cobre os dois artefatos do SDK (metrike-ads-core e metrike-ads-video), os seis formatos de anúncio, o lado do publisher e do advertiser da conversão in-app, e as limitações declaradas do V1.

Status: V1 demo-grade com API production-shaped (nomes finais, interop Java, crash-safety, semver). Ver §11 do design spec (docs/superpowers/specs/2026-08-31-android-sdk-design.md) para o corte completo V1 × V1.1.


Repositório Mavenhttps://cdx.metrike.com.br/maven

maven { url = uri("https://cdx.metrike.com.br/maven") }

Código-fonte de exemplo:


1. Instalação

Repositório Maven estático servido pelo cdx (mesmo CDN que já serve os outros assets):

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google(); mavenCentral()
        maven { url = uri("https://cdx.metrike.com.br/maven") }
    }
}
// build.gradle.kts do app
dependencies {
    implementation("com.metrike.ads:metrike-ads-core:0.1.0")
    // Só se for usar algum formato de vídeo (fullscreen, outstream ou
    // instream automático) — display-only não precisa deste artefato:
    implementation("com.metrike.ads:metrike-ads-video:0.1.0")
}

2. Inicialização e identidade automática

// Kotlin — identidade automática (recomendado)
Metrike.initialize(context)

// Kotlin — com override do nome do app
Metrike.initialize(context, MetrikeConfig(appName = "MeuApp"))
// Java
Metrike.initialize(context);

// com override
Metrike.initialize(context, new MetrikeConfig("MeuApp"));

Chame uma vez, cedo (Application.onCreate é o lugar recomendado — é onde o app de teste faz).

O que é coletado automaticamente (via PackageManager, sem nenhuma declaração manual):

| Campo | Origem | |---|---| | app_bundle | context.packageName — autoritativo, impossível de errar por typo | | app_name | label do app (ou o appName do MetrikeConfig, se informado) | | app_version | versionName (fallback: versionCode se versionName estiver vazio) | | sdk_version | versão do SDK (MetrikeSdk.VERSION) | | device_type | mobile / tablet / ctv, via UiModeManager + smallestScreenWidthDp | | visitor_id | UUID gerado pelo SDK, persistido em SharedPreferences — equivalente do cookie _mk_vid da tag web | | session_id | UUID novo por processo |

Esses campos acompanham toda requisição do SDK (ad request, beacon, clique, conversão).

O que NÃO é coletado:

Metrike.isInitialized() reporta se initialize() já rodou. Toda API pública é um try/catch na borda: uma falha interna nunca derruba o app hospedeiro — na pior hipótese, uma chamada vira no-op.


3. Formatos de anúncio

| Formato | Classe | Endpoint | Módulo | |---|---|---|---| | Banner inline | MetrikeBannerView | /serve | core | | Interstitial fullscreen | MetrikeInterstitial | /serve | core | | Vídeo fullscreen (pre/mid/post-roll manual) | MetrikeVastVideo | /vast/{token} | video | | Vídeo outstream inline | MetrikeVideoAdView | /vast/{token} | video | | Instream automático (estilo IMA) | MetrikeAdsLoader | /vmap/{token} ou /vast/{token} | video | | Player próprio do publisher | Metrike.buildVastUrl(token) | /vast/{token} | core |

3.1 Banner (MetrikeBannerView)

Via XML:

<com.metrike.ads.MetrikeBannerView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="320dp"
    android:layout_height="50dp"
    app:metrikeZoneId="12345" />

Via código:

// Kotlin
val banner = findViewById<MetrikeBannerView>(R.id.banner)
banner.zoneId = 12345L
banner.contentUrl = "https://meusite.com.br/noticia/123" // opcional — ver §4
banner.setAdListener(object : MetrikeAdListener {
    override fun onAdLoaded() { /* renderizado */ }
    override fun onNoFill() { /* view já colapsa sozinha (View.GONE) */ }
    override fun onError(code: Int, message: String) { /* log/telemetria */ }
    override fun onImpression() { /* beacon nativo já disparado */ }
    override fun onAdClicked() { /* Custom Tab já foi aberto */ }
})
banner.load()
// Java
MetrikeBannerView banner = findViewById(R.id.banner);
banner.setZoneId(12345L);
banner.setContentUrl("https://meusite.com.br/noticia/123");
banner.setAdListener(new MetrikeAdListener() {
    @Override public void onAdLoaded() { }
    @Override public void onNoFill() { }
    @Override public void onError(int code, String message) { }
    @Override public void onImpression() { }
    @Override public void onAdClicked() { }
});
banner.load();

O creative é desenhado num WebView interno (image vira <img>, htmlzip/ext_tag carregam o HTML), mas medição e beacons são 100% nativos — viewability, impressão e clique não dependem de nenhum IntersectionObserver rodando dentro do WebView (que não enxergaria oclusão nativa, WebView fora de tela ou tela desligada). Desanexar a view (RecyclerView reciclando, fragment saindo) derruba o creative atual por completo; para reaparecer, chame load() de novo.

ext_tag: o servidor já pré-substitui qualquer macro de clique no HTML antes de enviá-lo — o SDK apenas injeta o HTML no WebView tal como veio, sem nenhuma pós-processação própria de macro.

3.2 Interstitial (MetrikeInterstitial)

// Kotlin
val interstitial = MetrikeInterstitial(activity, zoneId = 12345L)
interstitial.closeButtonDelaySeconds = 5 // default
interstitial.setListener(object : MetrikeInterstitial.Listener {
    override fun onReady() {
        interstitial.show(activity) // false se já não estiver mais pronto
    }
    override fun onClosed() { /* usuário fechou ou voltou */ }
    override fun onNoFill() { }
    override fun onError(code: Int, message: String) { }
    override fun onAdClicked() { }
})
interstitial.preload()
// Java
MetrikeInterstitial interstitial = new MetrikeInterstitial(activity, 12345L);
interstitial.setCloseButtonDelaySeconds(5);
interstitial.setListener(new MetrikeInterstitial.Listener() {
    @Override public void onReady() { interstitial.show(activity); }
    @Override public void onClosed() { }
    @Override public void onAdLoaded() { } // herdado de MetrikeAdListener — não usado por este formato
    @Override public void onNoFill() { }
    @Override public void onError(int code, String message) { }
    @Override public void onImpression() { } // herdado de MetrikeAdListener — não usado por este formato
    @Override public void onAdClicked() { }
});
interstitial.preload();

preload()/show() seguem o mesmo modelo pre-fetch do AdButler: show() retorna false (sem lançar nenhuma Activity) se isReady() for falso — chame preload() de novo antes de tentar de novo. Mesma renderização do banner; não há mudança de contrato no servidor — /serve é o mesmo endpoint. Convenção sugerida de tamanho de zona: 320×480 ou 768×1024.

Nota: ao contrário do banner, MetrikeInterstitial não aceita contentUrl nesta versão — a requisição /serve do interstitial não leva esse parâmetro. Use um banner (ou o classificador app-level do §4) se precisar de classificação por conteúdo em telas cujo único formato é interstitial.

3.3 Vídeo fullscreen manual — pre/mid/post-roll (MetrikeVastVideo)

Modelo AdButler: preload()onReady()display(). O SDK não decide quando mostrar o anúncio — quem decide o timing (pré-roll, meio do conteúdo, pós-roll) é o publisher, chamando display() no momento certo.

Exemplo de mid-roll pausando o próprio player de conteúdo do publisher:

// Kotlin
val vast = MetrikeVastVideo(activity, vastToken = "abc123")
vast.setListener(object : MetrikeVastVideo.VastListener {
    override fun onReady() {
        myContentPlayer.pause() // pausa o conteúdo ANTES de mostrar o ad
        vast.display(activity)
    }
    override fun onComplete() { myContentPlayer.play() } // retoma
    override fun onClose() { myContentPlayer.play() } // fechou antes do fim — retoma também
    override fun onNoAd() { /* sem preenchimento — conteúdo já não estava pausado */ }
    override fun onError(code: Int, message: String) { myContentPlayer.play() }
    override fun onStart() { }
    override fun onFirstQuartile() { }
    override fun onMidpoint() { }
    override fun onThirdQuartile() { }
    override fun onSkip() { }
    override fun onAdClicked() { }
})
// Chame preload() com antecedência (ex.: ao chegar perto do ponto de meio de conteúdo)
vast.preload()
// Java
MetrikeVastVideo vast = new MetrikeVastVideo(activity, "abc123");
vast.setListener(new MetrikeVastVideo.VastListener() {
    @Override public void onReady() {
        myContentPlayer.pause();
        vast.display(activity);
    }
    @Override public void onComplete() { myContentPlayer.play(); }
    @Override public void onClose() { myContentPlayer.play(); }
    @Override public void onNoAd() { }
    @Override public void onError(int code, String message) { myContentPlayer.play(); }
    @Override public void onStart() { }
    @Override public void onFirstQuartile() { }
    @Override public void onMidpoint() { }
    @Override public void onThirdQuartile() { }
    @Override public void onSkip() { }
    @Override public void onAdClicked() { }
});
vast.preload();

Pre-roll: chame preload()/display() antes de iniciar o conteúdo. Post-roll: chame no evento de fim de conteúdo, e não retome nada em onComplete()/onClose().

Comportamentos do player fullscreen que você não precisa (nem consegue) configurar: o botão de fechar (X) só aparece quando fechar é permitido (pós-complete ou pós-skip — antes disso o vídeo é a experiência); um countdown "Anúncio · 0:SS" e uma barra de progresso ficam sempre visíveis; e um clique no "Saiba mais" abre o destino em Custom Tab pausando o anúncio, que retoma sozinho quando o usuário volta (comportamento padrão de mercado, igual ao Google IMA — sem risco de ad "congelado").

3.4 Vídeo outstream inline (MetrikeVideoAdView)

Sem Activity própria — um FrameLayout que você coloca direto no seu layout (ex.: uma linha de feed). Autoplay mudo quando ≥50% visível continuamente por ≥2s, pausa ao sair de 50%; som ativa no toque do próprio usuário no ícone. Ao terminar, a view colapsa sozinha (View.GONE) — não há botão de fechar nem skip (a saída padrão de mercado do outstream é o próprio scroll levando o anúncio pra fora de tela).

// Kotlin
val outstream = MetrikeVideoAdView(context)
outstream.vastToken = "abc123"
outstream.setListener(object : MetrikeVastVideo.VastListener {
    override fun onReady() { }
    override fun onNoAd() { }
    override fun onError(code: Int, message: String) { }
    // demais callbacks (onStart, quartis, onComplete, onAdClicked...) — mesma interface do fullscreen
})
feedContainer.addView(outstream, MATCH_PARENT, MATCH_PARENT)
outstream.load()
// Java — em Java todos os métodos da interface precisam ser implementados,
// mesmo os que você não usa (ver nota sobre listeners em Java, §1).
MetrikeVideoAdView outstream = new MetrikeVideoAdView(context);
outstream.setVastToken("abc123");
outstream.setListener(new MetrikeVastVideo.VastListener() {
    @Override public void onReady() { }
    @Override public void onNoAd() { }
    @Override public void onError(int code, String message) { }
    @Override public void onStart() { }
    @Override public void onFirstQuartile() { }
    @Override public void onMidpoint() { }
    @Override public void onThirdQuartile() { }
    @Override public void onComplete() { }
    @Override public void onSkip() { }
    @Override public void onAdClicked() { }
    @Override public void onClose() { }
});
feedContainer.addView(outstream, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
outstream.load();

Chame destroy() (ou deixe a view ser desanexada da window) para parar o player e a medição de viewability — reciclagem de RecyclerView já dispara isso sozinha via onDetachedFromWindow.

3.5 Instream automático estilo IMA (MetrikeAdsLoader)

Requisito obrigatório: o player de conteúdo do publisher precisa ser Media3/ExoPlayer — mesma restrição do Google IMA. Player diferente (ex.: MediaPlayer nativo, ou outro player de terceiro) não é compatível com este formato; use o modelo fullscreen manual (§3.3) ou buildVastUrl() (§3.6) nesse caso.

MetrikeAdsLoader implementa a interface AdsLoader do Media3 — o Media3 faz toda a coreografia (inserir na timeline, pausar conteúdo, tocar o ad, retomar, seek por cima de break pulado, seguir em frente se um ad falhar). Exemplo completo (o mesmo wiring do InstreamDemoActivity do app de teste, sdk/android/testapp/src/main/java/com/metrike/ads/testapp/InstreamDemoActivity.kt):

// Kotlin
val playerView = PlayerView(this) // seu próprio player.xml normalmente
val loader = MetrikeAdsLoader(this)

// A ordem importa: o media source factory (que já captura `loader` e
// `playerView`) precisa existir ANTES do player ser construído.
val mediaSourceFactory = DefaultMediaSourceFactory(this)
    .setLocalAdInsertionComponents({ _ -> loader }, playerView)

val exoPlayer = ExoPlayer.Builder(this)
    .setMediaSourceFactory(mediaSourceFactory)
    .build()
playerView.player = exoPlayer
loader.setPlayer(exoPlayer)

// adTagUri aponta pra um VMAP (múltiplos breaks) ou um VAST único (só pre-roll)
val adTagUri = Uri.parse("$baseUrl/vmap/abc123?breaks=start,50%,end")
val mediaItem = MediaItem.Builder()
    .setUri(Uri.parse(contentVideoUrl))
    .setAdsConfiguration(MediaItem.AdsConfiguration.Builder(adTagUri).build())
    .build()

exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
exoPlayer.playWhenReady = true

// Ao sair da tela:
exoPlayer.release()
loader.release()
// Java
PlayerView playerView = new PlayerView(this);
MetrikeAdsLoader loader = new MetrikeAdsLoader(this);

DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(this)
    .setLocalAdInsertionComponents(adsConfiguration -> loader, playerView);

ExoPlayer exoPlayer = new ExoPlayer.Builder(this)
    .setMediaSourceFactory(mediaSourceFactory)
    .build();
playerView.setPlayer(exoPlayer);
loader.setPlayer(exoPlayer);

Uri adTagUri = Uri.parse(baseUrl + "/vmap/abc123?breaks=start,50%,end");
MediaItem mediaItem = new MediaItem.Builder()
    .setUri(Uri.parse(contentVideoUrl))
    .setAdsConfiguration(new MediaItem.AdsConfiguration.Builder(adTagUri).build())
    .build();

exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.setPlayWhenReady(true);

// ao sair da tela
exoPlayer.release();
loader.release();

Um break individual que falhar (VAST malformado, sem preenchimento) não derruba o schedule inteiro — só aquele break é marcado com erro, o Media3 pula pra frente e o conteúdo continua. Overlay de "Anúncio" / "Pular" (quando a zona tiver skipoffset) é desenhado automaticamente sobre o AdViewProvider.

3.6 Player próprio (Metrike.buildVastUrl)

Para publishers com player de vídeo próprio que não usam nenhum dos formatos acima:

// Kotlin
val vastUrl = Metrike.buildVastUrl("abc123")
// vastUrl já vem com app_bundle/app_name/app_version/sdk_version anexados —
// alimente-o no seu player como faria com qualquer VAST tag.
// Java
String vastUrl = Metrike.buildVastUrl("abc123");

Trade-off explícito: o SDK não mede nada nesse caminho (sem beacons nativos de viewability/audibilidade) — o ad request deixa de ser anônimo (identidade do app anexada), mas a medição vira responsabilidade do player externo, exatamente como um VAST tag puro contratado de qualquer fornecedor.


4. contentUrl — classificação por conteúdo

MetrikeBannerView.contentUrl é a URL web equivalente do conteúdo da tela atual (apps de notícias tipicamente têm uma). O servidor trata como page_urlo mesmo classificador de conteúdo que já roda na tag web funciona sem nenhuma mudança, por matéria, igual à experiência web.

Sem contentUrl (ou em telas que só usam interstitial/vídeo, que não carregam esse parâmetro nesta versão), o servidor cai num caminho app-level: resolve a categoria pela página pública da Play Store do app_bundle (cacheada por app, não por URL — cardinalidade de dezenas, não milhares). Preencha contentUrl sempre que a tela tiver um equivalente web identificável — é estritamente melhor que o fallback app-level.


Consent (IABTCF): o SDK lê IABTCF_gdprApplies e IABTCF_TCString das SharedPreferences default — é onde todo CMP in-app certificado IAB escreve — e repassa como gdpr/gdpr_consent no /serve, automaticamente, sem nenhuma chamada extra do publisher. Sem CMP presente, o SDK se comporta como a tag web sem TCF (nenhum dos dois parâmetros é enviado).

GAID: não é coletado. Sem permissão AD_ID, sem leitura do identificador de publicidade. visitor_id é um UUID próprio persistido em SharedPreferences, não um device ID.

GPS — opt-in, default OFF:

// Kotlin
Metrike.initialize(context, MetrikeConfig(collectLocation = true))
// Java — collectLocation é o 2º parâmetro do construtor; appName fica null
Metrike.initialize(context, new MetrikeConfig(null, true));

Com collectLocation = true, o SDK lê a última localização conhecida (LocationManager.PASSIVE_PROVIDER) — nunca solicita permissão, nunca se registra para atualizações de localização — e somente se o app hospedeiro já detém ACCESS_FINE_LOCATION ou ACCESS_COARSE_LOCATION. Sem uma das duas permissões, collectLocation = true é um no-op silencioso.

Aviso de Data Safety: se o publisher ativar collectLocation, a declaração de dados coletados do app na Play Store (formulário Data Safety) precisa refletir a coleta de localização aproximada/precisa — isso é responsabilidade do publisher, o SDK não gera nenhuma declaração automática.

INTERNET e ACCESS_NETWORK_STATE já vêm declaradas no manifest do próprio SDK (mesclado automaticamente pelo manifest merger do Gradle) — o publisher não precisa declará-las de novo.


6. Conversão in-app

Lado do publisher (onde o anúncio é exibido)

Nada a fazer. O clique já abre a URL de /click via Custom Tabs; o redirect 302 do servidor já anexa mk_cid e (se o destino for a Play Store) referrer=mk_cid%3D... na própria URL da loja. Não há API adicional do lado de quem exibe o anúncio.

Lado do advertiser (app anunciado no clique)

O app do advertiser também integra metrike-ads-core (só o core — não precisa de metrike-ads-video) e usa dois pontos de entrada:

1. Capturar o mk_cid — dois transportes, ambos entram:

// Kotlin — deep link / App Link (app já instalado, ou reaberto via link)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Metrike.handleDeepLink(intent) // lê mk_cid da URL que abriu a Activity, se houver
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    Metrike.handleDeepLink(intent)
}
// Java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Metrike.handleDeepLink(getIntent());
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Metrike.handleDeepLink(intent);
}

Chame no launcher Activity do app do advertiser (ou em qualquer Activity capaz de receber o deep link). Retorna true se um mk_cid foi encontrado e persistido; um link sem esse parâmetro não apaga um mk_cid já armazenado por outro caminho.

Se o app já vem instalado quando o usuário clica (o clique abre a loja mas o app já está lá, então o sistema abre o app direto via App Link), o Play Install Referrer é o segundo transporte: quando o destino do clique é a Play Store, o /click server-side anexa referrer=mk_cid%3D<id> à URL da loja, e o SDK do advertiser lê esse referrer sozinho, uma única vez, na primeira abertura pós-instalação — nenhuma chamada extra do publisher é necessária, só ter Metrike.initialize rodado.

⚠️ Aviso importante: se o app do advertiser já usa um Install Referrer próprio (ex.: para atribuição de UTM de outra ferramenta de marketing), o referrer da Play Store é um valor único por instalação — o segundo escritor não sobrescreve o primeiro. Nesse cenário, o mk_cid injetado pelo /click se perde silenciosamente (o app lê o referrer da outra ferramenta, não o nosso). Avise o advertiser dessa limitação antes de assumir que a atribuição de instalação-via-clique vai funcionar — não há solução de SDK para isso; é uma limitação estrutural do Install Referrer API (um único slot por instalação). O deep link (transporte 1) não tem esse problema.

2. Registrar o evento de conversão:

// Kotlin
Metrike.trackConversion("goal_public_id", valueCents = 4990L, orderId = "order-123")
// Java
Metrike.trackConversion("goal_public_id", 4990L, "order-123");

Instalação não é evento contável — o Install Referrer é só o "carteiro" do click-id; não existe (e não está planejado) um goal type install.


7. Métricas

Paridade total com a tag web — mesmo pipeline de ingestão, mesmos dashboards, mesmo verification report:

| Evento | Disparado por | |---|---| | request | load() (banner) / preload() (interstitial) — vídeo (MetrikeVastVideo/MetrikeAdsLoader/buildVastUrl) não dispara um beacon JSON de request próprio: cada fill bem-sucedido de GET /vast/{token} já grava sua própria linha request no servidor (2026-08-31), então zonas de vídeo agora contam requests + uniques igual às demais — efetivo a partir do deploy desta mudança, sem alteração nenhuma no SDK cliente | | impression | banner/interstitial renderizados; vídeo ao iniciar (pixel <Impression> do VAST) | | viewable / notViewable / viewUndetermined | medição nativa de viewability (abaixo) | | click | toque no creative (abre /click via Custom Tabs) | | Quartis VAST (start, firstQuartile, midpoint, thirdQuartile, complete) | polling de posição do player, todos os formatos de vídeo | | mute / unmute / skip | interação do usuário no player de vídeo | | Conversão | Metrike.trackConversion (lado advertiser) |

Exclusivas do SDK (não existem no caminho web-tag/in-app-sem-SDK): identidade do app (app_bundle/app_name/app_version), sdk_version (prova de ambiente in_app_mechanism='sdk' — topo da escada de detecção in-app, acima de mraid-top/mraid-window/webview-ua), viewability com oclusão/tela/foreground reais (não estimada por UA), audibilidade real, device_type declarado (não inferido por UA).

Viewability (display — banner/interstitial): regra MRC, ≥50% da área visível por 1s contínuo, medida nativamente (View.getGlobalVisibleRect × área total, janela com foco, tela ligada, app em foreground) — poll de ~250ms enquanto pendente. viewable_mechanism = 'native-sdk'. Timeout de 30min sem atingir o critério: comportamento silencioso — igual à tag web, nenhum beacon é disparado nesse desfecho (a tag web também apenas desconecta seu observer sem notificar nada).

Viewability + audibilidade (vídeo): ≥50% visível por 2s contínuos (MRC vídeo). Audibilidade é acumulada: quando visível≥50% e audível (volume do stream × volume do device × não-mudo) somam, juntos, pelo menos metade da duração do vídeo, dispara fully_viewable_audible_half_duration_impression — evento que já existia no vocabulário do /pixel/event (por compatibilidade IMA) e passa a ser emitido de verdade pela primeira vez com este SDK.

Duração de referência (quartis + audibilidade): o SDK usa a duração real reportada pelo player assim que conhecida — a <Duration> do XML é só fallback inicial. Igual ao Google IMA, e imune a metadado errado no ad item (um duration_seconds ausente/incorreto no servidor não quebra os quartis).

Mecanismos: todo beacon do SDK carrega in_app_mechanism='sdk' (display/interstitial) — prova positiva de identidade, diferente de mraid-top/mraid-window/webview-ua (heurísticas de UA sem identidade).

Identidade no pipeline de vídeo: os pixels de tracking/impressão/click que voltam dentro do próprio XML VAST (<Impression>, <Tracking event="...">, <ClickTracking>) são GETs simples, sem corpo JSON — o SDK anexa app_bundle/app_name/app_version/sdk_version/device_type/ visitor_id a cada um deles imediatamente antes de disparar, mas só quando a URL aponta pro nosso próprio servidor (mesmo host do baseUrl configurado). Um break VMAP resolvido pra um ad server de terceiro nunca recebe essa identidade — só os pixels que o /vast//serve da própria Metrike gerou. É esse anexo que faz in_app_mechanism='sdk' resolver de verdade pra cada beacon de vídeo (não só pro fetch inicial do /vast, que já carregava identidade via Metrike.buildVastUrl/identityQuery).


8. Android TV

MetrikeVastVideo/MetrikeAdsLoader funcionam em Android TV (validado com D-pad) — o app de teste tem uma entrada leanback exercitando os caminhos de vídeo. O que muda:


9. Limitações declaradas do V1


10. App de teste

O app :testapp deste repositório é o exemplo vivo de integração — todo snippet acima corresponde a uma chamada real em sdk/android/testapp/src/main/java/com/metrike/ads/testapp/.

Instalação: APK assinado publicado em https://cdx.metrike.com.br/sdk/testapp-release.apk — baixe e instale direto no aparelho (aviso padrão do Android de "fontes desconhecidas"). QR code para o mesmo link disponível junto à distribuição (ver docs/sdk/distribution.md).

O que dá pra testar: