原生广告自定义事件

前提条件

完成自定义事件设置

请求原生广告

当到达广告瀑布流中介链中的自定义事件订单项时,系统将针对您在创建自定义事件时提供的类名称调用the loadNativeAd:adConfiguration:completionHandler: method 。在本例中,该方法位于 SampleCustomEvent 中,后者随后会在 SampleCustomEventNative中调用the loadNativeAd:adConfiguration:completionHandler: method 。

若要请求原生广告,请创建或修改实现 GADMediationAdapterloadNativeAd:adConfiguration:completionHandler: 的类。如果已存在扩展 GADMediationAdapter 的类,请在该类中实现 loadNativeAd:adConfiguration:completionHandler:。此外,请创建一个新类来实现 GADMediationNativeAd

在我们的自定义事件示例中,SampleCustomEvent 会实现the GADMediationAdapter interface ,然后委托给SampleCustomEventNative

Swift

import GoogleMobileAds

class SampleCustomEvent: NSObject, GADMediationAdapter {

  fileprivate var nativeAd: SampleCustomEventNativeAd?

  func loadNativeAd(
    for adConfiguration: GADMediationNativeAdConfiguration,
    completionHandler: @escaping GADMediationNativeAdLoadCompletionHandler
  ) {
    self.nativeAd = SampleCustomEventNativeAd()
    self.nativeAd?.loadNativeAd(
      for: adConfiguration, completionHandler: completionHandler)
  }
}

Objective-C

#import "SampleCustomEvent.h"

@implementation SampleCustomEvent

SampleCustomEventNativeAd *sampleNativeAd;

- (void)loadNativeAdForAdConfiguration:
            (GADMediationNativeAdConfiguration *)adConfiguration
                     completionHandler:
                         (GADMediationNativeAdLoadCompletionHandler)
                             completionHandler {
  sampleNative = [[SampleCustomEventNativeAd alloc] init];
  [sampleNative loadNativeAdForAdConfiguration:adConfiguration
                             completionHandler:completionHandler];
}

SampleCustomEventNative 负责以下任务:

  • 加载原生广告

  • 实现 GADMediationNativeAd protocol

  • 接收广告事件回调并将其报告给 Google 移动广告 SDK

AdMob 界面中定义的可选参数会作为 the loadNativeAd:adConfiguration:completionHandler: method的一部分传递到自定义事件中。您可以通过 adConfiguration.credentials.settings[@"parameter"] 访问该参数。通常,此参数是实例化广告对象时广告联盟 SDK 所需的广告单元标识符。

Swift

class SampleCustomEventNativeAd: NSObject, GADMediationNativeAd {
  /// The Sample Ad Network native ad.
  var nativeAd: SampleNativeAd?

  /// The ad event delegate to forward ad rendering events to the Google Mobile
  /// Ads SDK.
  var delegate: GADMediationNativeAdEventDelegate?

  /// Completion handler called after ad load
  var completionHandler: GADMediationNativeLoadCompletionHandler?

  func loadNativeAd(
    for adConfiguration: GADMediationNativeAdConfiguration,
    completionHandler: @escaping GADMediationNativeLoadCompletionHandler
  ) {
    let adLoader = SampleNativeAdLoader()
    let sampleRequest = SampleNativeAdRequest()

    // The Google Mobile Ads SDK requires the image assets to be downloaded
    // automatically unless the publisher specifies otherwise by using the
    // GADNativeAdImageAdLoaderOptions object's disableImageLoading property. If
    // your network doesn't have an option like this and instead only ever
    // returns URLs for images (rather than the images themselves), your adapter
    // should download image assets on behalf of the publisher. This should be
    // done after receiving the native ad object from your network's SDK, and
    // before calling the connector's adapter:didReceiveMediatedNativeAd: method.
    sampleRequest.shouldDownloadImages = true
    sampleRequest.preferredImageOrientation = NativeAdImageOrientation.any
    sampleRequest.shouldRequestMultipleImages = false
    let options = adConfiguration.options
    for loaderOptions: GADAdLoaderOptions in options {
      if let imageOptions = loaderOptions as? GADNativeAdImageAdLoaderOptions {
        sampleRequest.shouldRequestMultipleImages =
          imageOptions.shouldRequestMultipleImages
        // If the GADNativeAdImageAdLoaderOptions' disableImageLoading property is
        // YES, the adapter should send just the URLs for the images.
        sampleRequest.shouldDownloadImages = !imageOptions.disableImageLoading
      } else if let mediaOptions = loaderOptions
        as? GADNativeAdMediaAdLoaderOptions
      {
        switch mediaOptions.mediaAspectRatio {
        case GADMediaAspectRatio.landscape:
          sampleRequest.preferredImageOrientation =
            NativeAdImageOrientation.landscape
        case GADMediaAspectRatio.portrait:
          sampleRequest.preferredImageOrientation =
            NativeAdImageOrientation.portrait
        default:
          sampleRequest.preferredImageOrientation = NativeAdImageOrientation.any
        }
      }
    }
    // This custom event uses the server parameter to carry an ad unit ID, which
    // is the most common use case.
    adLoader.delegate = self
    adLoader.adUnitID =
      adConfiguration.credentials.settings["parameter"] as? String
    self.completionHandler = completionHandler
    adLoader.fetchAd(sampleRequest)
  }
}

