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.
Downloads e links
Repositório Maven → https://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")
}
- minSdk 21 (piso do Media3, que
metrike-ads-videodepende). metrike-ads-corenão traz OkHttp/Gson nem nenhuma lib HTTP externa — usaHttpURLConnectionpuro, para não conflitar com o que o app hospedeiro já usa. A única dependência externa além do androidx básico é a lib oficial do Googlecom.android.installreferrer(minúscula, usada só para o transporte do click-id — §6).- Apps Java: o SDK é Kotlin, então
kotlin-stdlibentra transitivamente via o artefato — nenhuma configuração extra é necessária, mas confirme que seu app não está excluindoorg.jetbrains.kotlin:kotlin-stdlibem algumexcludede dependência. - Apps Java — listeners exigem TODOS os métodos, não só os que importam:
os listeners deste SDK (
MetrikeAdListener,MetrikeInterstitial .Listener,MetrikeVastVideo.VastListener) são interfaces Kotlin com corpo default vazio (fun onX() {}) em cada método — em Kotlin isso permite implementar só o que interessa. Em Java isso não vale: sem a flag de compilador-Xjvm-default(que este SDK não usa), um método Kotlin com corpo default ainda vira um método abstrato do ponto de vista do bytecode Java — uma classe anônima Java que só sobrescreveonAdLoaded(), por exemplo, falha a compilar comis not abstract and does not override abstract method onAdClicked(). Os snippets Java deste documento já implementam todos os métodos de cada interface por esse motivo — copie-os por completo, não recorte para "só o que eu preciso".
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:
- GAID (Google Advertising ID) — o SDK não pede a permissão
AD_IDnem lê o identificador.visitor_idé um UUID próprio, não um device ID. - Localização — só se o publisher optar explicitamente (§5).
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_url — o 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.
5. Consent, privacidade e GPS
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");
valueCentsé umLongem centavos (espelhaconversion_goals .value_centsno servidor) — mas isso é só a convenção da assinatura Kotlin/Java do método; no wire ele é convertido para unidades decimais de moeda antes de sair (4990→value=49.90, nãovalue=4990), porque o parser do servidor espera decimal e multiplica por 100 internamente, igual à tag web. Não confunda: a API do SDK recebe centavos, a URL que ela monta manda reais/decimal.- Um
valueCentsnegativo é ignorado — o parâmetrovalueé omitido por completo da requisição (mesmo efeito de não passar nada), porque o servidor rejeita valores negativos de qualquer forma (cai no default do goal). Não há como enviar um valor negativo "de propósito". orderIdé opcional, string livre (para dedup do lado do servidor, se o goal estiver configurado para isso).- Atribuição, deduplicação e janela de atribuição são inteiramente do
servidor — nada muda no comportamento existente do
/pixel/conversion.
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:
device_type = 'ctv'.- Sempre fullscreen — sem outstream/inline em TV.
- Sem clique/CTR — não existe ponteiro em D-pad; CTR não é métrica de CTV (o mercado usa QR code no creative, fora do escopo do V1).
- Display em TV está fora de escopo — só o caminho de vídeo foi validado (D6 do design spec). CTV amplo (Tizen/webOS/Roku, fora do universo Android) é uma frente separada via SSAI (roadmap item 10), não este SDK.
9. Limitações declaradas do V1
- MRAID host — perfil core apenas. O SDK implementa a máquina de
estados MRAID 3.0 completa para creatives
htmlzip/ext_tag(loading → default → expanded/resized → hidden, eventos,expand()/resize()/close(), getters), mas:mraid.getLocationestá fora por decisão de privacidade — bloqueia só o creative de terceiro de ler a localização do usuário; não afeta a medição de localização própria do SDK (§5), que não passa por MRAID.- Expand em duas partes (URL própria), orientation lock fino e
audioVolumeChangeficam para V1.1. - Um
ext_tagque dependa de MRAID e for servido para um creative não marcado como JS-capable degrada para renderização de display padrão (o MRAID host só é instanciado parakind == "htmlzip"ou"ext_tag"— umimagepuro nunca temwindow.mraid).
- Sem GAID — por decisão de privacidade (D8 do design spec), não por
limitação técnica.
visitor_idé o único identificador. - Sem OM SDK (Open Measurement) — o SDK faz self-measurement nativo (mesmo padrão do AdButler). Certificação OM SDK para verificadores terceiros (IAS/DV) é V1.1, só necessária quando um publisher real exigir medição de verificador externo dentro do app.
contentUrlsó no banner — interstitial e os formatos de vídeo não aceitam esse parâmetro nesta versão (§3.2, §4).- Instalação não é evento contável — ver §6.
- S2S postback, view-through, MMPs — fora de escopo (não fazem parte deste SDK nem estão planejados para V1.1 conhecido).
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:
- Banner e interstitial, com campo de
zone_ideditável. - Vídeo fullscreen manual (VAST) e outstream inline, com campo de
vastTokeneditável. - Instream automático (Media3/
MetrikeAdsLoader) com um vídeo de conteúdo demo e break schedule pre/mid/post-roll. - Alternância de ambiente (produção / servidor local) sem precisar reinstalar — só reiniciar o app.
- Simulação de deep link (
handleDeepLink) e de conversão (trackConversion) com um clique de exemplo. - Log de eventos na tela — todo callback e beacon aparece com
timestamp, é o que torna a demo autoevidente (dá pra ver o
viewabledisparando ao rolar a tela).