OpenAdAdapter — это библиотека для мобильных игр (Android и iOS, лицензия Apache 2.0). Я решил делать адаптер для игр, а не для всех приложений, чтобы API был проще. Под игрой я понимаю приложение, у которого на весь экран один GL канвас, и баннер расположен сверху или снизу. Когда баннер есть, канву надо чуть подвинуть. То есть разработчик просто говорит: покажи баннер снизу, без того, чтобы вникать как сеть Х засунуть в лайот. Многие игры разрабатываются с помощью SDK и движков типа Marmalade или Unity. Там добраться до нативной платформы и изучить все нюансы реализации колбеков, это отдельное джитсу. Кстати, как раз поэтому в OpenAdAdapter нет колбеков.
Предполагается, что API OpenAdAdapter можно вызывать из любого потока. (Я так задумывал, но опасаюсь зарекаться). Все методы статичные.
На данный момент поддерживаются следующие сети:
Android
— AdColony
— Admob
— AerServ
— Chartboost
— Heyzap
— InMobi
iOS
— AdColony
— Admob
— AerServ
— Chartboost
— Heyzap
— InMobi
— iAd
Создать адаптер для новой сети несложно. Я изначально хотел поддержать российскую WapStart так, как они мне раз платили невероятный $1 за клик целых два дня за московский трафик, (обычно 5 центов), потом штрафанули на 30% и отдали деньги, но добавление их усложнило бы все на андроиде. Дело в том, что все сети дают jar файл для интеграции, а WapStart давал проект на еклипс так еще с файлами ресурсов. Вот поэтому я их не реализовал пока.
Настройки загружаются из JSON файла из интернета.
OpenAdAdapter.initFromUrl(
this,
"https://raw.githubusercontent.com/sample-data/oad1/master/android-redirect.json");
[OpenAdAdapter startWithUrl:@"https://raw.githubusercontent.com/sample-data/oad1/master/ios-redir.json"];
(хостить статические файлы на гитхабе может нарушать правила github)
Это очень важно. Вы можете поменять стратегию или перестать показывать рекламу какой-то сети, или какая-то сеть вас может забанить.
OpenAdAdapter умеет показать баннер сверху или снизу, спрятать баннер, показать объявление на весь экран (fullscreen/interstitial), видео(video) и видео за вознаграждение (rewarded). Что и как показывать описано в json файле.
iOS
#import "ViewController.h"
#import "OpenAdAdapter.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *btnInit;
@property (weak, nonatomic) IBOutlet UILabel *label1;
@end
@implementation ViewController
{
bool btick;
NSString * rewardText;
}
- (IBAction)clickChkReward:(id)sender {
OADReward * reward = [OpenAdAdapter reward];
if(reward != nil){
self->rewardText = [NSString stringWithFormat:@"%@ %f %@", [reward network], [reward amount], [reward currency]];
}else{
self->rewardText = @"";
}
}
- (IBAction)clickInit:(id)sender {
if(!self->btick){
self->btick = true;
[self performSelector:@selector(tick) withObject:nil afterDelay:1.0];
}
self.label1.text = @"Initializing 1";
[OpenAdAdapter startWithUrl:@"https://raw.githubusercontent.com/sample-data/oad1/master/ios-redir.json"];
// [OpenAdAdapter startWithUrl:@"https://raw.githubusercontent.com/sample-data/oad1/master/ios-no-heyzap.json"];
//[OpenAdAdapter startWithUrl:@"https://raw.githubusercontent.com/sample-data/oad1/master/ios-heyzap.json"];
self.label1.text = @"Initializing 2";
}
-(void)tick{
[self performSelector:@selector(tick) withObject:nil afterDelay:1.0];
NSString * s1 = [NSString stringWithFormat:@"bh %g %g %@", [OpenAdAdapter bannerHeightPts], [OpenAdAdapter bannerHeightPixels], self->rewardText];
self.label1.text = s1;
}
- (IBAction)clickBanner:(id)sender {
[OpenAdAdapter showTopBanner:self];
}
- (IBAction)clickBottomBanner:(id)sender {
[OpenAdAdapter showBottomBanner:self];
}
- (IBAction)clickHideBanner:(id)sender {
[OpenAdAdapter hideBanner];
}
- (IBAction)clickFullscreen:(id)sender {
[OpenAdAdapter showFullscreen:self];
}
- (IBAction)clickVideo:(id)sender {
[OpenAdAdapter showVideo:self];
}
- (IBAction)clickRewarded:(id)sender {
[OpenAdAdapter showRewarded:self];
}
- (IBAction)clickTest:(id)sender {
// [TestX1 test1];
[OpenAdAdapter test1];
}
- (IBAction)clickTest2:(id)sender {
//[OpenAdAdapter test2:self];
[OpenAdAdapter showTopBanner:self];
}
- (IBAction)clickTest3:(id)sender {
//[OpenAdAdapter test3:self];
[OpenAdAdapter showBottomBanner:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
Отличие линковки приложений под iOS (от Android) заставило сделать не одну либу, а одну + адаптер для каждой сети.
То есть, если вам нужен Chartboost, то в проект надо добавить
— libOADAdapterChartboost.a — адаптер из OpenAdAdapter
— Chartboost.framework — оригинальный фреймворк от Chartboost
github.com/OpenAdAdapter/OAD-iOS-bin/tree/master/chartboost
— libOpenAdAdapter.a — и сам OpenAdAdapter
Самое простое это кинуть все сети сразу.
github.com/OpenAdAdapter/OAD-iOS-bin
Добавить необходимые фреймворки:
AdSupport.framework
StoreKit.framework
MessageUI.framework
libxml2.2.dylib
libz.dylib
libsqlite3.0.dylib
CoreTelephony.framework
EventKit.framework
EventKitUI.framework
Security.framework
Social.framework
WebKit.framework
и Other Linker Flags: -ObjC
Android
package com.example.testoad01;
import com.openadadapter.OpenAdAdapter;
import com.openadadapter.Reward;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
Runnable tick = new Runnable(){
@Override
public void run() {
try{
label1.setText("bh " + OpenAdAdapter.getBannerHeightInPoints() + " " +OpenAdAdapter.getBannerHeightInPixels());
}
finally{
handler.postDelayed(tick, 1000);
}
}};
Handler handler = new Handler(Looper.getMainLooper());
boolean ticking;
private TextView label1;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TestX.test();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
OpenAdAdapter.onCreate(this);
label1 = (TextView)findViewById(R.id.textView1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void butBS(View v) {
Toast.makeText(getApplicationContext(), "Bottom Banner Show",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showBottomBanner(null);
}
public void butBSTop(View v) {
Toast.makeText(getApplicationContext(), "Top Banner Show",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showTopBanner(null);
}
public void butBH(View v) {
Toast.makeText(getApplicationContext(), "Banner Hide",
Toast.LENGTH_LONG).show();
OpenAdAdapter.hideBanner();
}
public void butF(View v) {
Toast.makeText(getApplicationContext(), "Banner Fullscreen",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showFullscreen(null);
}
public void butVideo(View v) {
Toast.makeText(getApplicationContext(), "Banner Video",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showVideo(null);
}
public void butR(View v) {
Toast.makeText(getApplicationContext(), "Rewarded Video",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showRewarded(null);
}
public void butIU(View v) {
Toast.makeText(getApplicationContext(), "Init from URL",
Toast.LENGTH_LONG).show();
// OpenAdAdapter.initFromUrl(this,
// "https://raw.githubusercontent.com/sample-data/oad1/master/data1.json");
OpenAdAdapter
.initFromUrl(
this,
"https://raw.githubusercontent.com/sample-data/oad1/master/android-redirect.json");
if(!ticking){
handler.postDelayed(tick, 1000);
ticking = true;
}
}
public void butIF(View v) {
Toast.makeText(getApplicationContext(), "Init from File",
Toast.LENGTH_LONG).show();
// OpenAdAdapter.preinit();
}
public void butV(View v) {
Toast.makeText(getApplicationContext(), "verify", Toast.LENGTH_LONG)
.show();
OpenAdAdapter.verify();
}
public void butSF(View v) {
Toast.makeText(getApplicationContext(), "Show Fullscreen",
Toast.LENGTH_LONG).show();
OpenAdAdapter.showMyFullscreen(this);
}
public void clickFetchReward(View v) {
Reward reward = OpenAdAdapter.fetchReward();
if (reward == null) {
Toast.makeText(getApplicationContext(), "No reward",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(
getApplicationContext(),
"Reward " + reward.getNetwork() + " " + reward.getAmount()
+ " " + reward.getCurrency(), Toast.LENGTH_LONG)
.show();
}
}
@Override
public void onStart() {
super.onStart();
OpenAdAdapter.onStart(this);
}
@Override
public void onResume() {
super.onResume();
OpenAdAdapter.onResume(this);
}
@Override
public void onPause() {
super.onPause();
OpenAdAdapter.onPause(this);
}
@Override
public void onStop() {
super.onStop();
OpenAdAdapter.onStop(this);
}
@Override
public void onDestroy() {
super.onDestroy();
OpenAdAdapter.onDestroy(this);
}
@Override
public void onBackPressed() {
if (OpenAdAdapter.onBackPressed(this))
return;
super.onBackPressed();
}
}
Так как некоторые сети хотят получать уведомление onStart, onPause, onResume и т.д., то и OpenAdAdapter на андроиде нужно вызывать при этих событиях.
Так же нужно не забыть про кипу активити, которых нужно указать в AndroidManifest.xml
И все jar файлы кинуть в папку lib (ненужные можно выкинуть): github.com/OpenAdAdapter/OAD-Android-bin/tree/master/lib
Неочевидное — вероятное
Важный момент: нежелательно, чтобы баннер перекрывал интерфейс программы. Как раз эта проблема и подтолкнула меня на создание проекта. Мое приложение определяло есть или нет баннер, исходя из того, что сообщает делегат/колбек/лиснер.
— AdShown — экран сжался
— AdFailed — экран разжался
То есть, я не делал скриншот экрана и не распознавал образ баннера на нем, но оказалось, что самая прекрасная сеть рекламы работает не так, как я предполагал. Она может сообщить AdFailed, а когда приложение расширит канву на весь экран — забанить приложение.
Решением проблемы с перекрытием контента стали следующие функции
int pt = OpenAdAdapter.getBannerHeightInPoints();
int px = OpenAdAdapter.getBannerHeightInPixels();
int pt = [OpenAdAdapter bannerHeightPts];
int px = [OpenAdAdapter bannerHeightPixels];
Если значение 0 — баннера нет, значение больше 0 — баннер сверху, значение меньше 0 — баннер снизу. Вызывать можно каждый кадр или раз в секунду и соответсвенно изменять габариты GL канваса.
Аналогичным способом без колбеков я решил реализовать проверку получения вознаграждения за просмотр Rewarded видео.
Конфигурационный файл JSON
raw.githubusercontent.com/sample-data/oad1/master/data1.json:
{
"debug":{"verify":true},
"urls":["https://ohohoho.appspot.com/track", {"url":"https://xman545476.appspot.com/track", "priority": 10}],
"commands":["save", {"cmd":"settings", "settings":{"reportLocation":true, "advertisingId":true, "userId": true}}],
"strategy":{
"banner": {"list":["admob", "inmobi", "wapstart", "aerserv"], "strategy":"random"},
"fullscreen2": {
"list":[
"aerserv",
"inmobi"],
"strategy":"random"},
"fullscreen": {
"list":[
"aerserv",
"heyzap",
"adcolony",
{"name":"heyzap","type":"rewarded", "preload": "always"},
"admob",
{"name":"chartboost","type":"video", "preload": "low"},
"inmobi"],
"strategy":"random"},
"video": {"list":["chartboost", "heyzap"],"strategy":"round-robin"},
"rewarded": {"list":["adcolony", "chartboost", "heyzap"],
"strategy":"random"}
},
"networks":[
{
"name":"admob",
"bannerId":"ca-app-pub-8607147313123654/8458359243",
"fullscreenId":"ca-app-pub-8607147313123654/9935092440"
},
{
"name":"inmobi",
"propId":"d4783f2efd4147499e40cc3540f2d221",
"bannerId":"d4783f2efd4147499e40cc3540f2d221",
"fullscreenId":"d4783f2efd4147499e40cc3540f2d221",
"bannerId1":"1428178968194889",
"fullscreenId1":"1428178928625995"
},
{
"name":"chartboost",
"id":"5519c460c909a67c4e1e58a4",
"sig":"e34adc93d56dfcff2a3a34d5f0dcd74204cbaf05",
"video": "true",
"rewarded": "true"
},
{
"name":"heyzap",
"id":"e0c44b21d39c921a55f31ea836a70b65",
"video": "true",
"rewarded": "true"
},
{
"name":"adcolony",
"id":"app398cb71e4cae463f94",
"videoId":"vzc71a58270b924c95a0",
"rewardedId":"vz6669f90f9dbe4e4a89"
},
{
"name":"aerserv",
"fullscreenId":"1000741",
"banner320":"1000834",
"banner728":"1000835"
},
{
"name":"home",
"bannerId":"",
"halfId":"",
"fullscreenId":""
},
{
"name":"direct",
"bannerId":"",
"halfId":"",
"fullscreenId":""
}
]
}
Первые три ключа debug, urls, commands — пока не используются. В ключе networks перечислены сети. Последние две (home, direct) тоже пока не используются. Я разрабатываю сервер, который будет более интеллектуальным чем JSON файл.
Ключ strategy самый сложный в этом конфиге. У него есть 4 подключа для 4х стратегий — banner, fullscreen, video, rewarded.
(fullscreen2 — не используется)
"banner": {"list":["admob", "inmobi", "wapstart", "aerserv"], "strategy":"random"}
Каждая стратегия имеет в свою очередь два ключа list и strategy.
— list — перечисляет сети;
— strategy — определяет стратегию: random, round-robin, failsafe;
random — показывает рекламу случайным образом;
round-robin — показывает рекламу по очереди;
failsafe — показывает всегда рекламу первую из списка, а если та не работает, то вторую.
Мною написано приложение на HTML для создания данного файла. Выложу его вскорости.
Как проект будет развиваться
HTML — приложение для редактирования конфига (уже написано);
Вебсайт — планирую сделать вебсайт, чтобы разработчик выбирал сети и получал ZIP файл (который будет содержать все и можно просто скопировать в проект одним движением руки) и инструкции, какие фреймворки добавить и что написать в AndroidManifest.xml;
Unity3D — написанно, все виды рекламы показывает на обеих платформах, но там не все так просто делается и описание реализации требует отдельной статьи, пока нет поддержки UnityAds;
Сервер — тут много идей, можно бесконечно развиваться;
Тюнинг — размеры банеров относительно экрана, умный и экономный прелоад сетей и т.п.;
Самопроверка — в проекте андроида можно заметить метод verify. В теории он должен проверять определены ли все нужные активити, пермишины, ресиверы и мета тег с версией google play services. И он это частично делает, но до конца этот функционал не реализован;
Статья на Мегамозг — с экономическим обоснованием использования OpenAdAdapter против Mopub, AdTapsy, Appodeal и других, обещающих невероятный CTR.
Комментарии (6)
petrovichtim
16.07.2015 10:49Если не ошибаюсь, то тоже самое делают площадки медиаторы. Но они дают свое sdk а рекламу распределяют через свой сервис.Попутно ещё выбирая рекламу подороже
pyra Автор
16.07.2015 13:51Да, но на мой взгляд они немного дурят играя цифрами. Как «дурят»? Планирую показать в статье на мегамозге, если наскребу больше трех абзацев.
+ Они могут закрыть свой бизнес, приложения перестанут приносить прибыль вообще (так как приложения завязаны на их сервер), + они берут себе %, например AdTapsy берет 10% трафикаpetrovichtim
16.07.2015 14:52Admob от гугла тоже же медиатор
pyra Автор
16.07.2015 15:19сейчас адаптеры к другим сетям есть, практически во всех сетях. Admob, AerServ, TapIt, HeyZap (We currently support AdColony/Opera, AdMob, Vungle, AppLovin, Facebook Audience Network, UnityAds, Apple iAd, and Chartboost. Heyzap's standard SDK is included by default… Convenient, right? We will also be bringing on new networks as time goes by)
Дело в том, что мелкие сети имеют низкий филлрейт и не могут без медиации конкурировать и любой момент могут обанкротится, а большая как например, Admob может вас забанить, чтобы другим было неповадно.
C OpenAdAdapter вы сами контролируете медиацию. Все что вам нужно это хостить статичный JSON файл в интернете. Если гугл запретит вам пожизненно пользовать сервисами гугла или HeyZap обанкротится — это не отразится на том, что вы выбираете, чью рекламу показывать.
Так же добавить OpenAdAdapter в приложение в 6 сетями может быть проще чем просто один Адмоб или HeyZap, что я попробую показать на видео в скринкасте. Но на все надо время
Suvitruf
Хорошо бы документацию добавить. Да и комментарии в коде не помешали бы.
pyra Автор
Документация будет в ближайшее время. А пока есть два примера под iOS и Android