Objective-C

#import "SampleCustomEventNativeAd.h"

@interface SampleCustomEventNativeAd () <SampleNativeAdDelegate,
                                         GADMediationNativeAd> {
  /// The sample native ad.
  SampleNativeAd *_nativeAd;

  /// The completion handler to call when the ad loading succeeds or fails.
  GADMediationNativeLoadCompletionHandler _loadCompletionHandler;

  /// The ad event delegate to forward ad rendering events to the Google Mobile
  /// Ads SDK.
  id<GADMediationNativeAdEventDelegate> _adEventDelegate;
}
@end

- (void)loadNativeAdForAdConfiguration:
            (GADMediationNativeAdConfiguration *)adConfiguration
                     completionHandler:(GADMediationNativeLoadCompletionHandler)
                                           completionHandler {
  __block atomic_flag completionHandlerCalled = ATOMIC_FLAG_INIT;
  __block GADMediationNativeLoadCompletionHandler originalCompletionHandler =
      [completionHandler copy];

  _loadCompletionHandler = ^id<GADMediationNativeAdEventDelegate>(
      _Nullable id<GADMediationNativeAd> ad, NSError *_Nullable error) {
    // Only allow completion handler to be called once.
    if (atomic_flag_test_and_set(&completionHandlerCalled)) {
      return nil;
    }

    id<GADMediationNativeAdEventDelegate> delegate = nil;
    if (originalCompletionHandler) {
      // Call original handler and hold on to its return value.
      delegate = originalCompletionHandler(ad, error);
    }

    // Release reference to handler. Objects retained by the handler will also
    // be released.
    originalCompletionHandler = nil;

    return delegate;
  };

  SampleNativeAdLoader *adLoader = [[SampleNativeAdLoader alloc] init];
  SampleNativeAdRequest *sampleRequest = [[SampleNativeAdRequest alloc] init];

  // The Google Mobile Ads SDK requires the image assets to be downloaded
  // automatically unless the publisher specifies otherwise by using the
  // GADNativeAdImageAdLoaderOptions object's disableImageLoading property. If
  // your network doesn't have an option like this and instead only ever returns
  // URLs for images (rather than the images themselves), your adapter should
  // download image assets on behalf of the publisher. This should be done after
  // receiving the native ad object from your network's SDK, and before calling
  // the connector's adapter:didReceiveMediatedNativeAd: method.
  sampleRequest.shouldDownloadImages = YES;

  sampleRequest.preferredImageOrientation = NativeAdImageOrientationAny;
  sampleRequest.shouldRequestMultipleImages = NO;
  sampleRequest.testMode = adConfiguration.isTestRequest;

  for (GADAdLoaderOptions *loaderOptions in adConfiguration.options) {
    if ([loaderOptions isKindOfClass:[GADNativeAdImageAdLoaderOptions class]]) {
      GADNativeAdImageAdLoaderOptions *imageOptions =
          (GADNativeAdImageAdLoaderOptions *)loaderOptions;
      sampleRequest.shouldRequestMultipleImages =
          imageOptions.shouldRequestMultipleImages;

      // If the GADNativeAdImageAdLoaderOptions' disableImageLoading property is
      // YES, the adapter should send just the URLs for the images.
      sampleRequest.shouldDownloadImages = !imageOptions.disableImageLoading;
    } else if ([loaderOptions
                   isKindOfClass:[GADNativeAdMediaAdLoaderOptions class]]) {
      GADNativeAdMediaAdLoaderOptions *mediaOptions =
          (GADNativeAdMediaAdLoaderOptions *)loaderOptions;
      switch (mediaOptions.mediaAspectRatio) {
        case GADMediaAspectRatioLandscape:
          sampleRequest.preferredImageOrientation =
              NativeAdImageOrientationLandscape;
          break;
        case GADMediaAspectRatioPortrait:
          sampleRequest.preferredImageOrientation =
              NativeAdImageOrientationPortrait;
          break;
        default:
          sampleRequest.preferredImageOrientation = NativeAdImageOrientationAny;
          break;
      }
    } else if ([loaderOptions isKindOfClass:[GADNativeAdViewAdOptions class]]) {
      _nativeAdViewAdOptions = (GADNativeAdViewAdOptions *)loaderOptions;
    }
  }

  // This custom event uses the server parameter to carry an ad unit ID, which
  // is the most common use case.
  NSString *adUnit = adConfiguration.credentials.settings[@"parameter"];
  adLoader.adUnitID = adUnit;
  adLoader.delegate = self;

  [adLoader fetchAd:sampleRequest];
}

