С 2015 года ситуация с Admob Native ads практически не изменилась, нативная реклама по прежнему находится в бета-релизе, с лимитированным доступом для издателей. В официальных доках появились новая редакция и некоторые разъяснения по поводу того, каким образом планируется эти самые Native ads внедрять. Мы, в свою очередь, также не сидели сложа руки, копили материал для очередной статьи, и, как только появилось свободное время, слегка расширили функционал библиотеки admobadapter . А именно, реализовали в ней поддержку прокручиваемой нативной рекламы для RecyclerView, так же как мы делали это в прошлой статье для ListView.
Подружим 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 и пытаемся загрузить блок рекламы
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 претерпели изменения:
- Создание views для отображения данных/рекламы
- Биндинг views к данным/рекламе
- Переопределение прочих абстрактных методов базового класса.
Остались в прежнем виде:
- Механизм загрузки рекламы с сервера AdMob (класс AdmobFetcher)
- Структура рекламных views (xml) и ее заполнение при биндинге
- Калькуляция индексов и количества рекламных блоков / блоков с данными
- Суть методов getItemCount(), getItemId(int position) и getItemViewType(int position) осталась прежней, но изменились их названия.
Сухой остаток из второй части статьи — официальная документация по Admob native ads продолжает развиваться, как бы намекая нам, что процедура интеграции нативной рекламы в приложение может оказаться нетривиальной и количество затраченного времени будет зависеть не только от нашей скорости разработки, но и от скорости модерации / адекватности персонала Admob/Google.
Будем признательны за статистику, если проголосуете в прикрепленных опросах! По доброй традиции, желаем Вам красивой рекламы в Ваших приложениях!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
ссылка на оригинал статьи https://habrahabr.ru/post/282427/
Добавить комментарий