Skip to content

Android

Интеграция в приложение для смартфонов и планшетов. Общая схема описана в разделе WebView: общая схема, здесь — готовый код для Android.

Что понадобится

  • WebView с включённым JavaScript.
  • Видеоплеер — в примере Media3 ExoPlayer.
  • Адрес страницы SDK с платформой android: https://cdn.adsdk.ru/android/v3/.

Как устроен экран

xml
<!-- res/layout/activity_ad.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <androidx.media3.ui.PlayerView
        android:id="@+id/adPlayer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:use_controller="false" />

    <!-- Сверху: принимает нажатия на кнопки SDK -->
    <WebView
        android:id="@+id/adWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

Полный пример

Настройте WebView и откройте страницу SDK

kotlin
class AdActivity : AppCompatActivity() {

    private lateinit var webView: WebView
    private lateinit var player: ExoPlayer
    private val main = Handler(Looper.getMainLooper())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ad)

        player = ExoPlayer.Builder(this).build()
        findViewById<PlayerView>(R.id.adPlayer).player = player
        player.addListener(playerListener)

        webView = findViewById(R.id.adWebView)
        webView.setBackgroundColor(Color.TRANSPARENT)
        webView.settings.javaScriptEnabled = true
        webView.settings.mediaPlaybackRequiresUserGesture = false
        webView.addJavascriptInterface(AdEvents(), "Android")
        webView.loadUrl(adPageUrl(vastUrl = intent.getStringExtra(EXTRA_VAST_URL)!!))
    }

    private fun adPageUrl(vastUrl: String): String =
        Uri.parse("https://cdn.adsdk.ru/android/v3/").buildUpon()
            .appendQueryParameter("url", vastUrl)
            .appendQueryParameter("lang", "ru")
            .appendQueryParameter("controls", """["ad-skip-btn","ad-click-btn","ad-more-menu"]""")
            .build()
            .toString()

    /** Выполняет метод SDK. evaluateJavascript можно вызывать только из главного потока. */
    private fun sdk(call: String) = main.post {
        webView.evaluateJavascript("window.myAdController.$call", null)
    }
}

Получайте события

SDK вызывает методы объекта Android. Объявите те, что нужны, — остальные SDK пропустит.

Потоки

Методы @JavascriptInterface вызываются в фоновом потоке WebView. Всё, что трогает плеер или интерфейс, переносите в главный поток — в примере это main.post { }.

kotlin
private inner class AdEvents {

    private var isVpaid = false

    // Приходит перед AdLoaded: тип креатива говорит, кто будет его проигрывать
    @JavascriptInterface
    fun AdCreativeLoaded(
        srcUrl: String, type: String, bannerId: String, isNoBanner: Boolean,
        isPod: Boolean, podLength: Int, isLast: Boolean, erid: String, durationMsec: Int,
    ) {
        isVpaid = type == "application/javascript"
    }

    @JavascriptInterface
    fun AdLoaded(mediaFileUrl: String) = main.post {
        if (isVpaid) {
            sdk("playAd()")                // VPAID играет внутри WebView
        } else {
            player.setMediaItem(MediaItem.fromUri(mediaFileUrl))
            player.prepare()
            player.playWhenReady = true
        }
    }

    @JavascriptInterface
    fun AdSkipped(adNumber: Int) = main.post { player.stop() }

    @JavascriptInterface
    fun AdClick(url: String, adNumber: Int) = main.post {
        startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
    }

    @JavascriptInterface
    fun AdError(code: String, message: String, type: String,
                isNoBanner: Boolean, isLast: Boolean, adNumber: Int) = main.post { close() }

    @JavascriptInterface
    fun AdDestroyed(isLast: Boolean) = main.post {
        player.stop()
        if (isLast) close()
    }
}

Сообщайте SDK о воспроизведении

kotlin
private val progress = object : Runnable {
    override fun run() {
        if (player.duration > 0) {
            sdk("timeupdateAd(${player.currentPosition / 1000.0}, ${player.duration / 1000.0})")
        }
        main.postDelayed(this, PROGRESS_INTERVAL_MS)
    }
}

private val playerListener = object : Player.Listener {

    override fun onIsPlayingChanged(isPlaying: Boolean) {
        if (isPlaying) {
            sdk("playAd()")                // и первый старт, и продолжение после паузы
            main.post(progress)
        } else {
            main.removeCallbacks(progress)
            if (!player.playWhenReady) sdk("pauseAd()")   // пауза пользователя, не буферизация
        }
    }

    override fun onPlaybackStateChanged(state: Int) {
        if (state == Player.STATE_ENDED) {
            val durationSec = player.duration / 1000.0
            sdk("timeupdateAd($durationSec, $durationSec)")   // SDK пришлёт AdComplete
        }
    }
}

private companion object {
    const val PROGRESS_INTERVAL_MS = 250L
    const val EXTRA_VAST_URL = "vast_url"
}

Завершите показ

kotlin
private fun close() {
    main.removeCallbacks(progress)
    finish()
}

override fun onPause() {
    super.onPause()
    player.pause()                         // listener отправит pauseAd()
}

override fun onDestroy() {
    webView.removeJavascriptInterface("Android")
    webView.destroy()
    player.release()
    super.onDestroy()
}

Сигнатуры событий

WebView ищет метод интерфейса по имени и числу аргументов. Метод с другим числом аргументов не будет вызван. Полный список — в разделе События → Android.

СобытиеМетод
Реклама загруженаAdCreativeLoaded(9 аргументов), затем AdLoaded(mediaFileUrl)
Начало показаAdStarted(adNumber)
КвартилиAdFirstQuartile(adNumber), AdMidpoint(adNumber), AdThirdQuartile(adNumber)
Можно пропуститьAdSkippableStateChanged(status)
Ролик досмотренAdComplete(adNumber)
Показ законченAdDestroyed(isLast)
ОшибкаAdError(code, message, type, isNoBanner, isLast, adNumber)

Частые ошибки

СимптомПричина
Кнопки SDK видны, ролика нетФон WebView не прозрачный или WebView лежит под плеером.
Нет AdStartedНе вызван playAd() после старта плеера.
Нет квартилей и AdCompleteНе вызывается timeupdateAd().
Событие не приходитЧисло аргументов метода не совпадает с сигнатурой.
Приложение падает в обработчике событияПлеер тронут не из главного потока.

Больше — в разделе Решение проблем.