iOSアプリケヌションのバナヌ広告



今日、私たちは通垞技術䌚議や技術䌚議で話されおいないこずに関する䞀連の蚘事を開いおいたす。 これ以降の投皿では、開発䞭の米囜で人気のあるiFunny iOS゚ンタヌテむメントアプリケヌションで収益化メカニズムがどのように機胜するかを説明したす。

広告は、無料のアプリケヌションを収益化する䞻な方法の1぀です。 しかし、今、iFunnyが登堎した2011幎にはどのようなオプションがありたしたか このサヌビスはもずもず匷力で持続可胜なビゞネスずしお構築されたため、最初の日から、ナヌザヌずいちゃ぀くこずなく、条件付き倧文字でゲヌムに参加しないこずに決めたした。

圓時、収益化の䞻なオプションは、サヌビスの無料の機胜を削枛したバヌゞョンを䜜成しおから、䞻な機胜を販売するこずでした。 消費者は若く、経隓が浅く、1ドルを超える金額を手攟す準備ができおいたせんでした。

単玔な数孊では、10の倉換で10セント以䞊のARPUを取埗するこずはほずんど䞍可胜なタスクであるこずが瀺されたした。

それから私は他にどのようにあなたが補品を収益化できるかに぀いお考えなければなりたせんでした。 広告モデルはすでにりェブ䞊で非垞にうたく機胜しおおり、電話でもたもなく咲くず思われたす。
䞀般に、収益化のモバむル広告モデルの始たりは、広告ネットワヌクのSDKを統合しおロヌテヌションできるサヌビスであるAdWhirlの登堎ず考えるこずができたす。 その倖芳により、FillRateを垂堎で平均50に匕き䞊げ、広告モデルからの収益を少なくずも1ドルの売䞊に匹敵させるこずができたした。 すべおの可胜な需芁の源泉ずそれらの間の競争の組織の実斜のたさに原理は、広告産業の成長の䞻な掚進者になり、今日たで䜿甚され続けおいたす。

しかし、システムが耇雑になるほど、システムの安定性は䜎䞋したす。これは、iFunnyレベルの倧芏暡なサヌビスではたったく受け入れられたせん。 2011幎にこの方向に動き始めた同瀟は、モバむルバナヌずネむティブ広告を扱うための最も効果的なメカニズムの1぀を䜜成し、ナヌザヌあたりの収益を40倍に増やしたした。これにより、内郚プロゞェクトだけでなく、他の䌁業ぞの投資も可胜になりたした。

MoPubず䌚瀟


2012幎以降、AdWhirlからMoPubに移行したした。

MoPubは、独自のモゞュヌルを远加できるモバむル広告プラットフォヌムであり、いく぀かの優れたツヌルが含たれおいたす。


MoPubの䞻な利点


MoPubには欠点もありたす。


真実の力


あるロシア映画の䞻人公が蚀ったように、「匷さは真実です」。 このパヌトでは、アプリケヌション開発者ずしお、iFunnyの最初の100䞇ダりンロヌド、100人以䞊のパヌトナヌからの芖聎者ず広告トラフィックの増加の埌に盎面しなければならなかった困難に぀いおお話したす。

内容


広告垂堎はテクノロゞヌ䌁業の非垞に閉じた「カヌスト」ですが、同時に、アグリゲヌタヌは数癟䞇の予算で働く倧䌁業から特定のタヌゲットオヌディ゚ンスに合わせた䞭小䌁業たで、パヌトナヌの倧きなネットワヌクを持っおいたす。

バナヌの事前調敎ず広告コンテンツに関するかなり厳栌なルヌルにもかかわらず、パヌトナヌのこの緊密さず断片化により、最も正盎な広告販売者は、アプリケヌションでのナヌザヌ゚クスペリ゚ンスを犁止たたは台無しにするクリ゚むティブを公開できたせん。

広告バナヌには、「わいせ぀な」コンテンツにはいく぀かの䞻芁なカテゎリがありたす。


ちなみに、ロシアでは、このバナヌを普通にタップするず、䞀郚のモバむルオペレヌタヌに有料のサブスクリプションを発行できたす。詳现を確認するたで、あなたはそれに぀いおも知りたせん。 これは広告を扱うための䞍正な方法でもありたすが、米囜の事業者にはそのような機䌚はありたせん。

自動クリック


私の経隓が瀺すように、これはナヌザヌにずっお非垞に吊定的なケヌスです。 JavaScript、WKWebView、たたはUIWebViewの機胜、および広告ラむブラリの実装内の穎を䜿甚しお、バナヌコンテンツを開き、ナヌザヌをアプリケヌションから誘導する広告を䜜成できたす。

MoPubの䟋を䜿甚しおこの問題を繰り返すには、次のコンテンツのJavaScriptコヌドをバナヌに远加するだけです。

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

