Pull to refresh

Нативная реклама возвращается: Native Admob, RecyclerView и вкратце о правилах

Reading time 6 min
Views 4.2K
С 2015 года ситуация с Admob Native ads практически не изменилась, нативная реклама по прежнему находится в бета-релизе, с лимитированным доступом для издателей. В официальных доках появились новая редакция и некоторые разъяснения по поводу того, каким образом планируется эти самые Native ads внедрять. Мы, в свою очередь, также не сидели сложа руки, копили материал для очередной статьи, и, как только появилось свободное время, слегка расширили функционал библиотеки admobadapter . А именно, реализовали в ней поддержку прокручиваемой нативной рекламы для RecyclerView, так же как мы делали это в прошлой статье для ListView.

Подружим RecyclerView с Native Ads


imageТак как предыдущий опыт показал себя неплохо, то и в случае RecyclerView решили реализовать wrapper (далее обертка) для адаптера. Это позволяет разработчику создавать свой особый и неповторимый адаптер для данных, а мы ему стараемся не мешать в этом начинании. Затем разработчик связывает обертку с адаптером, используя dependency injection, а в RecyclerView передает ссылку на обертку. Таким образом, мы не вмешиваемся в логику адаптера. Обертка при отображении коллекции вычисляет где показывать рекламный блок, а где — исходные данные. Поскольку рассматриваемая часть библиотеки никаких изящных решений и новаторских конструкций не несет, то хотелось бы подробнее остановиться на особенностях и отличиях в реализации прокручиваемой нативной рекламы для RecyclerView от ListView . Базовый класс адаптера для RecyclerView — это RecyclerView.Adapter<~>, а для ListViewBaseAdapter. Если применение ViewHolder-паттерна для ListView — это действо из разряда best-practices, то в декларации RecyclerView.Adapter<VH extends RecyclerView.ViewHolder> параметр-тип ViewHolder является обязательным. Базовый класс требует переопределение методов
abstract int getItemCount()
возвращает общее количество элементов, хранимых адаптером. По сути аналогичен методу int getCount();
abstract void onBindViewHolder(VH holder, int position)
выполняет биндинг вьюшки, хранимой в holder к элементу коллекции данных, взятому с индексом position. Этот и следующий методы пришли на «замену» методу View getView(int position, View convertView, ViewGroup parent) из BaseAdapter — ранее, в зависимости от результатов проверки convertView на null, либо создавали новый convertView, либо биндили текущий к соответствующим данным;
abstract VH onCreateViewHolder(ViewGroup parent, int viewType) 
создает и возвращает viewholder нужного типа viewType. Тип контейнера viewType нужно использовать в случае, если элементы вашей коллекции требуется отображать разными способами согласно определенному признаку. В этом случае также следует определить метод
int getItemViewType(int position)
возвращает тип контейнера для элемента коллекции данных по индексу position. Для BaseAdapter также требовалось определить общее число типов контейнеров в методе getViewTypeCount, в RecyclerView.Adapter<~> это делать уже не надо. На нашем примере getViewTypeCount во wrapper вернет 3: один тип для элементов из исходной коллекции данных (для адаптера), а еще два — для рекламы с контентом и рекламы установки приложений (см. в предыдущей статье). При этом, в адаптере может быть определена своя логика отображения типов контейнеров и обертке об этом ничего знать не нужно. Итак, исходный код лучше тысячи слов :)
Развернуть
   public class AdmobRecyclerAdapterWrapper<T, V extends View>
        extends RecyclerView.Adapter<ViewWrapper<V>>
        implements AdmobFetcher.AdmobListener {
        //...
         @Override
    public void onBindViewHolder(ViewWrapper<V> viewHolder, int position) {
        if (viewHolder==null)
            return;

        switch (viewHolder.getItemViewType()) {
//тип контейнера - реклама установки приложения
            case VIEW_TYPE_AD_INSTALL:
//берем из viewHolder.getView() и используем ее повторно (recycling)
                NativeAppInstallAdView lvi1 = (NativeAppInstallAdView) viewHolder.getView();
//берем рекламу по индексу. getItem в свою очередь возьмет из AdmobFetcher закешированную рекламу, либо даст команду на загрузку новой.
                NativeAppInstallAd ad1 = (NativeAppInstallAd) getItem(position);
//биндим рекламу во вьюшку
                AdViewHelper.bindInstallAdView(lvi1, ad1);
                break;
//тип контейнера - реклама с контентом
            case VIEW_TYPE_AD_CONTENT:
                NativeContentAdView lvi2 = (NativeContentAdView) viewHolder.getView();
                NativeContentAd ad2 = (NativeContentAd) getItem(position);
                AdViewHelper.bindContentAdView(lvi2, ad2);
                break;
            default:
//трансформируем индекс из системы индексации обертки(с учетом рекламных блоков), в индекс адаптера (исходной коллекции данных)
                int origPos = getOriginalContentPosition(position);
//делегируем биндинг адаптеру данных
                mAdapter.onBindViewHolder(viewHolder, origPos);
        }
    }

    @Override
    public final ViewWrapper<V> onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case VIEW_TYPE_AD_INSTALL:
            case VIEW_TYPE_AD_CONTENT:
//создаем новый viewholder в случае если viewType - это любой рекламный блок
                return new ViewWrapper<V>(onCreateItemView(parent, viewType));
            default:
//иначе делегируем операцию адаптеру данных
                return mAdapter.onCreateViewHolder(parent, viewType);
        }
    }
