Iklan spanduk di aplikasi iOS



Hari ini kami membuka serangkaian artikel tentang apa yang biasanya tidak dibicarakan di konferensi teknis dan pertemuan. Posting ini dan selanjutnya akan memberi tahu Anda bagaimana mekanisme monetisasi bekerja di aplikasi hiburan iOS iFunny yang populer di AS, yang sedang kami kembangkan.

Periklanan adalah salah satu cara utama untuk memonetisasi aplikasi gratis. Tapi sekarang, opsi apa yang ada di 2011 ketika iFunny muncul? Layanan ini awalnya dibangun sebagai bisnis yang kuat dan berkelanjutan, sehingga sejak hari pertama perusahaan memutuskan untuk tidak menggoda pengguna dan tidak terlibat dalam permainan dengan permodalan bersyarat.

Pada saat itu, opsi utama untuk monetisasi adalah membuat versi layanan yang bebas, dan kemudian mencoba menjual fungsionalitas utama. Konsumen masih muda, tidak berpengalaman dan tidak siap berpisah dengan jumlah yang lebih besar dari satu dolar.

Matematika sederhana menunjukkan bahwa dengan konversi 10%, mendapatkan ARPU lebih dari 10 sen adalah tugas yang hampir mustahil.

Kemudian saya harus memikirkan bagaimana cara lain Anda bisa memonetisasi produk. Model periklanan telah bekerja dengan sangat baik di web, dan dapat diasumsikan bahwa ia akan segera mekar di ponsel juga.
Secara umum, permulaan model monetisasi iklan seluler dapat dianggap sebagai penampilan AdWhirl - layanan yang memungkinkan Anda untuk mengintegrasikan SDK jaringan iklan dan memutarnya. Penampilannya memungkinkan untuk meningkatkan FillRate ke rata-rata 50% di pasar dan menghasilkan pendapatan dari model periklanan setidaknya sebanding dengan penjualan satu dolar. Prinsip pelaksanaan semua sumber permintaan yang mungkin dan organisasi persaingan di antara mereka telah menjadi pendorong utama pertumbuhan dalam industri periklanan dan terus dieksploitasi hingga hari ini.

Tetapi semakin kompleks sistemnya, semakin tidak stabilnya, yang sama sekali tidak dapat diterima untuk layanan besar pada tingkat iFunny. Mulai bergerak ke arah ini pada tahun 2011, perusahaan menciptakan salah satu mekanisme paling efektif untuk bekerja dengan spanduk seluler dan iklan asli dan meningkatkan pendapatan per pengguna sebanyak 40 kali, yang memungkinkan pengembangan tidak hanya proyek internal, tetapi juga berinvestasi di perusahaan lain.

MoPub dan perusahaan


Sejak 2012, kami telah pindah dari AdWhirl ke MoPub.

MoPub adalah platform iklan seluler dengan kemampuan untuk menambahkan modulnya sendiri, yang mencakup beberapa alat hebat:

  • Pasar MoPub - pertukaran iklan sendiri;
  • mediator jaringan periklanan untuk bekerja dengan jaringan eksternal;
  • mekanisme pemesanan yang memungkinkan Anda untuk secara mandiri menempatkan spanduk di aplikasi Anda sendiri dan menyesuaikan tampilan mereka.

Keuntungan utama MoPub:

  • mampu bekerja dengan sebagian besar jaringan iklan;
  • mekanisme yang jelas untuk menghubungkan jaringan pihak ketiga yang baru;
  • open source
  • sejumlah besar pengaturan dasar dan penargetan;
  • sebuah komunitas besar di sekitar jaringan, bahkan ada konferensi sendiri.

MoPub juga memiliki kekurangan:

  • permintaan kumpulan pada GitHub tidak diterima dan tidak ada reaksi sama sekali terhadap mereka;
  • panel kontrolnya sangat kompleks, dan bagi pengembang, ketika melakukan debugging, dibutuhkan beberapa waktu untuk mempelajari strukturnya.

