С 2015 года ситуация с Admob Native ads практически не изменилась, нативная реклама по прежнему находится в бета-релизе, с лимитированным доступом для издателей. В официальных доках появились новая редакция и некоторые разъяснения по поводу того, каким образом планируется эти самые Native ads внедрять. Мы, в свою очередь, также не сидели сложа руки, копили материал для очередной статьи, и, как только появилось свободное время, слегка расширили функционал библиотеки admobadapter . А именно, реализовали в ней поддержку прокручиваемой нативной рекламы для RecyclerView, так же как мы делали это в прошлой статье для ListView.
Так как предыдущий опыт показал себя неплохо, то и в случае RecyclerView решили реализовать wrapper (далее обертка) для адаптера. Это позволяет разработчику создавать свой особый и неповторимый адаптер для данных, а мы ему стараемся не мешать в этом начинании. Затем разработчик связывает обертку с адаптером, используя dependency injection, а в RecyclerView передает ссылку на обертку. Таким образом, мы не вмешиваемся в логику адаптера. Обертка при отображении коллекции вычисляет где показывать рекламный блок, а где — исходные данные. Поскольку рассматриваемая часть библиотеки никаких изящных решений и новаторских конструкций не несет, то хотелось бы подробнее остановиться на особенностях и отличиях в реализации прокручиваемой нативной рекламы для RecyclerView от ListView . Базовый класс адаптера для RecyclerView — это RecyclerView.Adapter<~>, а для ListView — BaseAdapter. Если применение ViewHolder-паттерна для ListView — это действо из разряда best-practices, то в декларации RecyclerView.Adapter<VH extends RecyclerView.ViewHolder> параметр-тип ViewHolder является обязательным. Базовый класс требует переопределение методов
В начале статьи мы упоминали про уточнения в официальных доках по нативной рекламе от Admob и одно из основных изменений — это правила публикации. В общих чертах процесс интеграции нативной рекламы должен выглядеть следующим образом (прошу меня поправить, если где ошибся):
Формальная проверка на внешний вид рекламы в Вашем UI будет выполняться согласно уже опубликованному чек-листу.
Также в developer-guide был добавлен комментарий, суть которого в том, чтобы предостеречь разработчиков от многопоточной загрузки блоков рекламы вызовом метода loadAd в контексте единственного объекта AdLoader. То есть, в основном, остается немногим более двух вариантов :)
Пример реализации второго варианта можно посмотреть, опять же, в нашей библиотеке admobadapter (без претензий на каноничность). Так как более подробно эти методы уже были описаны в предыдущей статье, рассмотрим фрагменты кода AdmobFetcher, отвечающие за синхронизацию loadAd. С этой целью используется флаг
Итак, в новой обертке для RecyclerView претерпели изменения:
Остались в прежнем виде:
Сухой остаток из второй части статьи — официальная документация по Admob native ads продолжает развиваться, как бы намекая нам, что процедура интеграции нативной рекламы в приложение может оказаться нетривиальной и количество затраченного времени будет зависеть не только от нашей скорости разработки, но и от скорости модерации / адекватности персонала Admob/Google.
Будем признательны за статистику, если проголосуете в прикрепленных опросах! По доброй традиции, желаем Вам красивой рекламы в Ваших приложениях!
Подружим RecyclerView с Native Ads
Так как предыдущий опыт показал себя неплохо, то и в случае RecyclerView решили реализовать wrapper (далее обертка) для адаптера. Это позволяет разработчику создавать свой особый и неповторимый адаптер для данных, а мы ему стараемся не мешать в этом начинании. Затем разработчик связывает обертку с адаптером, используя dependency injection, а в RecyclerView передает ссылку на обертку. Таким образом, мы не вмешиваемся в логику адаптера. Обертка при отображении коллекции вычисляет где показывать рекламный блок, а где — исходные данные. Поскольку рассматриваемая часть библиотеки никаких изящных решений и новаторских конструкций не несет, то хотелось бы подробнее остановиться на особенностях и отличиях в реализации прокручиваемой нативной рекламы для RecyclerView от ListView . Базовый класс адаптера для RecyclerView — это RecyclerView.Adapter<~>, а для ListView — BaseAdapter. Если применение 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 и одно из основных изменений — это правила публикации. В общих чертах процесс интеграции нативной рекламы должен выглядеть следующим образом (прошу меня поправить, если где ошибся):
- Разработка и получение одобрения у Вашего аккаунт-менеджера на mockup вашей будущей рекламы, и ее представления в вашем UI, еще до имплементации тестовой версии приложения (Добровольно)
- Тестирование шаблонов рекламы в закрытом режиме и с тестовым admob publish id
- Получение официального одобрения, что у вас с шаблоном и с правилами размещения все в порядке. Наше предположение, что эта процедура будет доступна непосредственно из вашей консоли разработчика в режиме альфа/бета тестирования, либо из admob-дэшборда
- Публикация одобренного приложения
Формальная проверка на внешний вид рекламы в Вашем UI будет выполняться согласно уже опубликованному чек-листу.
Также в developer-guide был добавлен комментарий, суть которого в том, чтобы предостеречь разработчиков от многопоточной загрузки блоков рекламы вызовом метода loadAd в контексте единственного объекта AdLoader. То есть, в основном, остается немногим более двух вариантов :)
- Создавать для каждого вызова loadAd отдельную сущность AdLoader.
- Перед каждым вызовом loadAd в контексте единственного объекта AdLoader, проверять, что загрузка предыдущего блока была завершена.
Пример реализации второго варианта можно посмотреть, опять же, в нашей библиотеке admobadapter (без претензий на каноничность). Так как более подробно эти методы уже были описаны в предыдущей статье, рассмотрим фрагменты кода AdmobFetcher, отвечающие за синхронизацию loadAd. С этой целью используется флаг
AtomicBoolean lockFetch = new AtomicBoolean();
Перед вызовом loadAd проверяем значение этого флага и если он выставлен в true — в данный момент загружается другой блок,- выходим. Иначе — выставляем его в true и пытаемся загрузить блок рекламыРазвернуть
Чтобы позволить загружать блоки рекламы, нам следует выставить флаг в false, это можно сделать подписавшись на завершение loadAd при создании сущности AdLoader, как показано далее private synchronized void fetchAd() {
Context context = mContext.get();
if (context != null) {
if(lockFetch.getAndSet(true))
return;
adLoader.loadAd(getAdRequest());
} else {
mFetchFailCount++;
}
}
Развернуть
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 претерпели изменения:
- Создание views для отображения данных/рекламы
- Биндинг views к данным/рекламе
- Переопределение прочих абстрактных методов базового класса.
Остались в прежнем виде:
- Механизм загрузки рекламы с сервера AdMob (класс AdmobFetcher)
- Структура рекламных views (xml) и ее заполнение при биндинге
- Калькуляция индексов и количества рекламных блоков / блоков с данными
- Суть методов getItemCount(), getItemId(int position) и getItemViewType(int position) осталась прежней, но изменились их названия.
Сухой остаток из второй части статьи — официальная документация по Admob native ads продолжает развиваться, как бы намекая нам, что процедура интеграции нативной рекламы в приложение может оказаться нетривиальной и количество затраченного времени будет зависеть не только от нашей скорости разработки, но и от скорости модерации / адекватности персонала Admob/Google.
Будем признательны за статистику, если проголосуете в прикрепленных опросах! По доброй традиции, желаем Вам красивой рекламы в Ваших приложениях!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.