//просто утилитарный метод для создания вьюшки рекламных контейнеров (см. подробнее в предыдущей статье)
    private V onCreateItemView(ViewGroup parent, int viewType) {
        switch (viewType) {
            case VIEW_TYPE_AD_INSTALL:
                NativeAppInstallAdView lvi1 = getInstallAdView(parent);
                return (V)lvi1;
            case VIEW_TYPE_AD_CONTENT:
                NativeContentAdView lvi2 = getContentAdView(parent);
                return (V)lvi2;
            default:
                return null;
        }
    }
        //прочие методы были приведены в предыдущей статье, изменились не существенно
        //...
   }


Немного бюрократии


В начале статьи мы упоминали про уточнения в официальных доках по нативной рекламе от Admob и одно из основных изменений — это правила публикации. В общих чертах процесс интеграции нативной рекламы должен выглядеть следующим образом (прошу меня поправить, если где ошибся):
  1. Разработка и получение одобрения у Вашего аккаунт-менеджера на mockup вашей будущей рекламы, и ее представления в вашем UI, еще до имплементации тестовой версии приложения (Добровольно)
  2. Тестирование шаблонов рекламы в закрытом режиме и с тестовым admob publish id
  3. Получение официального одобрения, что у вас с шаблоном и с правилами размещения все в порядке. Наше предположение, что эта процедура будет доступна непосредственно из вашей консоли разработчика в режиме альфа/бета тестирования, либо из admob-дэшборда
  4. Публикация одобренного приложения

Формальная проверка на внешний вид рекламы в Вашем UI будет выполняться согласно уже опубликованному чек-листу.
Также в developer-guide был добавлен комментарий, суть которого в том, чтобы предостеречь разработчиков от многопоточной загрузки блоков рекламы вызовом метода loadAd в контексте единственного объекта AdLoader. То есть, в основном, остается немногим более двух вариантов :)
  • Создавать для каждого вызова loadAd отдельную сущность AdLoader.
  • Перед каждым вызовом loadAd в контексте единственного объекта AdLoader, проверять, что загрузка предыдущего блока была завершена.

Пример реализации второго варианта можно посмотреть, опять же, в нашей библиотеке admobadapter (без претензий на каноничность). Так как более подробно эти методы уже были описаны в предыдущей статье, рассмотрим фрагменты кода AdmobFetcher, отвечающие за синхронизацию loadAd. С этой целью используется флаг
AtomicBoolean lockFetch = new AtomicBoolean();
Перед вызовом loadAd проверяем значение этого флага и если он выставлен в true — в данный момент загружается другой блок,- выходим. Иначе — выставляем его в true и пытаемся загрузить блок рекламы
Развернуть
    private synchronized void fetchAd() {
        Context context = mContext.get();

        if (context != null) {
            if(lockFetch.getAndSet(true))
                return;
            adLoader.loadAd(getAdRequest());
        } else {
            mFetchFailCount++;
        }
    }

Чтобы позволить загружать блоки рекламы, нам следует выставить флаг в false, это можно сделать подписавшись на завершение loadAd при создании сущности AdLoader, как показано далее
Развернуть
   adLoader = new AdLoader.Builder(mContext.get(), admobUnitId)
                .forAppInstallAd(new NativeAppInstallAd.OnAppInstallAdLoadedListener() {
                    @Override
                    public void onAppInstallAdLoaded(NativeAppInstallAd appInstallAd) {
                        lockFetch.set(false);
                        //...
                    }
                })
                .forContentAd(new NativeContentAd.OnContentAdLoadedListener() {
                    @Override
                    public void onContentAdLoaded(NativeContentAd contentAd) {
                        lockFetch.set(false);
                        //...
                    }
                })
                .withAdListener(new AdListener() {
                    @Override
                    public void onAdFailedToLoad(int errorCode) {
                        lockFetch.set(false);
                        mFetchFailCount++;
                         //...
                    }
                }).build();


Вместо выводов


Итак, в новой обертке для RecyclerView претерпели изменения:
  1. Создание views для отображения данных/рекламы
  2. Биндинг views к данным/рекламе
  3. Переопределение прочих абстрактных методов базового класса.

Остались в прежнем виде:
  1. Механизм загрузки рекламы с сервера AdMob (класс AdmobFetcher)
  2. Структура рекламных views (xml) и ее заполнение при биндинге
  3. Калькуляция индексов и количества рекламных блоков / блоков с данными
  4. Суть методов getItemCount(), getItemId(int position) и getItemViewType(int position) осталась прежней, но изменились их названия.

Сухой остаток из второй части статьи — официальная документация по Admob native ads продолжает развиваться, как бы намекая нам, что процедура интеграции нативной рекламы в приложение может оказаться нетривиальной и количество затраченного времени будет зависеть не только от нашей скорости разработки, но и от скорости модерации / адекватности персонала Admob/Google.
Будем признательны за статистику, если проголосуете в прикрепленных опросах! По доброй традиции, желаем Вам красивой рекламы в Ваших приложениях!

P.S.: Оказывается некоторая информация из данной статьи «слегка» устарела, даю ссылку на новую гугл-доку support.google.com/admob/answer/6270315?hl=ru. Как появится время — опишу процесс подключения на Хабре.
Only registered users can participate in poll. Log in, please.
Ждете ли релиза Admob Native Ads?
50% Так точно! 21
21.43% Никак нет! 9
28.57% О чем речь то? 12
42 users voted. 20 users abstained.
Only registered users can participate in poll. Log in, please.
Какой контрол чаще используете?
27.08% ListView 13
75% RecyclerView 36
2.08% другое (укажите в комментах) 1
48 users voted. 18 users abstained.
Tags:
Hubs:
0
Comments 0
Comments Leave a comment

Articles