Kekuatan kebenaran


Seperti pahlawan dari satu film Rusia berkata: "Kekuatan itu sebenarnya." Pada bagian ini, saya akan berbicara tentang kesulitan yang kita, sebagai pengembang aplikasi, harus hadapi setelah jutaan unduhan pertama iFunny, pertumbuhan pemirsa dan lalu lintas iklan dari lebih dari 100 mitra.

Konten


Pasar periklanan adalah "kasta" perusahaan teknologi yang sangat tertutup, tetapi pada saat yang sama, agregator memiliki jaringan mitra yang besar: dari perusahaan besar yang bekerja dengan jutaan anggaran hingga perusahaan kecil yang dirancang untuk audiens target tertentu.

Kedekatan dan fragmentasi mitra ini, terlepas dari spanduk pra-moderasi dan aturan yang cukup ketat pada konten iklan, tidak memungkinkan penjual iklan yang paling jujur ​​untuk menerbitkan materi iklan yang dilarang atau merusak pengalaman pengguna dalam aplikasi.

Ada beberapa kategori utama konten "cabul" dalam spanduk iklan:

  • konten porno. Baru-baru ini, tampaknya semakin sedikit, tetapi bagaimanapun terjadi. Kami tidak dapat menerbitkan konten ini di artikel, sehingga gambarnya tidak ada di sini
  • peringatan sistem dalam spanduk, contohnya dapat dilihat di salah satu pengguna twitter.com/IfunnyStates/status/1029393804749668352
  • konten dengan suara. Suara tidak dilarang oleh jaringan iklan, maupun animasi, tetapi jika suara diputar tanpa berinteraksi dengan antarmuka, ini dianggap oleh pengguna sebagai bug aplikasi dan secara negatif memengaruhi pengalaman pengguna
  • meraih perhatian. Spanduk yang bagus harus menarik perhatian pengguna, tetapi ini tidak selalu terjadi dengan cara yang jujur: kadang-kadang video yang berkelip jatuh ke dalam spanduk. Cara lain yang tidak jujur ​​untuk membuat pengguna mengetuk spanduk adalah dengan mensimulasikan antarmuka aplikasi, misalnya seperti ini:


Ngomong-ngomong, di Rusia, ketukan biasa pada spanduk ini dapat mengeluarkan langganan berbayar untuk beberapa operator seluler, dan Anda bahkan tidak akan mengetahuinya sampai Anda melihat detailnya. Ini juga merupakan cara tidak jujur ​​untuk bekerja dengan periklanan, tetapi operator di Amerika Serikat tidak memiliki kesempatan seperti itu.

Klik otomatis


Seperti yang ditunjukkan oleh pengalaman saya, ini adalah kasus yang sangat negatif bagi pengguna. Dengan menggunakan kemampuan JavaScript, WKWebView atau UIWebView, serta lubang di dalam implementasi pustaka iklan, Anda dapat membuat iklan yang akan membuka konten spanduk itu sendiri dan mengarahkan pengguna keluar dari aplikasi.

Untuk mengulangi masalah ini menggunakan contoh MoPub, cukup tambahkan kode javascript dari konten berikut ke spanduk:

<a href="https://ifunny.co" id="testbutton">test</a> <script>document.getElementById('testbutton').click(); </script> 

Ini bekerja untuk waktu yang lama di banyak versi MoPub, hingga versi 4.13.

Dengan menjelajahi implementasi MoPub, dimungkinkan untuk menghasilkan tautan yang lebih kompleks yang memungkinkan tidak hanya untuk membuka iklan di layar penuh, tetapi juga mengirim pengguna ke AppStore ke aplikasi tertentu dan bahkan tidak memperhitungkan tampilan spanduk.

Ngomong-ngomong, dalam catatan rilis untuk versi 4.13.0 dari MoPub SDK untuk iOS tidak ada informasi tentang perbaikan ini, karena itu adalah lubang yang agak serius di SDK, dan mitra MoPub yang tidak jujur ​​mengeksploitasinya dengan cukup aktif. Seperti yang ditunjukkan oleh log, yang akan saya bahas nanti, setiap hari saya harus memblokir hingga 2 juta upaya untuk membuka spanduk tanpa interaksi pengguna dengannya.

