Меня зовут Александр, я работаю тренером по питанию, а в свободное время, по вечерам — инди разработчик под ОС Android. Сегодня хочу с вами поделиться опытом реализации альтернативного платному способу отключения рекламы в приложении — отключение рекламы за просмотр рекламы (AdMob Rewarded Video Ads). Интересно? Тогда добро пожаловать под кат.
Как все было ?
В далеком 2013 году я решил заняться разработкой приложений под Android, начал читать тематические книги, статьи, смотрел видео уроки и т.д. Написал первое недоприложение и приуныл, т.к. хотелось сделать что-то полезное, нужное обществу, а идей не было. В 2014 меня знакомый попросил разработать для него мобильный справочник по синтаксису платформы Arduino (там С язык). С огромным желанием я взялся за этот проект и реализовал первую версию для Android 3.0+. Через время решено было усовершенствовать ее, и так появилась вторая версия (для Android 4.0+). Обе они бесплатные с баннером от AdMob внизу и платным его отключением. Все было хорошо, пока мне не стали писать, что ~150-170 рублей РФ дороговато для отключения рекламы навсегда в их любимом справочнике. На что я ответил «бартерным» решением вопроса — пользователь может отключить баннер внизу на время за просмотр видео рекламы от AdMob.
[вернуться к содержанию]
Реализация, часть 1: Принцип работы (словами)
При запуске приложения пользователю будет показан Dialog, с предложением отключить рекламу, если, конечно, он ранее ее не отключил или отсутствует подключение к сети Интернет. В случае положительного ответа, приложение показывает фрагмент с кнопками, с помощью которых и можно выполнить отключение рекламы в приложении удобным способом.
[вернуться к содержанию]
Реализация, часть 2: Внешний вид
[вернуться к содержанию]
Реализация, часть 3: Принцип работы (java код)
Код главной Activity
public class ActivityMain extends AppCompatActivity {
private static boolean internet = CheckURLConnection.isNetworkAvailable();
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
// 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);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null;
}
}
Код класса 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"; // Период отключения рекламы
}
И самое интересное — класс-фрагмент отключения рекламы
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 недели. Не все пользователи обновились. Но точно есть те, кто пользуется таким способом отключения баннера внизу.
Благодарю всех, кто дочитал статью до конца!
Комментарии (38)
petrovichtim
24.02.2018 17:37Спасибо за статью, интересна экономика этого решения. Стоит ли оно того?
web_alex Автор
24.02.2018 18:30Большую роль в данном решении играет возможность отключать рекламу бесплатно. Как я говорил в статье, что пользователи не хотят платить и жалуются в отзывах к приложению. Сопровождают свои комментарии заниженными оценками. То есть само приложение их устраивает на 1`000`000%, но реклама (маленький баннер внизу) + еще и разработчик жлоб, хочет нажиться, просит денег за отключение. Поэтому 3 или 2 звезды вообще. Сначала я писал аргументы и доводы в ответ, старался как-то дать понять, что реклама внизу и платное ее отключение — нормально! Но потом понял, что это бессмысленно. Так я и пришел к тому, что дал пользователю возможность самому принимать решение: платно или бесплатно он будет избавляться от баннера внизу.
Если говорить о цифрах, то имеем следующее (период 01.02 — 24.02.2018, сегодня):
bottom banner:
* 49`047 показов
* 217 кликов
* 0.44% CTR
* 0.05$ за клик
* сумма: 10.68$
rewarded video ads:
* 3`807 показов
* 7 кликов
* 0.18 CTR
* 0.27$ за клики
* сумма: 1.92$
Видно, что сам баннер внизу ГОРАЗДО прибыльный, оно то и понятно. Он расположен всегда внизу экрана, а до видео рекламы нужно еще и добраться по пунктам меню или через положительный ответ на вопрос в DialogFragment, который я показываю пользователю при старте приложения. Код, кстати, приложил к статье.
Еще немного цифр для Вас:
активных установок: 18`428 из общего числа установок — 62`004
средняя оценка: 4.66 по результатам 1`452 оцениваний
Спасибо за статью, ...
Пожалуйста. Рад тому, что Вам она понравилась.schetilin
24.02.2018 19:26Не про Ваше приложение, а про рекламу.
То есть само приложение их устраивает на 1`000`000%, но реклама (маленький баннер внизу) ...
Есть еще один неприятный момент. Этот маленький баннер внизу экрана содает жуткие тормоза. Что виновато? Может криво блок рекламы вставлен, может слабый телефон. Но во многих приложениях, при подключении к интернету (без интернета не показывает :)), наблюдаются фризы при смене баннера.KodyWiremane
25.02.2018 11:53Может, синхронный запрос какой-нибудь, или проверка в цикле. У нас же 5g уже везде, новые баннеры грузятся мгновенно, можно не париться с оптимизацией. А могли и ядра в фоновых процессах завязнуть. Появился интернет, все проснулись — и в сеть.
web_alex Автор
25.02.2018 12:39Появился интернет, все проснулись — и в сеть.
Да, бывает и так. Весь смартфон подвисает в момент тотальной синхронизации данных на смартфоне и различных сервисов.
TrllServ
24.02.2018 19:58Когда реклама сразу на экране, сверху или снизу, она мешает не только эстетически, но и вполне себе фукционально — например случайные тапы могут увести по ссылке рекламной. Если это во время игры приводит к потере контроля. А дальше зависит от типа игры, в интерактивных играх это всегда поражение, и вполне ожидаемо после 2-3 раз на эмоциях пользователь проставляет 2-3 звезды.
Собсно, это я к тому, что когда реклама на экране, больше переходов может быть не от желания её просмотреть, а отмисскликовмисстапов.web_alex Автор
24.02.2018 20:48Поэтому Google/AdMob установили привила, что нельзя ставить контролы около баннеров. И нужно явно отделять баннер от UI. Я, к примеру, добавил отступ в 8dp (в коде есть).
TrllServ
24.02.2018 23:18А если это «всевдошутер» где тапаешь в любом месте экрана, и если ты ещё не наловчился, запросто можно тыкнуть в рекламу? тоже самое верно для экранов 4-4.5, как у меня, потому что лопата на 5.5-6 уже не очень комфортна в виде мобилки.
Для тех целей где нужен экран больше у меня 7" планшетка имеется :)
Мисстапы случаются у многих, особенно поначалу (сужу исключительно по своему опыту и пробному приложению).web_alex Автор
24.02.2018 23:36Я с Вами согласен на 1оо% на счет мисстапов! Главное, чтобы они случались не по вине неправильной расстановки контролов на экране.
Хочу еще добавить к Вашему комментарию, что нормальный разработчик не будет вставлять баннер в шутере или иной динамичной игре. Для них существуют межстраничные (Interstitial Ads) и видео (Rewarded Video Ads) рекламы, которые показываются пользователю после проигрыша или в режиме паузы. Показывать баннер в динамичной игре — хардкор какой-то. Мистапов будет ооочень много и «мигания» объявлений будут отвлекать внимание игрока.
А мое решение в таких играх просто избавит от межстраничных или видео реклам после проигрыша. Хотя не каждый захочет его применять, т.к. выгодней в играх за просмотр видео рекламы давать монеты/жизни/золото/ключи/и другие плюшки.
// я так это вижу, другие разработчики могут считать по-другому))
web_alex Автор
24.02.2018 18:44Стоит ли оно того?
Мое решение хорошо подойдет для приложений с бОльшей аудиторией. В моем же случае, если говорить только о доп. доходе с рекламы, не стоит оно того. Пока не стоит. Посмотрим, что будет через 1-2 месяца, когда пользователи обновятся и привыкнут. Когда разрабатывал такой вид отключения, моя цель была — убавить негатив в комментариях и сохранить рейтинг приложения в Google Play.
a-tk
25.02.2018 11:26Статическая переменная для проверки наличия интернета… Что-то здесь не так.
web_alex Автор
25.02.2018 12:40Что именно не так?
anegin
27.02.2018 01:58Статическая переменная internet будет инициализирована только один раз при создании класса активити. Какой в ней смысл?
web_alex Автор
27.02.2018 02:04Во время создания ActivityMain получаем информацию о наличии доступа в сеть Интернет. Если есть, то делаем по коду, иначе пропускаем все.
В классе ActivityMain есть еще несколько мест, где нужно считать значение из internet. Поэтому переменная является глобальной.
anegin, или Вы хотили сказать, что модификатор static лишний?anegin
27.02.2018 14:38К тому моменту, когда нужно будет прочитать значение из переменной internet, ее значение может быть уже не актуальным, т.е. сети уже может не быть.
В нужных местах достаточно вызывать CheckURLConnection.isNetworkAvailable(), чтобы узнавать о наличии сети по факту в нужный момент.
А так получается, что наличие сети проверяется один раз при создании класса активити и зачем-то сохраняется в переменной, к тому же еще private static.
Static-поля и методы нужны для доступа к ним без создания экземпляра класса. Например, в static-методах этого класса или inner-static-классах — это если поле private. Или, например, как константы для доступа из других классов — это если поле не private.
bogotoff
27.02.2018 01:38На словах выглядит интересно, однако у меня есть одно подозрение.
Если пользователь посмотрит рекламу 5 раз подряд, и он не кликнет по ссылке, то его ценность сильно уменьшится и этому пользователю будут показываться дешевая реклама. Т.е. Вы получите меньше денег за рекламу, чем если бы вы растянули эти 5 показов на больший временной отрезок.web_alex Автор
27.02.2018 01:58Дельное замечание! Можете дать ссылку на подтверждение Ваших слов?
В моем решении есть два зайца, и обоих хочется поймать! Первый, самый ценный/важный, — заяц-хранитель рейтинга приложения. Он выполняет функции успокаивания пользователей в вопросах отключения рекламы. Платить не хочешь — смотри рекламу.
Второй заяц — не менее важная персона, а именно: доход с рекламы и ее платном отключении. И у этого зайца появляются какие-то прихрамывания — Ваше замечание касательно удешевления рекламы.
Но стоит помнить, что платят то за клики по баннеру, а не просмотры.
Поэтому я Вас прошу предоставить подтверждение/факты к Вашему дельному замечанию.
Если Ваше утверждение верно на 1оо%, то придется переписывать код.bogotoff
27.02.2018 03:19Но стоит помнить, что платят то за клики по баннеру, а не просмотры.
Я говорю про видео рекламу, и тап по нему после просмотра, а не про баннер, который висит все время в меню.
Можете дать ссылку на подтверждение Ваших слов?
К сожалению официального подтверждения у меня нет. Я обычный программист, и это утверждение слышал внутри нашей компании. Игнорировать думаю не стоит.
Даже если я прав, возможно не стоит переписывать код. Здесь говорится, что дешевая реклама в некоторых случаях выгоднее. Стоит изучить аналитику в admob.
HueyOne
27.02.2018 12:14Когда уже запилят адблок для мобилы...
web_alex Автор
27.02.2018 12:22Вы же понимаете, что для кого-то реклама — источник дохода?! И блокируя ее показ, Вы лишаете человека/компанию заработка. Это равносильно пиратству. Кто-то потратил часть своей жизни на создание, к примеру, цифрового продукта, и хочет, чтобы за его труд потребители заплатили. А пираты берут и крадут у него эту возможность.
Чтобы проникнуться этой мыслью, нужно сначала самому что-то создать для общества.
Каюсь! Я тоже раньше не уважал чужой труд! Сейчас же все наоборот!
Кстати, чтобы урегулировать «баталии» между правообладателями и пирами, придумали Open Source проекты. Они бесплатны в распространении, понятны в принципах работы. Но для их существования нужны денежные вливания, то есть донат/пожертвования. Как Вы относитесь к Open Source и донату?TrllServ
27.02.2018 15:16Игроки которые не покупают дополнительный контент в free to play, видимо тоже «равносильны пиратам»?
А может не нужно везде пихать это избитое слово?
Украсть «возможность» — не возможно.
Есть и третья возможность, которой часто пользуются, те кто вышел из пиратсва (а это между делом, большая часть СНГ) — бесплатно для частного пользования, платно для коммерческого.
Чем больше будет такого софта, тем спокойнее и легче будет жить в цифровую эпоху.
web_alex Автор
27.02.2018 20:09Игроки которые не покупают дополнительный контент в free to play, видимо тоже «равносильны пиратам»?
Нет, они не пираты.
А может не нужно везде пихать это избитое слово?
Никогда ранее не слышал такого.
Украсть «возможность» — не возможно.
Есть и третья возможность, которой часто пользуются, те кто вышел из пиратсва (а это между делом, большая часть СНГ) — бесплатно для частного пользования, платно для коммерческого.
И как узнать о типе использования? Ведь можно на дому заниматься коммерцией.
leshakk
27.02.2018 12:30Есть AdAway, практически полное уничтожение рекламы в андроиде. Причём никакой нагрузки на систему: домены, с которых качается реклама, блокируются на уровне DNS ( прописываются в /etc/hosts )
Списки доменов обновляются по сети, аналогично адблоку.
Недостаток: нужен рутweb_alex Автор
27.02.2018 20:13И как тогда монетизировать свой труд, если отовсюду народ старается что-то украсть, заблокировать и т.д.?
Как бы Вы, leshakk, монетизировали свое приложение, зная, что можно заблокировать рекламу, взломать покупку полной версии и т.д.?leshakk
27.02.2018 20:50Уж точно не рекламой.
Как пользователь скажу, что если мне ОЧЕНЬ понравилась прога,
я лучше заплачу за неё.
Вот пара примеров:
1.Утилита для диагностики вариаторов Nissan по OBDII.
Написана энтузиастом с автомобильного форума, писалась по сути «для себя».
Первые версии были бесплатны. Потом автор сдела программу платной,
но предыдущие версии оставил в свободном доступе на сайте.
По большому счёту, функционал бесплатной версии вполне достаточен,
дальнейшие отличаются только большим удобством, но я купил,
чтобы поблагодарить автора, учитывая, что стоимость программы в 5-10 раз ниже суммы,
которую просят за «компьютерную диагностику» в сервисе.
2. Программа учёта расхода топлива. Тоже написана энтузиастом.
Есть как платная, так и бесплатная версии.
В платной добавлена возможность синхронизации данных с облаком.
Стоимость символическая. Купил.
И что характерно, даже в бесплатных версиях этих программ не было никакой рекламы.
Чтож, их авторы уважают своих пользователей, потому и сами достойны уважения.
TrllServ
Правильно ли я понимаю, что это сделано чтоб можно смотреть рекламу с «наземного» инета, что б потом не тратить трафик с мобильного?
PS:
Хорошая идея. Было б вообще хорошо, если развить далее: «посмотреть» на пару дней или неделю вперед. Но думаю, тут уже можно столкнуться с ограничением со стороны рекламодателя.
web_alex Автор
В играх разработчики дают своим пользователям золото, монеты или дополнительные жизни, если те посмотрят 1-2 ролика. Я же даю то, что нужно моим пользователям. «И волки сыты, и овцы целы!»
Я хотел так сделать, но столкнулся с проблемой — рекламы для текущего региона, где пользователь находится, сейчас может не быть в нужном количестве. Поэтому решено было не развивать механизм хранения уже выполненных просмотров, а запустить в тестовом режиме то, что я описал в статье.
Благодарю Вас за конструктивный комментарий!