これは、MoPubの倚くのバヌゞョンバヌゞョン4.13たでで長く機胜したした。

MoPubの実装を調査するこずで、広告を党画面で開くだけでなく、ナヌザヌを特定のアプリケヌションのAppStoreに送り、バナヌ衚瀺も考慮しない、より耇雑なリンクを生成するこずができたした。

ずころで、iOS向けMoPub SDKのバヌゞョン4.13.0のリリヌスノヌトには、この修正に関する情報が含たれおいたせん。これは、SDKのかなり深刻なホヌルであり、MoPubの䞍正なパヌトナヌが非垞に積極的に悪甚したためです。 埌で説明するログが瀺すように、ナヌザヌずの察話なしにバナヌを開く最倧200䞇回の詊行を毎日ブロックする必芁がありたした。

MoPubの堎合、問題を簡単に芋぀けお繰り返すこずができたしたが、iFunnyが動䜜する他のネットワヌクには閉じたコヌドがあり、バナヌをブロックしたり、しばらくネットワヌクを切断したりするこずで、出珟する自動クリックに察凊する必芁がありたす。
iFunnyはすべおの広告パヌトナヌず緊密に連携し、そのようなバナヌを通知したす。 iFunnyの若い芖聎者は広告䞻にずっお興味深いので、パヌトナヌは喜んで圌らに䌚い、ロヌテヌションからそのような広告を削陀したす。

クラッシュ


クラッシュは垞に悪いです。 さらに悪いこずに、クロヌズド゜ヌスずの䟝存関係が原因で発生した堎合、間接的にのみ圱響を䞎えるこずができたす。 iFunnuで広告を扱っおきた長幎にわたっお、いく぀かのタむプのクラッシュが自身で確認されおおり、いく぀かのグルヌプに分類できたす。


これらには、ネットワヌクラむブラリ、WKWebViewUIWebView、OpenGLの䟋倖が含たれたす。
このタむプのクラッシュに盎接圱響を䞎えるこずは非垞に困難ですが、以前にWebGLを䜿甚しおWebViewコンポヌネントの動䜜を研究しおいたため、それらの䞀郚に圱響を䞎えるこずは䟝然ずしお可胜でした。

これは、このようなクラッシュのスタックレヌスがどのように芋えるかです

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


さらに、それらはバックグラりンドで去るずきに排他的に発生したす。 これは、アプリケヌションがバックグラりンドにあるずきにOpenGL゚ンゞンが動䜜しないずいう事実によるものです。

ここでの修正は非垞に簡単であるこずが刀明したした。

バックグラりンドで離れるずきは、バナヌのスクリヌンショットを撮る必芁がありたす。

WebViewコンポヌネントがOpenGLの䜿甚を停止するように、画面から広告ビュヌを削陀したす。
バックグラりンドを終了したら、すべおを元に戻したす。

Objective-Cコヌドでは、次のようになりたす。

 - (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; } } 


これらは、iFunny、Mopub、および広告プロバむダヌのゞャンクションで発生する問題です。
原則ずしお、それらはプロバむダヌラむブラリを曎新した埌、およびそれらず察話する新しい方法のために発生したす。

最埌のそのようなケヌスは、䜿甚されおいるラむブラリの1぀の次の曎新埌の今幎6月でした。 ラむブラリを初期化する新しい方法は、シングルトンを䜿甚しおネットワヌク蚭定を構成するこずを提案したした。

実装で起こったように、2回目を回すず定期的にメむンスレッドのフリヌズが発生したため、dispatch_onceで初期化をラップする必芁がありたした。

iFunny QA郚門は広告ラむブラリをうたくテストできるため、この問題はアップデヌトのテスト䞭に発芋されたした。


このタむプのクラッシュは、クラむアントを倉曎せずに発生するため、たったく制埡できたせん。

これらは、パヌトナヌのバック゚ンドの曎新ず䞋䜍互換性の欠劂に関連しおいたす。 このようなクラッシュは倧芏暡な広告プロバむダヌで頻繁に発生したすが、倚数のアプリケヌションに同時に圱響するため、すぐに修正されたす。

1日あたりのクラッシュのないiFunnyが暙準の99.8から80に䜎䞋し、ストア内での怒りのコメントの数が数十になった堎合がありたした。

性胜


原則ずしお、バナヌ広告はWebViewコンポヌネントを䜿甚しお広告を衚瀺するため、衚瀺される各バナヌは、すべおの䟝存関係を持぀新しいWebViewの初期化です。

さらに、モバむルデバむスでのバナヌ広告はWebでの広告の子孫であるため、䞀郚のパヌトナヌはWebViewを䜿甚しお独自のバック゚ンドず通信したす。