Dalam kasus MoPub, ternyata mudah untuk menemukan dan mengulangi masalahnya, tetapi jaringan lain yang berfungsi dengan iFunny memiliki kode tertutup, dan Anda harus berurusan dengan klik-otomatis yang muncul dengan memblokir spanduk atau bahkan memutus jaringan untuk sementara waktu.
iFunny bekerja erat dengan semua mitra periklanan dan memberi tahu mereka tentang spanduk tersebut. Karena khalayak muda iFunny menarik bagi pengiklan, mitra bersedia untuk bertemu mereka dan menghapus iklan tersebut dari rotasi.

Kecelakaan


Kecelakaan selalu buruk. Lebih buruk lagi, ketika mereka terjadi karena ketergantungan dengan sumber tertutup, dan Anda hanya dapat memengaruhi mereka secara tidak langsung. Selama bertahun-tahun bekerja dengan periklanan di iFunnu, beberapa jenis kerusakan telah diidentifikasi untuk diri mereka sendiri, yang dapat dibagi menjadi beberapa kelompok.

  • Sistem

Ini termasuk pengecualian di perpustakaan jaringan, WKWebView (UIWebView), OpenGL.
Sangat sulit untuk secara langsung mempengaruhi jenis crash ini, tetapi masih mungkin untuk mempengaruhi beberapa dari mereka, setelah sebelumnya mempelajari pengoperasian komponen WebView dengan WebGL.

Beginilah tampilan dari crash seperti itu:

1 libGPUSupportMercury.dylib gpus_ReturnNotPermittedKillClient + 12
2 AGXGLDriver gldUpdateDispatch + 7132
3 libGPUSupportMercury.dylib gpusSubmitDataBuffers + 172
4 AGXGLDriver gldUpdateDispatch + 12700
5 WebCore WebCore::GraphicsContext3D::reshape(int, int) + 524
6 WebCore WebCore::WebGLRenderingContextBase::initializeNewContext() + 712
7 WebCore WebCore::WebGLRenderingContextBase::WebGLRenderingContextBase(WebCore::HTMLCanvasElement*, WTF::RefPtr<WebCore::GraphicsContext3D>&&, WebCore::GraphicsContext3D::Attributes) + 512
8 WebCore WebCore::WebGLRenderingContext::WebGLRenderingContext(WebCore::HTMLCanvasElement*, WTF::PassRefPtr<WebCore::GraphicsContext3D>, WebCore::GraphicsContext3D::Attributes) + 36
9 WebCore WebCore::WebGLRenderingContextBase::create(WebCore::HTMLCanvasElement*, WebCore::WebGLContextAttributes*, WTF::String const&) + 1272
10 WebCore WebCore::HTMLCanvasElement::getContext(WTF::String const&, WebCore::CanvasContextAttributes*) + 520
11 WebCore WebCore::JSHTMLCanvasElement::getContext(JSC::ExecState&) + 212
12 JavaScriptCore llint_entry + 27340
13 JavaScriptCore llint_entry + 24756
14 JavaScriptCore llint_entry + 24756
15 JavaScriptCore llint_entry + 24756
16 JavaScriptCore llint_entry + 25676
17 JavaScriptCore llint_entry + 24756
18 JavaScriptCore llint_entry + 24656
19 JavaScriptCore vmEntryToJavaScript + 260
20 JavaScriptCore JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 164
21 JavaScriptCore JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 348
22 JavaScriptCore JSC::profiledCall(JSC::ExecState*, JSC::ProfilingReason, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) + 160
23 WebCore WebCore::JSEventListener::handleEvent(WebCore::ScriptExecutionContext*, WebCore::Event*) + 980
24 WebCore WebCore::EventTarget::fireEventListeners(WebCore::Event&, WebCore::EventTargetData*, WTF::Vector<WebCore::RegisteredEventListener, 1ul, WTF::CrashOnOverflow, 16ul>&) + 616
25 WebCore WebCore::EventTarget::fireEventListeners(WebCore::Event&) + 324
26 WebCore WebCore::EventContext::handleLocalEvents(WebCore::Event&) const + 108
27 WebCore WebCore::EventDispatcher::dispatchEvent(WebCore::Node*, WebCore::Event&) + 876
28 WebCore non-virtual thunk to WebCore::HTMLScriptElement::dispatchLoadEvent() + 80
29 WebCore WebCore::ScriptElement::execute(WebCore::CachedScript*) + 360
30 WebCore WebCore::ScriptRunner::timerFired() + 456
31 WebCore WebCore::ThreadTimers::sharedTimerFiredInternal() + 144
32 WebCore WebCore::timerFired(__CFRunLoopTimer*, void*) + 24
33 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 24
34 CoreFoundation __CFRunLoopDoTimer + 868
35 CoreFoundation __CFRunLoopDoTimers + 240
36 CoreFoundation __CFRunLoopRun + 1568
37 CoreFoundation CFRunLoopRunSpecific + 440
38 WebCore RunWebThread(void*) + 452
39 libsystem_pthread.dylib _pthread_body + 236
40 libsystem_pthread.dylib _pthread_start + 280
41 libsystem_pthread.dylib thread_start + 0


