スマートフォンにおける広告配信用SDKの設計について考えてみた

アプリのマネタイズ方法の一つである広告収入を実現するために必要な広告配信用SDKの設計について考えました。

## アプリの作りによる違いについて

現在、AppstoreやGooglePlay上に存在するアプリ数は膨大で、作りも多種多様になっている。
それに伴い配信される広告や配信方法も様々になっている。

アプリの作りは、以下に分類されることが多い気がします。
※主観で勝手に定義してます。

- ネイティブアプリ

定義：アニメーションをあまり使わず、端末の機能に依存する処理が多いアプリ
開発環境：Xcode,Eclipse,Android Studioなど
配信方法：広告配信用SDK,API
広告：アプリの下部や上部に320X50サイズの広告を固定で配信する。

- Webアプリ

定義：既存Webサイトを活用し、アプリでもWebサイトを利用できるようにすることが目的のアプリ
開発環境：Titanium Mobile,PhoneGap,Xamarin,Unity
配信方法：広告配信用タグ
広告：Webサイトへ配信する300X200サイズ、320X50サイズの広告と同じような広告を配信する。

- ハイブリットアプリ

定義：ロジック部分をネイティブコード、UI/UXなどのView部分をWebコードで記述しているアプリ
開発環境：Xcode,Eclipse,Titanium Mobile,PhoneGap,Xamarin,Unity
配信方法：広告配信用SDK,広告配信用タグ,API
広告：300X200サイズ、320X50サイズの広告以外に、インタースティシャル広告（全画面）などをアプリの利用途中に配信する。

上記のように様々な方法で作られ、様々な方法で広告が配信される。

## 広告配信における問題点

広告配信において配信方法それぞれに問題があり、それを解決することが求められている。

- 広告配信タグ
javascriptタグを用いてアプリに表示するため、IDFAやADIDなどの端末を識別するための情報を取得することができない。端末やOSによっては、広告表示が崩れる可能性がある。

- 広告配信用SDK
組み込みが難しいため、導入を懸念されることが多い。また、SDK起因でアプリのアップデートが発生する可能性があり、顧客への説明が大変。SDK起因でアプリが落ちると保証問題に発展する。他SDKとの競合が発生する可能性がある。

- API
API提供のため、実績を正確に計測できない可能性がある。広告配信タグと同様に端末を識別するための情報を取得することができない。

特に、広告配信用SDKは問題点が多く、軽量でシンプルな一度組み込みを行えばアップデートやメンテナンスの必要のないSDKが求められる。

## SDKとjavasriptの連動

軽量でシンプル、アップデートやメンテナンスの必要ないSDKを作るためには、
ロジックを外だしすることが重要になってくる。
外だしとは、アプリ申請後にロジックを変更できるようにすることである。

ロジックを外だしするには、javascriptでロジックを記述し、SDKからそのjavascriptを読み込む方法が必要になる。

iOSの場合、以下のようなWebViewのdelegateとjavascriptを使うことで連動することができる。

参考：
[iPhoneのネイティブ機能をWebViewから呼び出す方法（１）](http://blog.engineer.adways.net/archives/10172788.html)

``` sample.html
<html>
<head></head>
<body>
<script>
  var X ="1";
  var Y ="2";
  var Z ="3";
  var scheme = "jp.co.takuhou";

  var callNative = function(url){
    location.href = url + "://callback";
  }
  callNative(scheme);
</script>
</body>
</html>
```

``` ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIWebViewDelegate>{
    UIWebView *wv;
}

@end
```

``` ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    wv = [[UIWebView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
    wv.delegate = self;
    
    [self.view addSubview:wv];

    NSURL *url = [NSURL URLWithString:@"http://XXXXXX.co.jp/sample.html"];
    [wv loadRequest:[NSURLRequest requestWithURL:url]];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void) callback{
    NSString *X = [wv stringByEvaluatingJavaScriptFromString:@"getx();"];
    NSString *Y = [wv stringByEvaluatingJavaScriptFromString:@"gety();"];
    NSString *Z = [wv stringByEvaluatingJavaScriptFromString:@"getz();"];

    NSLog(@"%@",X);
    NSLog(@"%@",Y);
    NSLog(@"%@",X);    
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    
    NSString *requestString = [[request URL] absoluteString];
    NSLog(@"%@",requestString);
    if ([requestString rangeOfString:@"jp.co.takuhou://"].location == NSNotFound){
        return YES;
    }
    
    NSError *
    
    error = nil;
    NSString *pattern = [NSString stringWithFormat:@"^jp.co.takuhou://(.+)$"];
    NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
    
    if(error != nil){
        NSLog(@"%@", [error description]);
        return NO;
    }
    
    NSTextCheckingResult *match = [reg firstMatchInString:requestString options:0 range:NSMakeRange(0, requestString.length)];

    if(match.numberOfRanges == 0){
        NSLog(@"not match");
        return NO;
    }
    
    NSString *method = [requestString substringWithRange:[match rangeAtIndex:1]];
    
    if([method isEqualToString:@"callback"]){
        [self callback];
    }
    return NO;
}

@end
```

処理の流れは以下の通り

①SDKで0X0のWebViewを作成&sample.htmlを読み込み
②sample.html内のjavascriptを実行し、SDK内の[callback]メソッドの実行
③javascript内の[getx],[gety],[getz]を実行し、値を取得

これは、アプリ側で設定したURLスキームを利用してjavascriptからネイティブコードを呼び出すことが実現することができます。

ネイティブからjavascriptコードを呼び出すときは、`stringByEvaluatingJavaScriptFromString`メソッドでWebViewで定義されているjavascriptのメソッドを実行します。

参考：
[iOSアプリ未インストール時にSafariでカスタムURLスキームを開く時の対策](http://qiita.com/makoto_kw/items/5a99e37f7172337ae4be)

WebViewのdelegateは、HTTPのリクエストを行う際に呼ばれるもので、javascriptからURLスキームが叩かれた際に、delegateが呼ばれ、その通信をフェッチ&解析し、ネイティブコードのメソッドが実行される。

この仕組みをうまく利用することで、SDKからロジック部分を外だしすることが可能になる。

## 広告におけるjavascriptの連動

[IAB](http://www.iab.net/)という広告の団体が、[mraid.js](http://www.iab.net/MRAID)というもの発表している。

MRAIDとは、Mobile Rich Media Ad Interface Definitionsの略で、リッチ広告を表現するためのインターフェース仕様のことです。

具体的には、バナー広告(320X50)タップ時に、ポップアップでレクタングル広告(300X250)が表示されるような動きの場合、タップ時に、`expand`という名前のjavascriptメソッドを実行し、アプリ側でそれを検知し、広告のViewサイズを300X250サイズに変更するような処理が行われる。

iframeバスターのような考え方に似ています。

## まとめ

MVCでいうと、SDKは`V`、javascriptが`MC`を役割を担うするように作りを分ければ、イケてるSDKを作成することができると思う。

今後、広告配信用SDKは淘汰され、幾つかの代表的なSDKに集約していくと思う。
それに伴いSDKの機能は増え、作りが複雑化し、バグが発生する可能性が高くなる。

バグが発生した際に迅速に対応できるよう、またSDKを自体をシンプルに作れるよう、
ロジックを外だしすることが今後のイケてるSDKの姿だと思う。