Максимизируем прибыль от своих 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 в частности.