Selain itu, mereka terjadi secara eksklusif ketika meninggalkan latar belakang. Ini disebabkan oleh fakta bahwa mesin OpenGL seharusnya tidak berfungsi ketika aplikasi berada di latar belakang.

Perbaikan di sini ternyata cukup sederhana:

Saat meninggalkan di latar belakang, Anda perlu mengambil tangkapan layar spanduk.

Hapus Tampilan iklan dari layar sehingga komponen WebView berhenti menggunakan OpenGL.
Saat Anda keluar dari latar belakang, kembalikan semuanya seperti semula.

Dalam kode Objective-C, tampilannya seperti ini:

 - (void)onWillResignActive { if (self.adView.superview) { UIGraphicsBeginImageContext(self.adView.bounds.size); [self.adView.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *adViewScreenShot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); adViewThumbView = [[UIImageView alloc] initWithImage:adViewScreenShot]; adViewThumbView.backgroundColor = [UIColor clearColor]; adViewThumbView.frame = self.adView.frame; NSInteger adIndex = [self.adView.superview.subviews indexOfObject:self.adView]; [self.adView.superview insertSubview:adViewThumbView atIndex:adIndex]; [self.adView removeFromSuperview]; } } - (void)onDidBecomeActive { if (self.adView && adViewThumbView) { NSInteger adIndex = [adViewThumbView.superview.subviews indexOfObject:adViewThumbView]; [adViewThumbView.superview insertSubview:self.adView atIndex:adIndex]; [adViewThumbView removeFromSuperview]; adViewThumbView = nil; } } 

  • Integrasi

Ini adalah masalah yang terjadi di persimpangan iFunny, Mopub dan penyedia iklan.
Sebagai aturan, mereka muncul setelah memperbarui perpustakaan penyedia dan karena cara baru berinteraksi dengan mereka.

Kasus terakhir adalah pada bulan Juni tahun ini, setelah pembaruan berikutnya dari salah satu perpustakaan yang digunakan. Cara baru untuk menginisialisasi perpustakaan disarankan menggunakan singleton untuk mengonfigurasi pengaturan jaringan.

Beralih ke itu dua kali, seperti yang terjadi dalam implementasi, secara berkala menyebabkan dekorasi dari utas utama, jadi saya harus membungkus inisialisasi dalam dispatch_once.

Departemen QA iFunny dapat menguji perpustakaan periklanan dengan baik, sehingga masalah ini ditemukan selama pengujian pembaruan.

  • Tidak terduga

