在使用 AdMob 廣告之前,要先登入或申請 AdMob 帳號後,在所建立的應用程式中取得「廣告單元」編號,依照網頁指示即可申請取得,在此就不多加詳述了。
AdMob 的 廣告單元編號 |
首先,我們要先下載兩個 Google Mobile Ads 相關的檔案:
- 在 Unity 專案中使用的 Plugin。
- 在 Xcode 專案中使用的 framework。
我們平常可以很簡單的在 Google 搜尋關鍵字 google mobile ads sdk 即可找到 Google Developer 網頁上有關如何在 Unity 使用 AdMob 的相關說明,在網頁上直接點擊藍色的按鈕「下載外掛程式」即可進入下載 Google Mobile Ads Unity Plugin,而點擊左側的「SDK 下載」即可找到下載 Google Mobile Ads SDK iOS 的連結。
藍色按鈕下載外掛程式,左側「SDK下載」可找到其他 SDK |
下載 GoogleMobileAds.unitypackage |
下載好相關檔案之後,直接將所下載的 GoogleMobileAds.unitypackage 匯入到 Unity 專案中,不用多做什麼,Plugin 的部分就已經算是建立完成,接下來就是寫 script 來使用它。
AdMob 廣告主要分為橫幅和插頁式兩種,這兩種的呼叫方式有些微不同,所以必須分開來製作,另外,基本上測試時不應該投放真實的廣告,而是提供測試機的 Device ID 給 AdMob 來投放測試廣告,所以,還需要另外提供給橫幅及插頁廣告公用的 Script 以取得 Device ID。
這個 AdMob 所需要的 Device ID 其實有些特別,如果在 Google Play 下載一些取得 Device ID 的 App 來測試,會發現幾個 App 所取得的都是不同的 ID,通常,AdMob Device ID Finder 所取得的 ID 才是比較符合 AdMob 所需要的。
一般而言,只要將這個 App 安裝到所有的測試機上面取出 Device ID 之後,在程式碼或是 Unity 之中建表紀錄,這樣 AdMob 就能知道哪幾台機器正在做為測試機請求投放廣告,只是這種做法有點死板,特別是在將 App 提供給很多人測試時,會變得相當麻煩,所以我們必須要有一套機制。
如果要發佈為測試用的 App,在 Unity 中要 Build 時都應該勾選 Development Build,然後,在程式碼中透過 Debug.isDebugBuild 直接判斷是否為開發中的版本,並直接自該裝置取得 Deveice ID 告知 AdMob 本機為測試機,使用這種一勞永逸的機制,將來就不需要收集測試機的 Device ID 來建表紀錄了。
而這裡所需要的 Device ID 是個加密過的編碼,每個平台的取得方式不同,所以還需要使用 Unity 官方文件中的 Platform Dependent Compilation 中的這些 Platform Defines 區隔開來個別撰寫取得方式,以下,將可得到這樣的公用 Script 內容:
using UnityEngine;
using System;
using System.Text;
using System.Security.Cryptography;
public class AdCommon {
private static string Md5Sum(string strToEncrypt){
UTF8Encoding ue = new UTF8Encoding();
byte[] bytes = ue.GetBytes(strToEncrypt);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hashBytes = md5.ComputeHash(bytes);
string hashString = "";
for (int i = 0; i < hashBytes.Length; i++) {
hashString += Convert.ToString(hashBytes[i], 16).PadLeft(2 , '0');
}
return hashString.PadLeft(32, '0');
}
public static string DeviceIdForAdmob{
get{
#if UNITY_EDITOR
return SystemInfo.deviceUniqueIdentifier;
#elif UNITY_ANDROID
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
AndroidJavaObject secure = new AndroidJavaObject("android.provider.Settings$Secure");
string deviceID = secure.CallStatic<string>("getString" , contentResolver, "android_id");
return Md5Sum(deviceID).ToUpper();
#elif UNITY_IOS
return Md5Sum(UnityEngine.iOS.Device.advertisingIdentifier);
#else
return SystemInfo.deviceUniqueIdentifier;
#endif
}
}
}
using System;
using System.Text;
using System.Security.Cryptography;
public class AdCommon {
private static string Md5Sum(string strToEncrypt){
UTF8Encoding ue = new UTF8Encoding();
byte[] bytes = ue.GetBytes(strToEncrypt);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hashBytes = md5.ComputeHash(bytes);
string hashString = "";
for (int i = 0; i < hashBytes.Length; i++) {
hashString += Convert.ToString(hashBytes[i], 16).PadLeft(2 , '0');
}
return hashString.PadLeft(32, '0');
}
public static string DeviceIdForAdmob{
get{
#if UNITY_EDITOR
return SystemInfo.deviceUniqueIdentifier;
#elif UNITY_ANDROID
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
AndroidJavaObject secure = new AndroidJavaObject("android.provider.Settings$Secure");
string deviceID = secure.CallStatic<string>("getString" , contentResolver, "android_id");
return Md5Sum(deviceID).ToUpper();
#elif UNITY_IOS
return Md5Sum(UnityEngine.iOS.Device.advertisingIdentifier);
#else
return SystemInfo.deviceUniqueIdentifier;
#endif
}
}
}
完成了以上的 AdCommon Script 之後,就有了取得 Device ID 的方法,接下來,就可以開始撰寫橫幅廣告以及插頁廣告的 Script。
在原本的 Plugin 中,由於,橫幅廣告的尺寸定義(AdSize)本身是個 Class,不同尺寸項目使用靜態唯讀的欄位來宣告,為了方便在 Unity 的 Inspector 視窗用選項的方式來設置,我們需要另外宣告個 enum 以及配合 Dictionary 來指定橫幅廣告尺寸。
同時,為了能夠在整個專案中的任何其它 Script 中可以直接請求橫幅廣告的功能,還需要特別宣告一個代表 Class 本身的靜態欄位 ins 並使物件本身不會因為轉換場景而被銷毀。
剩下的就是操作橫幅廣告的功能,基本上,橫幅廣告在請求投放廣告下載完成後,即會直接顯示在畫面上,之後,再以畫面需求在不同時間點讓它隱藏或重新顯示,而不需要再次請求投放,原則上,在持續投放廣告的過程中,應該會自動更換不同的廣告。
其中,我們要特別注意的是,在請求投放廣告的時候必須要判斷目前所安裝的是不是開發中版本 Development Build,並讓使用此版本的裝置都自動將本機的 Device ID 告知 AdMob,讓它投放測試用廣告,以免在測試過程中重複點擊廣告而被判定不當點擊廣告,因此而被 AdMob 廣告停權就太不划算了。
using System.Collections.Generic;
using GoogleMobileAds.Api;
public class AdBanner : MonoBehaviour {
public enum BannerSize{
BANNER,
MEDIUM_RECTANGLE,
FULL_BANNER,
LEADERBOARD,
SMART_BANNER
}
public static AdBanner ins;
public string unitId;
public BannerSize size = BannerSize.BANNER;
public AdPosition position = AdPosition.Top;
private BannerView bannerView;
private Dictionary<BannerSize,AdSize> adSize = new Dictionary<BannerSize, AdSize>(){
{BannerSize.BANNER , AdSize.Banner},
{BannerSize.MEDIUM_RECTANGLE , AdSize.MediumRectangle},
{BannerSize.FULL_BANNER , AdSize.IABBanner},
{BannerSize.LEADERBOARD , AdSize.Leaderboard},
{BannerSize.SMART_BANNER , AdSize.SmartBanner}
};
void Awake(){
if(ins == null){
ins = this;
DontDestroyOnLoad(gameObject);
}else if(ins != this){
Destroy(gameObject);
}
}
void Start(){
this.RequestBanner();
}
private void RequestBanner(){
if(!string.IsNullOrEmpty(this.unitId)){
this.bannerView = new BannerView(this.unitId , this.adSize[this.size] , this.position);
AdRequest.Builder _builder = new AdRequest.Builder();
if(Debug.isDebugBuild) _builder.AddTestDevice(AdCommon.DeviceIdForAdmob);
this.bannerView.LoadAd(_builder.Build());
}
}
public void ShowBanner(){
if(this.bannerView != null){
this.bannerView.Show();
}
}
public void HideBanner(){
if(this.bannerView != null){
this.bannerView.Hide();
}
}
}
using GoogleMobileAds.Api;
public class AdBanner : MonoBehaviour {
public enum BannerSize{
BANNER,
MEDIUM_RECTANGLE,
FULL_BANNER,
LEADERBOARD,
SMART_BANNER
}
public static AdBanner ins;
public string unitId;
public BannerSize size = BannerSize.BANNER;
public AdPosition position = AdPosition.Top;
private BannerView bannerView;
private Dictionary<BannerSize,AdSize> adSize = new Dictionary<BannerSize, AdSize>(){
{BannerSize.BANNER , AdSize.Banner},
{BannerSize.MEDIUM_RECTANGLE , AdSize.MediumRectangle},
{BannerSize.FULL_BANNER , AdSize.IABBanner},
{BannerSize.LEADERBOARD , AdSize.Leaderboard},
{BannerSize.SMART_BANNER , AdSize.SmartBanner}
};
void Awake(){
if(ins == null){
ins = this;
DontDestroyOnLoad(gameObject);
}else if(ins != this){
Destroy(gameObject);
}
}
void Start(){
this.RequestBanner();
}
private void RequestBanner(){
if(!string.IsNullOrEmpty(this.unitId)){
this.bannerView = new BannerView(this.unitId , this.adSize[this.size] , this.position);
AdRequest.Builder _builder = new AdRequest.Builder();
if(Debug.isDebugBuild) _builder.AddTestDevice(AdCommon.DeviceIdForAdmob);
this.bannerView.LoadAd(_builder.Build());
}
}
public void ShowBanner(){
if(this.bannerView != null){
this.bannerView.Show();
}
}
public void HideBanner(){
if(this.bannerView != null){
this.bannerView.Hide();
}
}
}
插頁廣告的 Script 做法與橫幅廣告相當類似,不過並不需要設置廣告尺寸以及位置,比較不同的是,插頁廣告並不會請求投放廣告成功之後就顯示出來,而是分段執行的,所以在請求投放之後要另外執行顯示廣告,為了確保廣告能正確投放,在執行顯示廣告之前必須判斷是否已經下載完成。
還有,在每次顯示廣告之前都必須重新請求廣告投放,所以在請求投放廣告時,要將原本的廣告物件銷毀後再重新請求投放廣告。
using UnityEngine;
using System;
using GoogleMobileAds.Api;
public class AdInterstitial : MonoBehaviour {
public static AdInterstitial ins;
public string unitId;
private InterstitialAd interstitialAd;
void Awake(){
if(ins == null){
ins = this;
DontDestroyOnLoad(gameObject);
}else if(ins != this){
Destroy(gameObject);
}
}
public void RequestInterstitial(){
if(!string.IsNullOrEmpty(this.unitId)){
if(this.interstitialAd != null){
this.interstitialAd.Destroy();
this.interstitialAd = null;
}
this.interstitialAd = new InterstitialAd(this.unitId);
AdRequest.Builder _builder = new AdRequest.Builder();
if(Debug.isDebugBuild) _builder.AddTestDevice(AdCommon.DeviceIdForAdmob);
this.interstitialAd.LoadAd(_builder.Build());
}
}
public void ShowInterstitial(){
if(this.interstitialAd != null && this.interstitialAd.IsLoaded()){
this.interstitialAd.Show();
}
}
}
using System;
using GoogleMobileAds.Api;
public class AdInterstitial : MonoBehaviour {
public static AdInterstitial ins;
public string unitId;
private InterstitialAd interstitialAd;
void Awake(){
if(ins == null){
ins = this;
DontDestroyOnLoad(gameObject);
}else if(ins != this){
Destroy(gameObject);
}
}
public void RequestInterstitial(){
if(!string.IsNullOrEmpty(this.unitId)){
if(this.interstitialAd != null){
this.interstitialAd.Destroy();
this.interstitialAd = null;
}
this.interstitialAd = new InterstitialAd(this.unitId);
AdRequest.Builder _builder = new AdRequest.Builder();
if(Debug.isDebugBuild) _builder.AddTestDevice(AdCommon.DeviceIdForAdmob);
this.interstitialAd.LoadAd(_builder.Build());
}
}
public void ShowInterstitial(){
if(this.interstitialAd != null && this.interstitialAd.IsLoaded()){
this.interstitialAd.Show();
}
}
}
在完成以上三個程式檔之後,有關投放廣告的功能算是已經完成,我們可以自行利用 UGUI 製作畫面上的按鈕來測試,使用上只要在場景中分別建立橫幅廣告以及插頁廣告專用的 GameObject 並賦予 AdBanner 以及 AdInterstitial 後,設置好廣告單元編號 Unit Id 即可直接在 Unity 編輯器中做初步的測試。測試時,可在 Console 視窗看到相關的對應訊息。
設置好 Ad Banner 的廣告單元編號以及尺寸、位置 |
設置好 Ad Interstitial 的廣告單元編號 |
使用 UGUI 製作的測試按鈕 |
顯示測試相對應的訊息 |
iOS
在 Unity 編輯器中測試無誤後,就可以直接 Build 到 iOS 平台建立 Xcode 專案,在 Build 的時候務必記得勾選 Development Build。
記得勾選 Development Build |
選擇 Add Files to |
為 Xcode 專案加入 GoogleMobileAds.framework |
添加需要的 framework |
發生有關 @import 的問題 |
將 Enable Modules (C and Objective-C) 設置為 Yes |
關閉限制廣告追蹤 |
在 Android 實裝 AdMob 廣告時,還需要在 Unity 專案中加入 Google Play Service 的 Library,這個 Library 可在 {Android SDK 路徑}/extras/google/google_play_services/libproject/google-play-services_lib 找到,直接將 google-play-services_lib 資料夾整個放到 Unity 專案的 Plugins/Android 路徑下就可以了,之後,直接 Build 成為 apk 檔並安裝到 Android 裝置上即可,當然,Build 時記得也要勾選 Development Build 才可以正常投放測試廣告。
另外,Google Developers 網站上提到要在 Unity 專案裡的 Plugins/Android/AndroidManifest.xml 加入 <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" /> 以確保廣告可供點擊,但實際使用時,加入這一行反倒是讓 UGUI 按鈕無法點擊,經測試,不需要修改 AndroidManifest.xml 即可正常播放廣告且可正常點擊,因此,這邊就先不理會此部份。
以上就是有關於實作和測試 AdMob 廣告,以及 Build 到實機測試的部分,一般而言,廣告都投放正常,並可正確點擊就可以了;不過,Google Developers 網站上還提供了其他指定請求可以使用,有興趣可以研究一下;當然,也提供了廣告事件,可以在某些廣告狀況之後,再做其他的事情,只是廣告事件是依賴廣告物件去註冊使用的,所以,一般不應該直接註冊,而是要先確認廣告物件是否存在,所以,最好是另外宣告方法去註冊事件以及取消事件,以上的 AdBanner 可以再補充以下方法來做廣告事件的註冊以及取消。
public void RegisterBannerLoaded(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLoaded += _handle;
}
public void RegisterBannerFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdFailedToLoad += _handle;
}
public void RegisterBannerOpened(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdOpened += _handle;
}
public void RegisterBannerClosing(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosing += _handle;
}
public void RegisterBannerClosed(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosed += _handle;
}
public void RegisterBannerLeftApplication(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLeftApplication += _handle;
}
public void CancelBannerLoaded(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLoaded -= _handle;
}
public void CancelBannerFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdFailedToLoad -= _handle;
}
public void CancelBannerOpened(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdOpened -= _handle;
}
public void CancelBannerClosing(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosing -= _handle;
}
public void CancelBannerClosed(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosed -= _handle;
}
public void CancelBannerLeftApplication(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLeftApplication -= _handle;
}
if(this.bannerView == null) return;
this.bannerView.AdLoaded += _handle;
}
public void RegisterBannerFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdFailedToLoad += _handle;
}
public void RegisterBannerOpened(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdOpened += _handle;
}
public void RegisterBannerClosing(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosing += _handle;
}
public void RegisterBannerClosed(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosed += _handle;
}
public void RegisterBannerLeftApplication(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLeftApplication += _handle;
}
public void CancelBannerLoaded(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLoaded -= _handle;
}
public void CancelBannerFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdFailedToLoad -= _handle;
}
public void CancelBannerOpened(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdOpened -= _handle;
}
public void CancelBannerClosing(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosing -= _handle;
}
public void CancelBannerClosed(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdClosed -= _handle;
}
public void CancelBannerLeftApplication(EventHandler<EventArgs> _handle){
if(this.bannerView == null) return;
this.bannerView.AdLeftApplication -= _handle;
}
例如,當需要在橫幅廣告被載入之後處理一些事情,可使用以下程式碼實現:
public void HandleAdLoaded(object sender, EventArgs args)
{
// ... do something...
}
AdBanner.ins.RegisterBannerLoaded(this.HandleAdLoaded);
{
// ... do something...
}
AdBanner.ins.RegisterBannerLoaded(this.HandleAdLoaded);
相同的做法,AdInterstitial 也可以補充相關的事件註冊與取消,程式碼如下:
public void RegisterInterstitialLoaded(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdLoaded += _handle;
}
public void RegisterInterstitialFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdFailedToLoad += _handle;
}
public void RegisterInterstitialOpened(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdOpened += _handle;
}
public void RegisterInterstitialClosing(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosing += _handle;
}
public void RegisterInterstitialClosed(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosed += _handle;
}
public void RegisterInterstitialLeftApplication(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdLeftApplication += _handle;
}
public void CancelInterstitialLoaded(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdLoaded -= _handle;
}
public void CancelInterstitialFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdFailedToLoad -= _handle;
}
public void CancelInterstitialOpened(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdOpened -= _handle;
}
public void CancelInterstitialClosing(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosing -= _handle;
}
public void CancelInterstitialClosed(EventHandler<EventArgs> _handle){
if(interstitialAd == null) return;
interstitialAd.AdClosed -= _handle;
}
public void CancelInterstitialLeftApplication(EventHandler<EventArgs> _handle){
if(interstitialAd == null) return;
interstitialAd.AdLeftApplication -= _handle;
}
if(this.interstitialAd == null) return;
this.interstitialAd.AdLoaded += _handle;
}
public void RegisterInterstitialFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdFailedToLoad += _handle;
}
public void RegisterInterstitialOpened(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdOpened += _handle;
}
public void RegisterInterstitialClosing(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosing += _handle;
}
public void RegisterInterstitialClosed(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosed += _handle;
}
public void RegisterInterstitialLeftApplication(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdLeftApplication += _handle;
}
public void CancelInterstitialLoaded(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdLoaded -= _handle;
}
public void CancelInterstitialFailedToLoad(EventHandler<AdFailedToLoadEventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdFailedToLoad -= _handle;
}
public void CancelInterstitialOpened(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdOpened -= _handle;
}
public void CancelInterstitialClosing(EventHandler<EventArgs> _handle){
if(this.interstitialAd == null) return;
this.interstitialAd.AdClosing -= _handle;
}
public void CancelInterstitialClosed(EventHandler<EventArgs> _handle){
if(interstitialAd == null) return;
interstitialAd.AdClosed -= _handle;
}
public void CancelInterstitialLeftApplication(EventHandler<EventArgs> _handle){
if(interstitialAd == null) return;
interstitialAd.AdLeftApplication -= _handle;
}
Google 的 AdMob 廣告應該算目前行動 App 使用最廣泛的,不過,使用 Unity 遊戲引擎通常都是用於開發遊戲產品,Unity 官方也提供了 Unity Ads 讓人投放遊戲相關的廣告並讓開發者也有機會從中獲取利潤,那也是個不錯的選擇。
範例專案下載
目前版本:
OS X Yosemite 10.10.4
Xcode 6.4
Unity 5.1.1f1
Google Mobile Ads Unity Plugin v2.3.0
Target iOS Version: 6.0
Google Mobile Ads iOS v7.3.1
Test Device: iOS 8.4
Minimum API Level: Android 2.3.1 (API Level 9)
Android SDK Build-tools: Rev. 21.1.2
Android 5.0.1 (API 21)
Test Device: Android 4.2.2