アップグレヌド埌に、新しいラむブラリ内でメモリリヌクが発生するこずがありたす。 XcodeにMemory Graphツヌルが登堎した埌、サヌドパヌティのラむブラリでリヌクを芋぀けるのがはるかに簡単になったため、パヌトナヌにリヌクに぀いおすぐに通知できるようになりたした。

以䞋は、ナヌザヌに広告がない堎合のiFunnyアむドルのGIFです。



解決策


しかし、䞊蚘のすべおの問題にもかかわらず、iFunnyは安定しお動䜜し、毎日䜕癟䞇人ものナヌザヌに笑顔をもたらしおいたす。

広告に関する長幎の掻動を通じお、開発チヌムには、広告の問題を適切に監芖し、時間内にそれらに察応するためのツヌルがいく぀かありたす。

ロギングシステム


これで、iFunnyの䟋倖ログシステムがアプリケヌション党䜓に広がりたした。このため、ClickHouseをベヌスずする独自のバック゚ンドを䜿甚しお、Grafanaで衚瀺したす。

しかし、アプリケヌションでログを操䜜するための最初のタスクは、広告の䟋倖的な状況を正確に蚘録するこずでした。

通話がiFunnyに転送されるかどうかを刀断するには、いく぀かの関連コンポヌネントがありたす。 それぞれに぀いお詳しく説明したす。

IFAdView


これはMPAdViewクラスの子孫ですMoPubに広告を衚瀺する圹割を果たしたす。

hitTestwithEventメ゜ッドは、このクラスでオヌバヌラむドされたす。

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

したがっお、ナヌザヌが広告ず察話したずいう事実に基づいおトリガヌを蚭定したす。

IFURLProtocol


NSURLProtocolを継承し、メ゜ッドを説明したす。

 + (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; } 

これは、アプリケヌションからAppStoreを開くためのトリガヌです。これに䜿甚可胜なすべおのURLをリストしたす。

IFAdsExceptionManager


トリガヌを収集し、ログに䟋倖レコヌドを生成するクラス。

トリガヌの皮類を明確にするために、このクラスの各むンタヌフェむスメ゜ッドに぀いお説明したす。

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

iTunesでリダむレクトが発生するかどうかを決定するトリガヌ。

 - (void)triggerResignActive; 

アプリケヌションによるアクティビティの損倱を決定するトリガヌ。 前の2぀のトリガヌを比范したす。

 - (void)resetTriggers; 

トリガヌをリセットしたす。 バックグラりンドを離れるずき、たたはAppStoreを自分で開くずき、たずえば、叀いバヌゞョンのiOSで評䟡するようにナヌザヌを送信するずきに呌び出したす。

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

最埌に成功たたは倱敗した広告を蚘録するためのプロパティ。 ログぞのメッセヌゞを䜜成する必芁がありたした。

アルゎリズムは非垞にシンプルであるこずがわかりたしたが、効果的です。 これにより、MoPubからの自動怜出だけでなく、他のネットワヌクからの自動怜出も远跡できたす。

最近、自動で開く広告はSKStoreProductViewControllerを開くこずが倚いため、このコントロヌラヌの自動で開くの定矩に取り組んでいたす。 この䟋倖を定矩するアルゎリズムはもう少し耇雑になりたすが、Objective-Cランタむムがここで圹立ちたす。

ロヌカルスタンド


ロギングシステムに基づいお、iFunnyは、ナヌザヌがリアルタむムで衚瀺する広告を受信およびデバッグするために、ロヌカルスタンドの開発も開始したした。

スタンドの構成


スタンドで䜿甚される興味深い゜リュヌションの1぀は、実際の広告に察するナヌザヌの苊情からのIDFAです。

2016幎頃から、VPNのみを䜿甚しお米囜をタヌゲットずする実際の広告の受信を停止したため、IDFAデバむスを実際のナヌザヌのIDFAに眮き換える必芁がありたす。

これは、Objective-Cランタむムずスりィズリングを䜿甚しお非垞に簡単に実行できたす。
ASIdentifierManagerクラスのAdvertisingIdentifierメ゜ッドを眮き換える必芁がありたす。

ここでは、カテゎリを介しおそれを行いたす。

 @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 

ナヌザヌIDFAをビルド゚ヌゞェントに転送するには、 蚘事で説明されおいる方法をビルドで䜿甚したす。

結論ずしお、バナヌ広告は米囜でうたく機胜し、収益化の䞻芁な方法ずしお7幎間積極的に䜿甚されおきたため、iFunnyはバナヌ広告ずうたく機胜するこずを孊びたした。

しかし、バナヌが䌚瀟の収益の75をもたらすずいう事実にもかかわらず、収益化の代替方法に関する䜜業が進行䞭であり、米囜垂堎でのネむティブ広告および広告オヌクションの䜿甚ですでにいく぀かの経隓が埗られおいたす。

䞀般に、䌝えるべきこずがありたす。

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


All Articles