Jenis kerusakan ini tidak dapat dikontrol sama sekali, karena terjadi tanpa perubahan pada klien.

Mereka dikaitkan dengan memperbarui backend mitra dan kurangnya kompatibilitas mundur. Kerusakan seperti itu sering terjadi pada penyedia iklan besar, tetapi cepat diperbaiki, karena mereka memengaruhi sejumlah besar aplikasi pada saat yang bersamaan.

Ada kasus-kasus ketika iFunny bebas macet per hari turun dari standar 99,8% menjadi 80%, dan jumlah komentar marah dalam cerita itu mencapai puluhan.

Performa


Iklan banner, sebagai suatu peraturan, menggunakan komponen WebView untuk menampilkan iklan, sehingga setiap banner yang ditampilkan adalah inisialisasi dari WebView baru dengan semua dependensinya.

Selain itu, beberapa mitra juga menggunakan WebView untuk berkomunikasi dengan backend mereka sendiri, karena iklan banner di perangkat seluler adalah turunan dari iklan di web.

Kebetulan setelah upgrade ada kebocoran memori di dalam perpustakaan baru. Setelah kemunculan alat Memory Graph di Xcode, menjadi lebih mudah untuk menemukan kebocoran di perpustakaan pihak ketiga, jadi sekarang mitra dapat dengan cepat diberitahu tentang hal itu.

Di bawah ini adalah GIF dari iFunny idle ketika tidak ada iklan untuk pengguna:



Solusi


Namun terlepas dari semua masalah yang dijelaskan di atas, iFunny stabil dan setiap hari menyebabkan senyum di antara jutaan penggunanya.

Selama bertahun-tahun bekerja aktif dengan periklanan, tim pengembangan memiliki beberapa alat yang dapat dengan sukses memantau masalah periklanan dan menanggapinya tepat waktu.

Sistem pembalakan


Sekarang sistem logging pengecualian di iFunny telah menyebar ke seluruh aplikasi: untuk ini, kami menggunakan backend kami sendiri dengan basis di ClickHouse dan ditampilkan di Grafana.

Tetapi tugas pertama untuk bekerja dengan log dalam aplikasi adalah tepatnya pencatatan situasi luar biasa dalam periklanan.

Ada beberapa komponen terkait untuk menentukan apakah panggilan diteruskan ke iFunny. Saya akan bercerita lebih banyak tentang mereka masing-masing.

IFAdView


Ini adalah turunan dari kelas MPAdView (bertanggung jawab untuk menampilkan iklan di MoPub).

Metode hitTest: withEvent ditimpa di kelas ini:

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *hitView = [super hitTest:point withEvent:event]; if (hitView) { [[IFAdsExceptionManager instance] triggerTouchView]; } return hitView; } 

Dengan demikian, kami menetapkan pemicu pada fakta bahwa pengguna berinteraksi dengan iklan.

IFURLProtocol


Kami mewarisi dari NSURLProtocol dan menjelaskan metode ini:

 + (BOOL)canInitWithRequest:(NSURLRequest *)request { __weak NSString *wRequestURL = request.URL.absoluteString; dispatch_async(dispatch_get_main_queue(), ^{ if (wRequestURL == nil) return; if ([wRequestURL hasPrefix:@"itms-appss://itunes.apple.com"] || [wRequestURL hasPrefix:@"itms-apps://itunes.apple.com"] || [wRequestURL hasPrefix:@"itmss://itunes.apple.com"] || [wRequestURL hasPrefix:@"http://itunes.apple.com"] || [wRequestURL hasPrefix:@"https://itunes.apple.com"]) { [[IFAdsExceptionManager instance] adsTriggerItunesURL:wRequestURL]; } }); return NO; } 

Ini adalah pemicu untuk membuka AppStore dari aplikasi, kami mencantumkan semua URL yang tersedia untuk ini.

IFAdsExceptionManager


Kelas yang mengumpulkan memicu dan menghasilkan catatan pengecualian di log.

