Максимизируем прибыль от своих Android-приложений
Сегодня я хотел бы поделиться с уважаемым сообществом своим опытом получения прибыли от рекламы в Android-приложениях. Почему именно от рекламы? Ну, во-первых, исторически так сложилось, что некоторое время это был практически единственный способ монетизации приложений для разработчиков из России. Во-вторых, мой опыт говорит о том, что бесплатное приложение с интегрированной рекламой может быть в разы выгоднее для разработчика чем продажа того же приложения. Например, у меня лучший показатель на данный момент – это около 2k$ в месяц исключительно за рекламу. Согласитесь, что даже на такой уровень продаж выйти не так-то просто, особенно если вы одиночка, размещающий на Маркете свое первое приложение.
Итак, как же мы будем максимизировать эту самую прибыль? Идея заключается в том, что бы объединить в своем приложении баннеры из нескольких рекламных сетей, с целью повышения Fill Rate (наполняемости баннерами), количества кликов и, следовательно, вашей прибыли. Разумеется, я знаю о существовании adwhirl, mobclix, smaato и им подобных. Но зачем нам посредники? К тому же интересно понимать и контролировать, как все это происходит. Конечно, не маловажным аспектом является так же «удобство» приложения для рекламы и место размещения баннеров, но это тема для отдельного разговора.
В качестве примера, я выбрал три рекламных сети. Рассмотрим каждую из них более подробно.
1. admob
Самая популярная и часто используемая разработчиками Android рекламная сеть.
Плюсы:
+ Развитый, хорошо документированный API.
+ На данный момент нет проблем с выплатами, за исключением не значительных задержек, доступен Wire (перевод на банковский счет). Ранее, для России были только чеки, что создавало определенные трудности с получением денег, не говоря уже об огромных временных затратах.
Минусы:
— Низкая цена за клик.
— Очень неохотно реагирующий на обращения саппорт.
В целом, дает неплохие результаты, если использовать ключевые слова и геотаргетинг. Лучшая, на мой взгляд, рекламная сеть после Quattro Wireless который на данный момент не существует (был куплен и закрыт Apple, видимо как основной конкурент iAd).
2. InMobi
Новинка для меня, решил попробовать, прочитав положительные отзывы (здесь в том числе).
Плюсы:
+ Самый удобный и проработанный web-портал из всех прочих.
+ Достаточно оперативный саппорт.
Минусы:
— Крайне глючный и не стабильный SDK для Android. Работа над ним явно ведется, выходят новые билды, возможно в будущем ситуация исправится. Но на данный момент мне удалось подключить их рекламу с большим трудом.
Какие-то выводы по отдаче для себя я пока сделать не могу, время покажет…
3. AdClick
Это единственная, насколько я знаю, российская рекламная сеть.
Плюсы:
+ Русскоговорящий, оперативно решающий проблемы и отвечающий на вопросы саппорт.
+ Множество удобных (в том числе для России) способов вывода заработанных денег, до недавнего времени были даже Яндекс-деньги.
Минусы:
— Совершенно куцый API. Хотя для кого-то это может быть плюсом, так как из этого следует простота интеграции в свое приложение.
— Большое количество adult-баннеров (фильтров тоже нет).
Хочу отметить этого поставщика рекламы, как самого перспективного для российского разработчика. Пока еще все довольно сыровато, но у меня сложилось очень хорошее впечатление от работы с ними.
Теперь перейдем непосредственно к реализации нашей задачи. Первое что нам понадобится – это подключить библиотеки с SDK соответствующих рекламных сетей в проект. Их можно скачать после регистрации на сайте рекламной сети. На этом я подробнее останавливаться не буду, так как все описано неоднократно и не должно составить для среднестатистического Android-разработчика никакого труда.
Далее, добавляем в манифест требуемые Permission и часть нужную для работы admob:
<?xml version=«1.0» encoding=«utf-8»?> <manifest xmlns:android=«schemas.android.com/apk/res/android» package=«ru.coder1cv8.sample» android:versionCode=«1» android:versionName=«1.0»> <uses-sdk android:minSdkVersion=«3» /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Main" android:label="@string/app_name"> <intent-filter> <action android:name=«android.intent.action.MAIN» /> <category android:name=«android.intent.category.LAUNCHER» /> </intent-filter> </activity> <meta-data android:value=«Your AdMob publisher ID» android:name=«ADMOB_PUBLISHER_ID» /> <activity android:name=«com.admob.android.ads.AdMobActivity» android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:configChanges=«orientation|keyboard|keyboardHidden» /> <receiver android:name=«com.admob.android.ads.analytics.InstallReceiver» android:exported=«true»> <intent-filter> <action android:name=«com.android.vending.INSTALL_REFERRER» /> </intent-filter> </receiver> <meta-data android:value=«true» android:name=«ADMOB_ALLOW_LOCATION_FOR_ADS»/> </application> <uses-permission android:name=«android.permission.INTERNET» /> <uses-permission android:name=«android.permission.ACCESS_COARSE_LOCATION» /> <uses-permission android:name=«android.permission.ACCESS_NETWORK_STATE» /> </manifest>
Права на «INTERNET» и «COARSE_LOCATION» стандартные для всех рекламных сетей. Кроме этого, AdClick требует дополнительно «NETWORK_STATE».
Затем в layout.xml нашей Activity добавляем контейнер для баннера:
<?xml version=«1.0» encoding=«utf-8»?> <LinearLayout xmlns:android=«schemas.android.com/apk/res/android» android:orientation=«vertical» android:layout_width=«fill_parent» android:layout_height=«fill_parent»> <LinearLayout android:id="@+id/adContainer" android:layout_marginTop=«5dip» android:layout_marginBottom=«5dip» android:orientation=«vertical» android:layout_width=«fill_parent» android:gravity=«center» android:layout_height=«wrap_content»> <LinearLayout android:id="@+id/adClick" android:layout_width=«320dip» android:layout_height=«wrap_content» android:visibility=«invisible»/> </LinearLayout> </LinearLayout>
Здесь я использую LinearLayout, на котором в дальнейшем будет программно размещаться View с тем или иным баннером. В принципе, можно было бы добавить баннеры сразу в разметке и потом только управлять их видимостью (как сделано с AdClick), но баннер от admob-а сразу после добавления начинает слать запросы на сервер, а такое поведение в данном случае, нам не совсем подходит.
Теперь перейдем к реализации класса, который будет получать и отображать рекламные баннеры. Здесь я хочу обратить ваше внимание на два момента.
Первое: порядок получение баннеров. Я выбрал такую стратегию: получаем баннер admob, если получен – отображаем, если нет, переходим к получению баннера InMobi, затем, при неудаче – пробуем получить AdClick. Такой порядок обусловлен тем, что AdClick не имеет в своем API поддержки событий, соответственно может быть получен только в последнюю очередь. А выбор между admob и InMobi вы можете сделать на свое усмотрение.
И второе: я не стал реализовывать refresh баннеров, потому что считаю это излишним в большинстве случаев. Я стараюсь не размещать баннеры в основном окне (пользователь скорее удалит приложение, чем нажмет на реально мешающий баннер), а предпочитаю делать это в настройках или меню, то есть на таких формах, где пользователь, скорее всего не будет задерживаться более 1 минуты. А обновлять баннер чаще и не стоит. Хотя, строго говоря, баннер AdClick refresh-ся сам в независимости от желания разработчика.
Если же ваше приложение построено таким образом, что обновление баннеров просто необходимо, оставим его реализацию в качестве домашнего задания.
package ru.coder1cv8.sample; import java.util.Date; import adclick.mod.AdClick; import android.app.Activity; import android.location.Location; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout.LayoutParams; import com.admob.android.ads.AdListener; import com.admob.android.ads.AdView; import com.inmobi.androidsdk.EducationType; import com.inmobi.androidsdk.EthnicityType; import com.inmobi.androidsdk.GenderType; import com.inmobi.androidsdk.InMobiAdDelegate; import com.inmobi.androidsdk.impl.InMobiAdView; public class AdSupport { private static final String TAG = «AD»; private static final String IN_MOBI_TAG = «IN_MOBI_TAG»; private static final String AD_MOB_TAG = «AD_MOB_TAG»; public static final String KEYWORDS = «Your keywords»; public static final String IN_MOBI_ID = «Your InMobi app ID»; public static final String AD_CLICK_ID = «Your AdClick app ID»; private Activity act; private InMobiAdView inMobi; private boolean single; private LinearLayout parent; public AdSupport(Activity act, int adContainer) { super(); this.act = act; single = true; parent = (LinearLayout) act.findViewById(adContainer); } public void getAds() { getAdMob(); } private void getAdMob() { Log.d(TAG, «AdMob: getAdMob»); final AdView adMob = new AdView(act); adMob.setRequestInterval(0); adMob.setAdListener(new AdListener() { @Override public void onReceiveRefreshedAd(AdView arg0) { Log.d(TAG, «AdMob: onReceiveRefreshedAd»); } @Override public void onReceiveAd(AdView arg0) { Log.d(TAG, «AdMob: onReceiveAd»); View inMobiView = parent.findViewWithTag(IN_MOBI_TAG); if (inMobiView != null) { inMobiView.setVisibility(View.INVISIBLE); } View adClickView = parent.findViewById(R.id.adClick); if (adClickView != null) { adClickView.setVisibility(View.INVISIBLE); } arg0.setVisibility(View.VISIBLE); } @Override public void onFailedToReceiveRefreshedAd(AdView arg0) { Log.d(TAG, «AdMob: onFailedToReceiveRefreshedAd»); } @Override public void onFailedToReceiveAd(AdView arg0) { Log.d(TAG, «AdMob: onFailedToReceiveAd»); arg0.setVisibility(View.INVISIBLE); getInMobi(); } }); adMob.setKeywords(KEYWORDS); adMob.setTag(AD_MOB_TAG); if (parent.findViewWithTag(AD_MOB_TAG) == null) { parent.addView(adMob); } } private void getInMobi() { Log.d(TAG, «InMobi: getInMobi»); final InMobiAdDelegate adDelegate = new InMobiAdDelegate() { @Override public boolean testMode() { return false; } @Override public String siteId() { return IN_MOBI_ID; } @Override public String searchString() { return null; } @Override public String postalCode() { return null; } @Override public String keywords() { return KEYWORDS; } @Override public boolean isPublisherProvidingLocation() { return false; } @Override public boolean isLocationInquiryAllowed() { return true; } @Override public String interests() { return null; } @Override public int income() { return 0; } @Override public GenderType gender() { return GenderType.G_M; } @Override public EthnicityType ethnicity() { return null; } @Override public EducationType education() { return null; } @Override public Date dateOfBirth() { return new Date(); } @Override public Location currentLocation() { return null; } @Override public String areaCode() { return null; } @Override public int age() { return 0; } @Override public void adRequestFailed(InMobiAdView arg0) { if (single) { // событие вызывается 2 раза почему-то single = false; Log.d(TAG, «InMobi: adRequestFailed»); getAdClick(); } } @Override public void adRequestCompleted(InMobiAdView arg0) { Log.d(TAG, «InMobi: adRequestCompleted»); if (parent.findViewWithTag(IN_MOBI_TAG) == null) { // ограничиваем высоту баннера float px = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 48, act.getResources() .getDisplayMetrics()); final LayoutParams params = new LayoutParams( LayoutParams.WRAP_CONTENT, (int) px); parent.addView(inMobi, params); } } }; inMobi = InMobiAdView.requestAdUnitWithDelegate(act .getApplicationContext(), adDelegate, act, InMobiAdDelegate.INMOBI_AD_UNIT_320X48); inMobi.setTag(IN_MOBI_TAG); new Thread(new Runnable() { @Override public void run() { inMobi.loadNewAd(); } }).start(); } private void getAdClick() { Log.d(TAG, «AdClick: getAdClick»); final AdClick adClick = new AdClick(); AdClick.CONTX = act; AdClick.PUB_ID = AD_CLICK_ID; AdClick.AdBox = (LinearLayout) parent.findViewById(R.id.adClick); adClick.ShowAD(); } }
Единственный нюанс в этом коде, о котором еще стоит упомянуть отдельно — это ограничение баннера InMobi по высоте, если этого не сделать, то он зальет всю Activity белым фоном! Вот такой вот неприятный баг.
Осталось только вставить вызов получения рекламы в OnCreate() нашей Activity:
package ru.coder1cv8.sample; import android.app.Activity; import android.os.Bundle; public class Main extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); new AdSupport(this, R.id.adContainer).getAds(); } }
В заключении, хочу попросить отнестись со снисхождением к моему Java-коду. Знаю, он далеко не идеален. Я только разбираюсь с премудростями ООП в общем и Java в частности.