Максимизируем прибыль от своих Android-приложений

5 апреля 2011 г.

Сегодня я хотел бы поделиться с уважаемым сообществом своим опытом получения прибыли от рекламы в Android-приложениях. Почему именно от рекламы? Ну, во-первых, исторически так сложилось, что некоторое время это был практически единственный способ монетизации приложений для разработчиков из России. Во-вторых, мой опыт говорит о том, что бесплатное приложение с интегрированной рекламой может быть в разы выгоднее для разработчика чем продажа того же приложения. Например, у меня лучший показатель на данный момент – это около 2k$ в месяц исключительно за рекламу. Согласитесь, что даже на такой уровень продаж выйти не так-то просто, особенно если вы одиночка, размещающий на Маркете свое первое приложение.
Прибыль от Android
Итак, как же мы будем максимизировать эту самую прибыль? Идея заключается в том, что бы объединить в своем приложении баннеры из нескольких рекламных сетей, с целью повышения 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 в частности.