无论广告是已成功提取还是遇到错误,您都需要调用 GADMediationNativeAdLoadCompletionHandler。如果成功,请传递实现 GADMediationNativeAd 的类,其中错误参数的值为 nil;如果失败,请传递您遇到的错误。

通常,这些方法是在适配器实现的第三方 SDK 的回调内实现的。在此示例中,示例 SDK 具有包含相关回调的 SampleNativeAdDelegate

Swift

func adLoader(
  _ adLoader: SampleNativeAdLoader, didReceive nativeAd: SampleNativeAd
) {
  extraAssets = [
    SampleCustomEventConstantsSwift.awesomenessKey: nativeAd.degreeOfAwesomeness
      ?? ""
  ]

  if let image = nativeAd.image {
    images = [GADNativeAdImage(image: image)]
  } else {
    let imageUrl = URL(fileURLWithPath: nativeAd.imageURL)
    images = [GADNativeAdImage(url: imageUrl, scale: nativeAd.imageScale)]
  }
  if let mappedIcon = nativeAd.icon {
    icon = GADNativeAdImage(image: mappedIcon)
  } else {
    let iconURL = URL(fileURLWithPath: nativeAd.iconURL)
    icon = GADNativeAdImage(url: iconURL, scale: nativeAd.iconScale)
  }

  adChoicesView = SampleAdInfoView()
  self.nativeAd = nativeAd
  if let handler = completionHandler {
    delegate = handler(self, nil)
  }
}

func adLoader(
  _ adLoader: SampleNativeAdLoader,
  didFailToLoadAdWith errorCode: SampleErrorCode
) {
  let error =
    SampleCustomEventUtilsSwift.SampleCustomEventErrorWithCodeAndDescription(
      code: SampleCustomEventErrorCodeSwift
        .SampleCustomEventErrorAdLoadFailureCallback,
      description:
        "Sample SDK returned an ad load failure callback with error code: \(errorCode)"
    )
  if let handler = completionHandler {
    delegate = handler(nil, error)
  }
}

Objective-C

- (void)adLoader:(SampleNativeAdLoader *)adLoader
    didReceiveNativeAd:(SampleNativeAd *)nativeAd {
  if (nativeAd.image) {
    _images = @[ [[GADNativeAdImage alloc] initWithImage:nativeAd.image] ];
  } else {
    NSURL *imageURL = [[NSURL alloc] initFileURLWithPath:nativeAd.imageURL];
    _images = @[ [[GADNativeAdImage alloc] initWithURL:imageURL
                                                 scale:nativeAd.imageScale] ];
  }

  if (nativeAd.icon) {
    _icon = [[GADNativeAdImage alloc] initWithImage:nativeAd.icon];
  } else {
    NSURL *iconURL = [[NSURL alloc] initFileURLWithPath:nativeAd.iconURL];
    _icon = [[GADNativeAdImage alloc] initWithURL:iconURL
                                            scale:nativeAd.iconScale];
  }

  // The sample SDK provides an AdChoices view (SampleAdInfoView). If your SDK
  // provides image and click through URLs for its AdChoices icon instead of an
  // actual UIView, the adapter is responsible for downloading the icon image
  // and creating the AdChoices icon view.
  _adChoicesView = [[SampleAdInfoView alloc] init];
  _nativeAd = nativeAd;

  _adEventDelegate = _loadCompletionHandler(self, nil);
}

- (void)adLoader:(SampleNativeAdLoader *)adLoader
    didFailToLoadAdWithErrorCode:(SampleErrorCode)errorCode {
  NSError *error = SampleCustomEventErrorWithCodeAndDescription(
      SampleCustomEventErrorAdLoadFailureCallback,
      [NSString stringWithFormat:@"Sample SDK returned an ad load failure "
                                 @"callback with error code: %@",
                                 errorCode]);
  _adEventDelegate = _loadCompletionHandler(nil, error);
}

映射原生广告

