Доброго времени суток, Хабрахабр!
Меня зовут Александр, я разработчик под ОС Android. Сегодня хочу с вами поделиться опытом реализации альтернативного платному способу отключения рекламы в приложении — отключение рекламы за просмотр рекламы (AdMob Rewarded Video Ads). Интересно? Тогда добро пожаловать под кат.
В далеком 2013 году я решил заняться разработкой приложений под Android, начал читать тематические книги, статьи, смотрел видео уроки и т.д. Написал первое недоприложение и приуныл, т.к. хотелось сделать что-то полезное, нужное обществу, а идей не было. В 2014 меня знакомый попросил разработать для него мобильный справочник по синтаксису платформы Arduino (там С язык). С огромным желанием я взялся за этот проект и реализовал первую версию для Android 3.0+. Через время решено было усовершенствовать ее, и так появилась вторая версия (для Android 4.0+). Обе они бесплатные с баннером от AdMob внизу и платным его отключением. Все было хорошо, пока мне не стали писать, что ~150-170 рублей РФ дороговато для отключения рекламы навсегда в их любимом справочнике. На что я ответил «бартерным» решением вопроса — пользователь может отключить баннер внизу на время за просмотр видео рекламы от AdMob.
[вернуться к содержанию]
При запуске приложения пользователю будет показан Dialog, с предложением отключить рекламу, если, конечно, он ранее ее не отключил или отсутствует подключение к сети Интернет. В случае положительного ответа, приложение показывает фрагмент с кнопками, с помощью которых и можно выполнить отключение рекламы в приложении удобным способом.
[вернуться к содержанию]
[вернуться к содержанию]
Код главной Activity
Код класса CheckURLConnection
Код класса PreferencesManager
Класс ConstantHolder — класс, в котором я храню константы, чтобы не импортировать их отовсюду, а только из одного места брать (аналог класса R)
И самое интересное — класс-фрагмент отключения рекламы
Класс работы с баннером AdMob. Ничего сложного, публикую для ознакомления. Хотя его же и на StackOverFlow ни раз выкладывал
[вернуться к содержанию]
Вот и все. Суть статьи — поделиться с обществом своей идеей и ее реализацией. А также получить приглашение на Хабрахабр, если кому-то понравится то, чем я поделился. Буду рад и благодарен за ваше мнение в вопросах доработки кода и/или идеи. Если понадобится дополнительное пояснение — пишите, я в кратчайшие сроки внесу правки в статью или дам ответ в комментариях.
Ссылку на приложение по понятным причинам не публикую в открытом доступе. Этика есть этика! Кому нужно — дам в лс.
Статистика AdMob пока еще сырая, с момента внедрения данной альтернативы прошло всего ~2 недели. Не все пользователи обновились. Но точно есть те, кто пользуется таким способом отключения баннера внизу.
UPDATE: Ссылка на статью с результатами ИТОГ 3-х месяцев: Альтернатива платному отключению рекламы в бесплатном приложении Android
Благодарю всех, кто дочитал статью до конца!
Меня зовут Александр, я разработчик под ОС Android. Сегодня хочу с вами поделиться опытом реализации альтернативного платному способу отключения рекламы в приложении — отключение рекламы за просмотр рекламы (AdMob Rewarded Video Ads). Интересно? Тогда добро пожаловать под кат.
Содержание для удобства навигации
Как все было ?
В далеком 2013 году я решил заняться разработкой приложений под Android, начал читать тематические книги, статьи, смотрел видео уроки и т.д. Написал первое недоприложение и приуныл, т.к. хотелось сделать что-то полезное, нужное обществу, а идей не было. В 2014 меня знакомый попросил разработать для него мобильный справочник по синтаксису платформы Arduino (там С язык). С огромным желанием я взялся за этот проект и реализовал первую версию для Android 3.0+. Через время решено было усовершенствовать ее, и так появилась вторая версия (для Android 4.0+). Обе они бесплатные с баннером от AdMob внизу и платным его отключением. Все было хорошо, пока мне не стали писать, что ~150-170 рублей РФ дороговато для отключения рекламы навсегда в их любимом справочнике. На что я ответил «бартерным» решением вопроса — пользователь может отключить баннер внизу на время за просмотр видео рекламы от AdMob.
[вернуться к содержанию]
Реализация, часть 1: Принцип работы (словами)
При запуске приложения пользователю будет показан Dialog, с предложением отключить рекламу, если, конечно, он ранее ее не отключил или отсутствует подключение к сети Интернет. В случае положительного ответа, приложение показывает фрагмент с кнопками, с помощью которых и можно выполнить отключение рекламы в приложении удобным способом.
[вернуться к содержанию]
Реализация, часть 2: Внешний вид
Диалог с предложением отключить рекламу
Экран отключения рекламы
1 видео реклама просмотрена
5 видео реклам просмотрено
Реклама отключена на 1 час
Реклама отключена на 1 день
[вернуться к содержанию]
Реализация, часть 3: Принцип работы (java код)
Код главной Activity
public class ActivityMain extends AppCompatActivity {
private boolean internet;
private boolean isAdsDisabled;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get SharedPreferences
prefManager = new PreferencesManager(this);
isAdsDisabled = prefManager.isAdsDisabled(); // true - disable | false - enabled
// ... здесь код создания Вашего UI
internet = CheckURLConnection.isNetworkAvailable();
// true - disable | false - enabled
if (internet && !isAdsDisabled && isTimeUp()) {
DialogFragment disableAds = new DisableAdsDialog();
if (!disableAds.isResumed()) {
disableAds.show(getSupportFragmentManager(), ConstantHolder.DIALOG_DISABLE_ADS);
}
}
private boolean isTimeUp() {
return System.currentTimeMillis() > prefManager.getEstimatedAdsTime();
}
}
Код класса CheckURLConnection
public class CheckURLConnection {
public static boolean isNetworkAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager)
MyAppClass.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
} else {
return false;
}
}
}
Код класса PreferencesManager
public class PreferencesManager {
private Context mContext;
private static SharedPreferences mSPref;
private SharedPreferences.Editor mSPEditor;
public PreferencesManager(Context context) {
this.mContext = context;
mSPref = mContext.getSharedPreferences(ConstantHolder.APP_PREF, Context.MODE_PRIVATE);
}
// получаем значение состояния рекламы из SharedPreferences
public boolean isAdsDisabled() {
return mSPref.getBoolean(ConstantHolder.APP_PREF_DISABLE_ADS, false);
}
// получаем дату в миллисекундах, когда нужно включить рекламу
public long getEstimatedAdsTime() {
return mSPref.getLong(ConstantHolder.APP_DISABLE_ADS_PERIOD, 0);
}
}
Класс ConstantHolder — класс, в котором я храню константы, чтобы не импортировать их отовсюду, а только из одного места брать (аналог класса R)
public class ConstantHolder {
//Preferences Constants
public static final String APP_PREF = "app_pref";
public static final String APP_PREF_DISABLE_ADS = "disableAds"; // Реклама
public static final String APP_DISABLE_ADS_PERIOD = "disableAdsPeriod"; // Период отключения рекламы
}
И самое интересное — класс-фрагмент отключения рекламы
java код целого класса
public class SettingsAdsFrag extends Fragment
implements View.OnClickListener {
private static final String VIEWED_ZERO_VIDEO_ADS = "0";
private static final int VIEWED_ADS_NUMBER_PER_HOUR = 1;
private static final int VIEWED_ADS_NUMBER_PER_DAY = 5; //5
private static final long DISABLE_ADS_PERIOD_1_HOUR = 60 * 60 * 1000;
private static final long DISABLE_ADS_PERIOD_24_HOURS = 24 * 60 * 60 * 1000;
private Context mContext;
private PreferencesManager prefManager;
private RewardedVideoAd mRewardedVideoAd;
private AdRequest mAdRequest;
private boolean internet;
private boolean readyToPurchase;
private boolean bDisableAds;
private String ready;
private String notReady;
private static int adsViewedCounter = 0;
private Button btnReadyToViewing;
private Button btnDisableAdsPerHour;
private Button btnDisableAdsPerDay;
private TextView tvViewedAds;
private TextView tvEstimatedDate;
private ToggleButton tbDisableAds;
private BillingProcessor bp;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.mContext = context;
// инициализируем свой класс менеджер хранения настроек
prefManager = new PreferencesManager(context);
// получаем текущее состояние интернет соединения
internet = CheckURLConnection.isNetworkAvailable();
// присваиваем "не готово" биллингу
readyToPurchase = false;
// сохраняем в глобальные переменные значения "НЕ ГОТОВО" и "СМОТРЕТЬ" из ресурсов. Сделал так, чтобы по несколько раз к ресурсам не обращаться
ready = context.getString(R.string.txt_cat_ads_ready_for_viewing);
notReady = context.getString(R.string.txt_cat_ads_not_ready_for_viewing);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// отключаю меню у Toolbar
setHasOptionsMenu(false);
// инициализирую биллинг с помощью библиотеки от Anjlab - In-App-Billing-v3
bp = new BillingProcessor(getActivity(),
InAppBillingResources.getRsaKey(), // мой RSA ключ
InAppBillingResources.getMerchantId(), // мой ID продавца из Google Play Developer Console
bpHandler // и сам хэндлер
);
// получаю состояние рекламы из файла настроек
bDisableAds = prefManager.isAdsDisabled(); // true - disable | false - enabled
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// здесь код вывода заголовков в Toolbar, опустил его, т.к. не по теме статьи
// [START AdMob Rewarded Video Ads - инициализация]
mRewardedVideoAd = MobileAds.getRewardedVideoAdInstance(getActivity());
mRewardedVideoAd.setRewardedVideoAdListener(rewardedVideoAdListener);
// такую хитрость я применяю везде, чтобы повторно Google меня не забанил на AdMob (еще 1 год писать апелляции я не хочу!)
if (BuildConfig.DEBUG) {
mAdRequest = new AdRequest.Builder()
.addTestDevice(DeviceHash.getHtcDeviceHash())
.build();
} else {
mAdRequest = new AdRequest.Builder()
.build();
}
// загружаю видео рекламу
loadRewardedVideoAd();
// [END AdMob Rewarded Video Ads]
// создаем View экрана Настройки - Отключение рекламы
View settAdsView = inflater.inflate(R.layout.frag_sett_ads_screen, container, false);
// [START ToggleButton Disable Ads]
tbDisableAds = (ToggleButton) settAdsView.findViewById(R.id.tb_disable_ads);
// если биллинг инициалирован и отключение рекламы куплено, то сохраняем это в SharedPrefereces и устанавливаем "Отключено" на кнопке-переключателе
// да-да-да, я еще раз делаю запрос в Google на предмет покупки. А вдруг юзер руками подправил файл настроек ?
if (readyToPurchase) {
if (bp.isPurchased(InAppBillingResources.getSKU_DisableAds())) {
setAdsDisable();
tbDisableAds.setChecked(false);
}
} else {
// в противном случае читаю то, что записано было
// true - disable | false - enabled
tbDisableAds.setChecked(!bDisableAds);
}
// устанавливаю слушатель нажатия по кнопке
tbDisableAds.setOnClickListener(this);
// [END ToggleButton Disable Ads]
// далее идет элементарная инициализация полей и установка значений для каждой из них. Ничего сложного
// [START TextView Rewarded Video Ads Disabling Guide]
TextView tvRewardedGuide = (TextView) settAdsView.findViewById(R.id.tv_rewarded_video_disabling_guide);
tvRewardedGuide.setText(String.format(getActivity().getString(R.string.txt_cat_ads_disable_tmp_text),
VIEWED_ADS_NUMBER_PER_HOUR,
VIEWED_ADS_NUMBER_PER_DAY));
// [END TextView Rewarded Video Ads Disabling Guide]
// [START TextView Viewed Ads]
tvViewedAds = (TextView) settAdsView.findViewById(R.id.tv_viewed_ads);
// [END TextView Viewed Ads]
// [START Button Ready for Viewing]
btnReadyToViewing = (Button) settAdsView.findViewById(R.id.btn_ready_to_viewing);
btnReadyToViewing.setText(notReady);
btnReadyToViewing.setEnabled(false);
btnReadyToViewing.setOnClickListener(this);
// [END Button Ready for Viewing]
// [START Button Disable Ads Per Hour]
btnDisableAdsPerHour = (Button) settAdsView.findViewById(R.id.btn_disable_ads_per_hour);
btnDisableAdsPerHour.setEnabled(false);
btnDisableAdsPerHour.setOnClickListener(this);
// [END Button Disable Ads Per Hour]
// [START Button Disable Ads Per Day]
btnDisableAdsPerDay = (Button) settAdsView.findViewById(R.id.btn_disable_ads_per_day);
btnDisableAdsPerDay.setEnabled(false);
btnDisableAdsPerDay.setOnClickListener(this);
// [END Button Disable Ads Per Day]
// [START TextView Last Viewing Time]
tvEstimatedDate = (TextView) settAdsView.findViewById(R.id.tv_estimated_date);
// [END TextView Last Viewing Time]
// обновляю значения текстовых полей и текста кнопок
updateTextView();
updateButtons();
return settAdsView;
}
@Override
public void onResume() {
// Rewarded Video Ad - необходимо для поддержания жизненного цикла видео рекламы
if (mRewardedVideoAd != null) {
mRewardedVideoAd.resume(getActivity());
}
super.onResume();
// updateUI - обновляю экран
updateTextView();
updateButtons();
}
@Override
public void onPause() {
// Rewarded Video Ad - необходимо для поддержания жизненного цикла видео рекламы
if (mRewardedVideoAd != null) {
mRewardedVideoAd.pause(getActivity());
}
super.onPause();
}
@Override
public void onDestroy() {
// Rewarded Video Ad - необходимо для поддержания жизненного цикла видео рекламы
if (mRewardedVideoAd != null) {
mRewardedVideoAd.destroy(getActivity());
}
super.onDestroy();
}
// обработчик нажатий по кнопкам
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tb_disable_ads:
// disable ads ? setText(..OFF) : setText(..ON)
// if state ON (disableAds - false)
// true - ads disabled; false - ads enabled
if (!bDisableAds && readyToPurchase) {
// если реклама не отключена и биллинг готов, выполняем покупку "Отключить рекламу навсегда платно"
bp.purchase(getActivity(), InAppBillingResources.getSKU_DisableAds());
}
break;
case R.id.btn_ready_to_viewing:
// если видео реклама загружена, то запускаем ее просмотр
if (mRewardedVideoAd.isLoaded()) {
mRewardedVideoAd.show();
}
break;
case R.id.btn_disable_ads_per_hour:
// отключаем рекламу 1 час
disableAdsPerPeriod(DISABLE_ADS_PERIOD_1_HOUR);
adsViewedCounter--;
updateTextView();
updateButtons();
break;
case R.id.btn_disable_ads_per_day:
// отключаем рекламу 1 день
disableAdsPerPeriod(DISABLE_ADS_PERIOD_24_HOURS);
clearAdsCounter();
updateTextView();
updateButtons();
break;
default:
break;
}
// true - ads disabled;
// false - ads enabled
if (bDisableAds) {
tbDisableAds.setChecked(false);
showSnackbar();
}
}
// ==========================================================
// [START R E W A R D E D V I D E O A D]
private RewardedVideoAdListener rewardedVideoAdListener = new RewardedVideoAdListener() {
@Override
public void onRewardedVideoAdLoaded() {
// когда видео реклама будет полностью загружена, влючаем кнопку просмотра
btnReadyToViewing.setText(ready);
btnReadyToViewing.setEnabled(true);
}
@Override
public void onRewardedVideoAdOpened() {
}
@Override
public void onRewardedVideoStarted() {
// устанавливаем НЕ ГОТОВО на кнопку и выключаем ее
btnReadyToViewing.setText(notReady);
btnReadyToViewing.setEnabled(false);
}
@Override
public void onRewardedVideoAdClosed() {
// загружаем новую видео рекламу
loadRewardedVideoAd();
}
@Override
public void onRewarded(RewardItem rewardItem) {
// если счетчик рекламы меньше количества просмотров для отключения на день, то инкрементируем его
if (adsViewedCounter < VIEWED_ADS_NUMBER_PER_DAY) {
adsViewedCounter++;
}
// обновляем поля экрана
updateTextView();
updateButtons();
}
@Override
public void onRewardedVideoAdLeftApplication() {
}
@Override
public void onRewardedVideoAdFailedToLoad(int i) {
// загружаем новую рекламу
loadRewardedVideoAd();
}
};
private void loadRewardedVideoAd() {
// если есть доступ в сеть Интернет, грузим видео рекламу
if (internet) {
mRewardedVideoAd.loadAd(mContext.getString(R.string.admob_rewarded_video_id), mAdRequest);
}
}
// [END R E W A R D E D V I D E O A D]
// ==========================================================
// обновляем текстовые поля согласно условий и значения счетчика просмотров
// показываем дату возобновления рекламы в приложении, если ее отключили временно
private void updateTextView() {
// true - disable | false - enabled
if (bDisableAds) {
tvViewedAds.setText(String.valueOf(adsViewedCounter));
tvEstimatedDate.setText("");
} else {
// [START U P D A T E T E X T V I E W : tvViewedAds]
if (adsViewedCounter > 0 && adsViewedCounter <= VIEWED_ADS_NUMBER_PER_DAY) {
String strViewedAdsCount = adsViewedCounter + " / " + VIEWED_ADS_NUMBER_PER_DAY;
tvViewedAds.setText(strViewedAdsCount);
} else {
tvViewedAds.setText(VIEWED_ZERO_VIDEO_ADS);
}
// [END U P D A T E T E X T V I E W : tvViewedAds]
// [START U P D A T E T E X T V I E W : tvEstimatedDate]
long estimatedDate = prefManager.getEstimatedAdsTime();
long currentDate = System.currentTimeMillis();
if (estimatedDate != 0 && estimatedDate > currentDate) {
String strEstimatedDate = convertTime(estimatedDate);
String strEstimatedDateFinal = "<b>" + mContext.getString(R.string.txt_tv_header_estimated_time).toUpperCase() + "</b>"
+ "<br>"
+ strEstimatedDate;
tvEstimatedDate.setText(Html.fromHtml(strEstimatedDateFinal));
}
// [END U P D A T E T E X T V I E W : tvEstimatedDate]
}
}
// обновляем кнопки
private void updateButtons() {
// 0
if (adsViewedCounter == 0) {
btnDisableAdsPerHour.setEnabled(false);
btnDisableAdsPerDay.setEnabled(false);
}
// 1 - 4
if (adsViewedCounter > 0 && adsViewedCounter < VIEWED_ADS_NUMBER_PER_DAY) {
btnDisableAdsPerHour.setEnabled(true);
}
// 5
if (adsViewedCounter == VIEWED_ADS_NUMBER_PER_DAY) {
btnDisableAdsPerHour.setEnabled(true);
btnDisableAdsPerDay.setEnabled(true);
}
}
// метод отключения рекламы на период
private void disableAdsPerPeriod(long disablePeriod) {
// текущая дата в миллисекундах
long currentDate = System.currentTimeMillis();
// дата возобновления рекламы в приложении
long estimatedDate = currentDate + disablePeriod;
// сохраняем дату в файл настроек
prefManager.setEstimatedDate(estimatedDate);
// отключаем баннер внизу экрана
AdMobAds.disableBanner(getActivity(), true);
}
// обнуляем счетчик
private void clearAdsCounter() {
adsViewedCounter = 0;
}
// конвертер миллисекунд в дату и время согласно формату
public String convertTime(long time) {
Date date = new Date(time);
Format format = new SimpleDateFormat("dd MMM yyyy @ HH:mm:ss");
return format.format(date);
}
// ==========================================================
// [START IN APP BILLING]
BillingProcessor.IBillingHandler bpHandler = new BillingProcessor.IBillingHandler() {
@Override
public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) {
// Called when requested PRODUCT ID was successfully purchased
// Вызывается, когда запрашиваемый PRODUCT ID был успешно куплен
if (bp.isPurchased(productId)) {
// сохраняем новое состояние рекламы "отключена" и устанавливаем "Выключено" для кнопки-переключателя
setAdsDisable();
tbDisableAds.setChecked(false);
// перезапускаем приложение
restartDialog();
} else {
// иначе устанавливаем "Включено"
tbDisableAds.setChecked(true);
}
}
@Override
public void onPurchaseHistoryRestored() {
//Вызывается, когда история покупки была восстановлена,
// и список всех принадлежащих идентификаторы продуктов был загружен из Google Play
}
@Override
public void onBillingError(int errorCode, @Nullable Throwable error) {
// Вызывается, когда появляется ошибка. См. константы класса
// для получения более подробной информации
}
@Override
public void onBillingInitialized() {
// Вызывается, когда bp был инициализирован и он готов приобрести
readyToPurchase = true;
}
};
// [START IN APP BILLING]
// ==========================================================
// метод сохранения отключенного состояния рекламы
private void setAdsDisable() {
prefManager.setAdsDisabled();
}
// диалог перезапуска приложения
// [START restartDialog]
private void restartDialog() {
AlertDialog.Builder builder;
View alertLayout = View.inflate(mContext, R.layout.dialog_restart, null);
if (prefManager.getAppTheme() == 0) {
builder = new AlertDialog.Builder(getActivity(), R.style.AppThemeDialogStyleLight);
} else {
builder = new AlertDialog.Builder(getActivity(), R.style.AppThemeDialogStyleDark);
}
builder.setTitle(getActivity().getString(R.string.msg_notification_Title));
builder.setView(alertLayout);
builder.setPositiveButton(getActivity().getString(R.string.ans_restart),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
restartApp();
}
});
builder.show();
}
// [END restartDialog]
// метод перезапуска приложения
// [START restartApp]
private void restartApp() {
Intent i = getActivity().getPackageManager().getLaunchIntentForPackage(getActivity().getPackageName());
if (i != null) {
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
getActivity().startActivity(i);
}
}
// [END restartApp]
// Snackbar с уведомлением, что рекламу уже отключена. Если пользователь снова кликнет по кнопке отключения рекламы
private void showSnackbar() {
Snackbar.make(getActivity().getWindow().getDecorView().getRootView(),
getActivity().getResources().getString(R.string.advertising_is_already_disabled),
Snackbar.LENGTH_SHORT).show();
}
}
Класс работы с баннером AdMob. Ничего сложного, публикую для ознакомления. Хотя его же и на StackOverFlow ни раз выкладывал
public class AdMobAds {
public static void disableBanner(final Activity activity, boolean disableAds) {
final View adsContainer = activity.findViewById(R.id.container);
final AdView adView = (AdView) activity.findViewById(R.id.adView);
if (disableAds) {
adView.setVisibility(View.GONE);
adsContainer.setPadding(0, 0, 0, 0);
} else {
AdRequest adRequest;
if (BuildConfig.DEBUG) {
adRequest = new AdRequest.Builder()
.addTestDevice(DeviceHash.getHtcDeviceHash())
.build();
} else {
adRequest = new AdRequest.Builder()
.build();
}
adView.loadAd(adRequest);
adView.setAdListener(new AdListener() {
@Override
public void onAdFailedToLoad(int errorCode) {
MyAppLogs.show("[bottom-banner] >> onAdFailedToLoad: реклама не загружена\terrorCode = " + errorCode + ".");
super.onAdFailedToLoad(errorCode);
}
@Override
public void onAdLoaded() {
super.onAdLoaded();
MyAppLogs.show("[bottom-banner] >> onAdLoaded");
if (adView.getVisibility() == View.GONE) {
adView.setVisibility(View.VISIBLE);
}
View adsContainer = activity.findViewById(R.id.container);
adsContainer.setPadding(adsContainer.getPaddingLeft(),
adsContainer.getPaddingTop(),
adsContainer.getPaddingRight(),
adView.getHeight() + 8);
}
});
}
}
}
[вернуться к содержанию]
Заключение
Вот и все. Суть статьи — поделиться с обществом своей идеей и ее реализацией. А также получить приглашение на Хабрахабр, если кому-то понравится то, чем я поделился. Буду рад и благодарен за ваше мнение в вопросах доработки кода и/или идеи. Если понадобится дополнительное пояснение — пишите, я в кратчайшие сроки внесу правки в статью или дам ответ в комментариях.
Ссылку на приложение по понятным причинам не публикую в открытом доступе. Этика есть этика! Кому нужно — дам в лс.
Статистика AdMob пока еще сырая, с момента внедрения данной альтернативы прошло всего ~2 недели. Не все пользователи обновились. Но точно есть те, кто пользуется таким способом отключения баннера внизу.
UPDATE: Ссылка на статью с результатами ИТОГ 3-х месяцев: Альтернатива платному отключению рекламы в бесплатном приложении Android
Благодарю всех, кто дочитал статью до конца!