Untuk memperjelas apa pemicunya, saya akan menjelaskan setiap metode antarmuka kelas ini.

 - (void)triggerTouchView;       . <source lang="objectivec">- (void)triggerItunesURL:(NSString *)itunesURL; 

Pemicu yang menentukan apakah pengalihan terjadi di iTunes.

 - (void)triggerResignActive; 

Pemicu untuk menentukan hilangnya aktivitas oleh aplikasi. Ini membandingkan dua pemicu sebelumnya.

 - (void)resetTriggers; 

Atur Ulang Pemicu. Kami menelepon saat meninggalkan latar belakang atau saat kami membuka AppStore sendiri, misalnya, saat kami mengirim pengguna untuk memberi peringkat di versi iOS yang lebih lama.

 @property (nonatomic, strong) FNAdConfigurationInfo *lastRequestedConfiguration; @property (nonatomic, strong) FNAdConfigurationInfo *lastLoadedConfiguration; @property (nonatomic, strong) FNAdConfigurationInfo *lastFailedConfiguration; 

Properti untuk merekam iklan yang terakhir kali berhasil atau tidak berhasil diminta dan diunduh. Diperlukan untuk membentuk pesan ke log.

Dapat dilihat bahwa algoritma tersebut ternyata cukup sederhana, tetapi efektif. Ini memungkinkan kami untuk melacak tidak hanya penemuan otomatis dari MoPub, tetapi juga dari jaringan lain.

Baru-baru ini, iklan dengan buka-otomatis sering membuka SKStoreProductViewController, jadi sekarang kami sedang mengerjakan definisi buka-otomatis pengontrol ini. Algoritma untuk mendefinisikan pengecualian ini akan sedikit lebih rumit, tetapi Objective-C Runtime akan membantu di sini.

Stand lokal


Berdasarkan sistem logging, iFunny juga mulai mengembangkan stand lokal untuk menerima dan men-debug iklan yang dilihat pengguna secara real time.

Stand terdiri dari:

  • membangun agen
  • perangkat
  • suite tes untuk setiap penyedia

Salah satu solusi menarik yang digunakan di stand adalah IDFA dari keluhan pengguna untuk iklan nyata.

Sejak sekitar 2016, kami berhenti menerima iklan nyata yang ditargetkan ke AS hanya menggunakan VPN, jadi kami harus mengganti perangkat IDFA dengan IDFA untuk pengguna nyata.

Ini dilakukan dengan cukup mudah menggunakan Objective-C Runtime dan swizzling.
Anda perlu mengganti metode advertisingIdentifier dari kelas ASIdentifierManager.

Di sini kami melakukannya melalui kategori:

 @interface ASIdentifierManager (IDFARewrite) @end @implementation ASIdentifierManager (IDFARewrite) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (AdsMonitorTests.customIDFA != nil) { [self swizzleIDFA]; } }); } + (void)swizzleIDFA { Class class = [self class]; SEL originalSelector = @selector(advertisingIdentifier); SEL swizzledSelector = @selector(swizzled_advertisingIdentifier); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } #pragma mark - Method Swizzling - (NSUUID *)swizzled_advertisingIdentifier { NSUUID *result = AdsMonitorTests.customIDFA; return result; } @end 

Metode yang dijelaskan dalam artikel ini digunakan untuk mentransfer IDFA pengguna ke build dari build agent.

Sebagai kesimpulan, saya ingin mengatakan bahwa iklan banner bekerja sangat baik di Amerika Serikat, dan selama tujuh tahun penggunaan aktif sebagai metode utama monetisasi, iFunny telah belajar untuk bekerja dengan baik dengannya.

Namun terlepas dari kenyataan bahwa spanduk membawa 75% dari pendapatan perusahaan, pekerjaan sedang dilakukan pada metode alternatif monetisasi dan beberapa pengalaman telah diperoleh dalam iklan asli dan penggunaan lelang iklan di pasar AS.

Secara umum, ada sesuatu untuk diceritakan.

Source: https://habr.com/ru/post/id426553/


All Articles