对于原生广告,不同的 SDK 都有自己独特的格式。例如,一个 SDK 可能返回包含“title”字段的对象,而另一个 SDK 返回的对象可能包含“headline”字段。此外,用于跟踪展示和处理点击的方法也可能会因 SDK 而异。

为了解决这些问题,当自定义事件从参与中介的 SDK 收到原生广告对象时,应使用实现 GADMediationNativeAd 的类(如 SampleCustomEventNativeAd)来“映射”参与中介的 SDK 的原生广告对象,使其与 Google 移动广告 SDK 预期的接口相匹配。

现在,我们来详细了解一下 SampleCustomEventNativeAd 的实现详情。

存储映射

GADMediationNativeAd 应实现从其他 SDK 的属性映射到的特定属性:

Swift

var nativeAd: SampleNativeAd?

var headline: String? {
  return nativeAd?.headline
}

var images: [GADNativeAdImage]?

var body: String? {
  return nativeAd?.body
}

var icon: GADNativeAdImage?

var callToAction: String? {
  return nativeAd?.callToAction
}

var starRating: NSDecimalNumber? {
  return nativeAd?.starRating
}

var store: String? {
  return nativeAd?.store
}

var price: String? {
  return nativeAd?.price
}

var advertiser: String? {
  return nativeAd?.advertiser
}

var extraAssets: [String: Any]? {
  return [
    SampleCustomEventConstantsSwift.awesomenessKey:
      nativeAd?.degreeOfAwesomeness
      ?? ""
  ]
}

var adChoicesView: UIView?

var mediaView: UIView? {
  return nativeAd?.mediaView
}

Objective-C

/// Used to store the ad's images. In order to implement the
/// GADMediationNativeAd protocol, we use this class to return the images
/// property.
NSArray<GADNativeAdImage *> *_images;

/// Used to store the ad's icon. In order to implement the GADMediationNativeAd
/// protocol, we use this class to return the icon property.
GADNativeAdImage *_icon;

/// Used to store the ad's ad choices view. In order to implement the
/// GADMediationNativeAd protocol, we use this class to return the adChoicesView
/// property.
UIView *_adChoicesView;

- (nullable NSString *)headline {
  return _nativeAd.headline;
}

- (nullable NSArray<GADNativeAdImage *> *)images {
  return _images;
}

- (nullable NSString *)body {
  return _nativeAd.body;
}

- (nullable GADNativeAdImage *)icon {
  return _icon;
}

- (nullable NSString *)callToAction {
  return _nativeAd.callToAction;
}

- (nullable NSDecimalNumber *)starRating {
  return _nativeAd.starRating;
}

- (nullable NSString *)store {
  return _nativeAd.store;
}

- (nullable NSString *)price {
  return _nativeAd.price;
}

- (nullable NSString *)advertiser {
  return _nativeAd.advertiser;
}

- (nullable NSDictionary<NSString *, id> *)extraAssets {
  return
      @{SampleCustomEventExtraKeyAwesomeness : _nativeAd.degreeOfAwesomeness};
}

- (nullable UIView *)adChoicesView {
  return _adChoicesView;
}

- (nullable UIView *)mediaView {
  return _nativeAd.mediaView;
}

- (BOOL)hasVideoContent {
  return self.mediaView != nil;
}

除了 Google 移动广告 SDK 定义的素材资源之外,一些参与中介的广告联盟还可以提供额外的素材资源。GADMediationNativeAd 协议包含一个名为 extraAssets 的方法,Google 移动广告 SDK 使用该方法从您的映射器中检索所有这些“额外”素材资源。

映射图片素材资源

与映射较为简单的数据类型(如 NSStringdouble)相比,映射图片素材资源会更加复杂。图片可能会自动下载或作为网址值返回。而且其像素密度还会变化。

为了帮助您管理这些详细信息,Google 移动广告 SDK 提供了 GADNativeAdImage 类。开发者应该使用此类将图片素材资源信息(无论是实际的 UIImage 对象还是只是 NSURL 值)返回给 Google 移动广告 SDK。

下面的代码说明了映射器类如何创建 GADNativeAdImage 来保存图标图片:

Swift

if let image = nativeAd.image {
  images = [GADNativeAdImage(image: image)]
} else {
  let imageUrl = URL(fileURLWithPath: nativeAd.imageURL)
  images = [GADNativeAdImage(url: imageUrl, scale: nativeAd.imageScale)]
}

Objective-C

