Несколько лет назад я решил, что вставлять одну рекламную сеть в мобильное приложение недостаточно эффективно и засунул несколько сетей, а этой зимой решил переписать и выложить эту разработку на github. Так и родился OpenAdAdapter.
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"];
Это очень важно. Вы можете поменять стратегию или перестать показывать рекламу какой-то сети, или какая-то сеть вас может забанить.
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.
ссылка на оригинал статьи http://habrahabr.ru/post/262715/
Добавить комментарий