Advertising
- Content: calls
migo.createAd({adType, adUnitId, …})and receives handles forload / show / hide / destroy; - Engine: turns each call into an
AdHandlermethod and treats an absent reply as a stalled ad request; - Host: implements
AdHandlerand bridges to its own advertising SDK (Pangle / GDT / Kuaishou Alliance / in-house) — the runtime does not link an advertising SDK, hold advertiser credentials, or decide whether an ad was fully watched.
Type constants use AdHandler.TYPE_*:
| Type | Scenario |
|---|---|
rewardedVideo |
Rewarded video |
interstitial |
Interstitial |
banner |
Banner |
custom |
Custom (native style) |
grid |
Grid ad |
gameBanner / gameIcon / gamePortal |
Three cross-promotion container surfaces |
The host does not need to implement every type — each create* has a default not supported settle (the reason this default is still active behavior is explained below).
adId Handles and Callback Threads
Section titled “adId Handles and Callback Threads”- The
adIdincreateAd(adId, adType, adUnitId, optionsJson)is a runtime-assigned handle and remains unchanged for its entire lifetime. Every event for this ad object must be reported with the same ID; - Every AdHandler method is called on the runtime’s host thread. Do not block this thread with SDK work. Callbacks to content go through
AdEventSink(safe from any thread), whose complete API has only seven methods:emitLoad(int)/emitLoad(int, boolean)(whether to fall back to a share-page-style alternative),emitError,emitClose(adId, isEnded),emitShowFailed(=emitError+emitClose(false)),emitResize, andemitHide. There is noemitShow,emitClick, oremitReward— reward delivery is carried byemitClose’sisEndedvalue.
Do not report events after destruction: after destroyAd, every emit* for that adId is discarded — the engine’s memory discipline means content has already returned the handle. AdEventSink sends are safe from any thread, but have no retry semantics; do not expect terminal state to queue inside the SDK.
emitResize is in CSS px, like soft-keyboard HEIGHT_CHANGE: it is a content-layout coordinate, and reporting physical pixels makes content size the ad slot by the wrong factor.
Fallback Contract: Not Implementing It Is Still a Working State
Section titled “Fallback Contract: Not Implementing It Is Still a Working State”When content calls an ad API without a handler, the request does not stall: every AdHandler method’s default implementation settles through the only authoritative channel:
| Call | Default behavior without a handler |
|---|---|
createAd |
emitError(adId, -1, "createAd:fail not supported") |
loadAd |
emitError(adId, -1, "loadAd:fail not supported") |
showAd |
emitShowFailed(adId, -1, "showAd:fail not supported") = emitError + emitClose(adId, false) |
hideAd |
emitHide(adId) — a positioning ad’s onHide tells content the space has been released; it is not an error |
updateAdStyle |
No-op (the layout change is consumed without reporting an ad error) |
destroyAd |
No-op |
That isEnded=false is both a traffic-side safety net and a content-compatibility layer:
- Safety net: issue rewards only for a genuinely completed view. Falsely reporting
truegrants phantom rewards — publisher money cannot be claimed before the view actually completes; - Compatibility layer: common mini-game content puts its “grant reward + resume game” logic in the
onClosecallback. If the fallback only reports an error and does not close, content remains stuck on its paused recording bar — worse than discovering that no ad opportunity exists.
These are one rule: a result content is waiting for must settle or the experience stalls; a result that could be misrepresented as paid must settle with a factual flag. This is stated verbatim in the AdHandler.java header comment, not suggested by this page.
Minimal Implementation
Section titled “Minimal Implementation”With one createAd, route on adType; report whether viewing completed through isEnded in emitClose:
session.setAdHandler(new AdHandler() { @Override public void createAd(int adId, String adType, String adUnitId, String optionsJson, AdEventSink sink) { if (!TYPE_REWARDED_VIDEO.equals(adType)) { AdHandler.super.createAd(adId, adType, adUnitId, optionsJson, sink); return; } RewardedAd ad = pangle.create(adUnitId); ad.setListener(new RewardedAd.AdInteractionListener() { @Override public void onAdReady() { sink.emitLoad(adId); } @Override public void onError(int code, String msg) { sink.emitError(adId, code, msg); } @Override public void onReward(RewardItem item, boolean verified) { sink.emitClose(adId, verified); // completion means reward; interruption means no reward } }); // Async load failure also uses emitShowFailed: error + close(false), leaving no paused content }});optionsJson is the platform-style JSON object containing remaining creation parameters (adIntervals / style / adTheme / gridCount / count / multiton) — it may be "{}", never null. Parse only the keys you support; safely ignore unsupported keys.
Common Pitfalls
Section titled “Common Pitfalls”- Do not initialize the SDK on the runtime host thread — creating an ad object is fine, but SDK initialization lasting tens of milliseconds makes the ad request stall, equivalent to not implementing the handler.
- Validate
adUnitIdon the host side — the runtime does not validate ad-unit IDs; report malformed requests throughemitError. - Multiple creates for the same
adUnitIdare normal content behavior (multitonoption). If the host reuses an object instance, rebind its sink to the correctadIdbefore reuse — the sink is tied to the ad ID, not the SDK object.