if (nativeAd.image) {
  _images = @[ [[GADNativeAdImage alloc] initWithImage:nativeAd.image] ];
} else {
  NSURL *imageURL = [[NSURL alloc] initFileURLWithPath:nativeAd.imageURL];
  _images = @[ [[GADNativeAdImage alloc] initWithURL:imageURL
                                               scale:nativeAd.imageScale] ];
}

展示事件和点击事件

Google 移动广告 SDK 和参与中介的 SDK 都需要知道展示或点击发生的时间,但只需要有一个 SDK 跟踪这些事件。有两种不同的方法可供自定义事件选用,具体取决于参与中介的 SDK 是否支持自行跟踪展示和点击。

使用 Google 移动广告 SDK 跟踪点击次数和展示次数

如果参与中介的 SDK 不自行执行展示和点击跟踪,但提供了记录点击和展示的方法,则 Google 移动广告 SDK 可以跟踪这些事件并通知适配器。GADMediationNativeAd protocol 包含两种方法:didRecordImpression:didRecordClickOnAssetWithName:view:viewController: ,自定义事件可实现对参与中介的原生广告对象调用相应的方法:

Swift

func didRecordImpression() {
  nativeAd?.recordImpression()
}

func didRecordClickOnAsset(
  withName assetName: GADUnifiedNativeAssetIdentifier,
  view: UIView,
  wController: UIViewController
) {
  nativeAd?.handleClick(on: view)
}

Objective-C

- (void)didRecordImpression {
  if (self.nativeAd) {
    [self.nativeAd recordImpression];
  }
}

- (void)didRecordClickOnAssetWithName:(GADUnifiedNativeAssetIdentifier)assetName
                                 view:(UIView *)view
                       viewController:(UIViewController *)viewController {
  if (self.nativeAd) {
    [self.nativeAd handleClickOnView:view];
  }
}

因为实现 GADMediationNativeAd protocol协议的类保留了对示例 SDK 的原生广告对象的引用,所以它可以调用该对象上的相应方法来报告点击或展示。请注意,didRecordClickOnAssetWithName:view:viewController: 方法仅接受一个参数:即与获得点击的原生广告素材资源对应的 View 对象。

使用参与中介的 SDK 跟踪点击和展示

一些参与中介的 SDK 可能更倾向于自行跟踪点击和展示。在这种情况下,您应该实现 handlesUserClickshandlesUserImpressions 方法,如以下代码段所示。通过返回 YES,您可以指明自定义事件会负责跟踪这些事件,并在这些事件发生时通知 Google 移动广告 SDK。

替换点击和展示跟踪的自定义事件可以使用 didRenderInView: 消息将原生广告视图传递给参与中介的 SDK 的原生广告对象,让参与中介的 SDK 可以进行实际跟踪。我们自定义事件示例项目(本指南中的代码段均选自这一项目)中的示例 SDK 并没有采用这种做法。但如果采用这种做法,自定义事件代码将会调用 setNativeAdView:view: 方法,如以下代码段所示:

Swift

func handlesUserClicks() -> Bool {
  return true
}
func handlesUserImpressions() -> Bool {
  return true
}

func didRender(
  in view: UIView, clickableAssetViews: [GADNativeAssetIdentifier: UIView],
  nonclickableAssetViews: [GADNativeAssetIdentifier: UIView],
  viewController: UIViewController
) {
  // This method is called when the native ad view is rendered. Here you would pass the UIView
  // back to the mediated network's SDK.
  self.nativeAd?.setNativeAdView(view)
}

Objective-C

- (BOOL)handlesUserClicks {
  return YES;
}

- (BOOL)handlesUserImpressions {
  return YES;
}

- (void)didRenderInView:(UIView *)view
       clickableAssetViews:(NSDictionary<GADNativeAssetIdentifier, UIView *> *)
                               clickableAssetViews
    nonclickableAssetViews:(NSDictionary<GADNativeAssetIdentifier, UIView *> *)
                               nonclickableAssetViews
            viewController:(UIViewController *)viewController {
  // This method is called when the native ad view is rendered. Here you would
  // pass the UIView back to the mediated network's SDK. Playing video using
  // SampleNativeAd's playVideo method
  [_nativeAd setNativeAdView:view];
}

将中介事件转发到 Google 移动广告 SDK

使用已加载的广告调用 GADMediationNativeLoadCompletionHandler 后,适配器便可以使用返回的 GADMediationNativeAdEventDelegate 委托对象,将展示事件从第三方 SDK 转发到 Google 移动广告 SDK。

您的自定义事件务必要转发尽可能多的此类回调,以便您的应用从 Google 移动广告 SDK 收到这些等效事件。下面是一个使用回调的示例:

到这里,我们已经实现原生广告的自定义事件。GitHub 上提供了完整的示例。