# Android SDK ネイティブ広告
# はじめに
開発環境としてAndroid Studioを利用し、インストール後の各種設定は準備されていることを前提にした手順となります。
# 対応バージョン
- Android 5.0以降(API Level 21)
# 導入の流れ
- SDKをダウンロードします
↓ - プロジェクトにSDKを追加します
↓ - AndroidManifest.xmlの設定を行います
↓ - Google Play Servicesの設定を行います
↓ - 例を参考に広告表示の実装を行います
↓ - proguardの設定を行います
# 1. SDKをダウンロードする ~ 4. Google Play Servicesを設定する
Android SDK Getting Started / バナー広告からご確認ください。
# 5. 広告表示を実装する
start()
を行う前に、ネイティブ広告オブジェクトをdelegateメソッドで取得するためのsetUsePartsResponse()
の設定を行います。
ネイティブ広告オブジェクトが取得できた場合、Listenerメソッドの以下が呼び出されます。
override fun onReceiveAd(_nativeAd: Any)
public void onReceiveAd(Object mediationNativeAd)
mediationNativeAd
からネイティブ広告オブジェクトが取得できます。
ネイティブ広告オブジェクトはADGNativeAd
クラスです。
Object型からADGNativeAd
へキャストし、クラス判定を行ってからアクセスしてください。
ADGNativeAd
からネイティブ広告パーツを取得するメソッドは以下の通りです。
com.socdm.d.adgeneration.nativead.ADGNativeAd
要素名 | メソッド |
---|---|
タイトル | ADGNativeAd.getTitle().getText() |
メイン画像(長方形画像)URL | ADGNativeAd.getMainImage().getUrl() |
アイコン画像(正方形画像)URL | ADGNativeAd.getIconImage().getUrl() |
リード文 | ADGNativeAd.getDesc().getValue() |
CTA(Call to action)のテキスト | ADGNativeAd.getCtatext().getValue() |
広告主 | ADGNativeAd.getSponsored().getValue() |
# 実装例
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "ADGListener"
}
private var adg: ADG? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Ad Generationのインスタンスを初期化
adg = ADG(this).apply {
locationId = "48635" // 管理画面から払い出された広告枠ID
adListener = AdListener() // Listenerの設定
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
}
/**
* 枠サイズ
* AdFrameSize.SP:320x50, AdFrameSize.Large:320x100,
* AdFrameSize.Rect:300x250, AdFrameSize.Tablet:728x90,
* AdFrameSize.Free:自由設定
*/
adg?.setAdFrameSize(ADG.AdFrameSize.FREE.setSize(300, 250))
// ネイティブ広告パーツ取得を有効
adg?.setUsePartsResponse(true)
// インフォメーションアイコンのデフォルト表示
// デフォルト表示しない場合は必ずADGInformationIconViewの設置を実装してください
adg?.setInformationIconViewDefault(false)
}
override fun onResume() {
super.onResume()
// 広告表示/ローテーション再開
adg?.start()
}
override fun onPause() {
super.onPause()
// ローテーション停止
adg?.pause()
}
override fun onDestroy() {
super.onDestroy()
// ローテーション停止
adg?.pause()
}
internal inner class AdListener : ADGListener() {
override fun onReceiveAd() {
Log.d(TAG, "Received an ad.")
}
override fun onReceiveAd(_nativeAd: Any) {
Log.d(TAG, "Received an native ad.")
if (ad_container.childCount > 0) {
ad_container.removeAllViews()
}
var nativeAdView: ViewGroup? = null
when(_nativeAd) {
is ADGNativeAd -> nativeAdView = ADGNativeAdHelper.createAdView(_nativeAd, this@MainActivity)
}
nativeAdView?.let {
val mediationView = adg?.getNativeMediationView(it)
// ローテーション時に自動的にViewを削除します
adg?.setAutomaticallyRemoveOnReload(mediationView)
ad_container.addView(mediationView, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
}
}
override fun onFailedToReceiveAd(code: ADGConsts.ADGErrorCode) {
Log.d(TAG, "Failed to receive an ad:$code");
// ネットワーク不通/エラー多発/広告レスポンスなし 以外はリトライしてください
when (code) {
ADGConsts.ADGErrorCode.EXCEED_LIMIT, ADGConsts.ADGErrorCode.NEED_CONNECTION, ADGConsts.ADGErrorCode.NO_AD -> {}
else -> adg?.start()
}
}
}
private val func = Runnable { finish() }
}
public class MainActivity extends AppCompatActivity {
private FrameLayout adContainer;
private ADG adg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adg = new ADG(this);
// 管理画面から払い出された広告枠ID
adg.setLocationId("48635");
/**
* 枠サイズ
* AdFrameSize.SP:320x50, AdFrameSize.Large:320x100,
* AdFrameSize.Rect:300x250, AdFrameSize.Tablet:728x90,
* AdFrameSize.Free:自由設定
*/
adg.setAdFrameSize(ADG.AdFrameSize.FREE.setSize(300, 250));
// ネイティブ広告パーツ取得を有効
adg.setUsePartsResponse(true);
// インフォメーションアイコンのデフォルト表示
// デフォルト表示しない場合は必ずADGInformationIconViewの設置を実装してください
adg.setInformationIconViewDefault(false);
// Listenerの設定
adg.setAdListener(new AdListener());
// HTMLテンプレートを使用したネイティブ広告を表示のためにはaddViewする必要があります
adContainer = (FrameLayout) findViewById(R.id.ad_container);
adContainer.addView(adg);
findViewById(R.id.btn_stopstart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (adg != null) {
// 広告表示/ローテーション再開
adg.stop();
adg.start();
}
}
});
}
@Override
protected void onPause() {
super.onPause();
if (adg != null) {
// ローテーション停止
adg.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (adg != null) {
// 広告を破棄する
adg.stop();
}
}
class AdListener extends ADGListener {
private static final String TAG = "ADGListener";
@Override
public void onReceiveAd() {
Log.d(TAG, "Received an ad.");
}
@Override
public void onReceiveAd(Object o) {
Log.d(TAG, "Received an ad.");
View view = null;
if (o instanceof ADGNativeAd) {
ADGNativeAdView nativeAdView = new ADGNativeAdView(MainActivity.this);
nativeAdView.apply((ADGNativeAd) o);
view = nativeAdView;
}
if (view != null) {
// ローテーション時に自動的にViewを削除します
adg.setAutomaticallyRemoveOnReload(view);
adContainer.addView(view);
}
}
@Override
public void onFailedToReceiveAd(ADGConsts.ADGErrorCode code) {
Log.d(TAG, "Failed to receive an ad.");
// ネットワーク不通/エラー多発/広告レスポンスなし 以外はリトライしてください
switch (code) {
case EXCEED_LIMIT: // エラー多発
case NEED_CONNECTION: // ネットワーク不通
case NO_AD: // 広告レスポンスなし
break;
default:
if (adg != null) {
adg.start();
}
break;
}
}
}
}
ネイティブ広告デザイン例
- 作成される広告イメージ
- アプリに応じてカスタマイズください(画像ロード処理の非同期化など)。
- レイアウトファイルはサンプルを参照してください。 https://github.com/AdGeneration/ADG-Android-SDK/tree/master/Samples
object ADGNativeAdHelper {
/**
* ネイティブ広告を作成します。
*
* @param nativeAd com.socdm.d.adgeneration.nativead.ADGNativeAd
* @param context Context
* @return ad
*/
fun createAdView(nativeAd: ADGNativeAd, context: Activity): FrameLayout {
// 広告枠の設定
val layout = FrameLayout(context)
layout.layoutParams = ViewGroup.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT)
layout.setBackgroundColor(Color.WHITE)
// 広告枠のレイアウト設定
val nativeAdContainer = LinearLayout(context)
nativeAdContainer.layoutParams = LinearLayout.LayoutParams(
Util.convertDpToPixel(context, 300),
Util.convertDpToPixel(context, 250))
nativeAdContainer.orientation = LinearLayout.VERTICAL
layout.addView(nativeAdContainer)
// Headerの設定
val headerWrapper = LinearLayout(context)
headerWrapper.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
Util.convertDpToPixel(context, 30))
headerWrapper.orientation = LinearLayout.HORIZONTAL
headerWrapper.gravity = Gravity.LEFT
nativeAdContainer.addView(headerWrapper)
// アイコンイメージの設定
if (nativeAd.iconImage != null) {
val adIconView = ImageView(context)
adIconView.layoutParams = ViewGroup.LayoutParams(
Util.convertDpToPixel(context, 30),
ViewGroup.LayoutParams.WRAP_CONTENT)
val adIconImageUrl = nativeAd.iconImage.url
// 画像をロードします(方法については任意で行ってください)
DownloadImageAsync(adIconView).execute(adIconImageUrl)
headerWrapper.addView(adIconView)
}
// タイトルと広告マークを縦に並べる
val titlesWrapper = LinearLayout(context)
titlesWrapper.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
titlesWrapper.orientation = LinearLayout.VERTICAL
titlesWrapper.setPadding(Util.convertDpToPixel(context, 6), 0, 0, 0)
headerWrapper.addView(titlesWrapper)
// タイトルの設定
if (nativeAd.title != null) {
val nativeAdTitle = LinearLayout(context)
nativeAdTitle.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
Util.convertDpToPixel(context, 16))
val titleView = TextView(context)
titleView.setTextColor(Color.BLACK)
titleView.textSize = 9f
titleView.layoutParams = ViewGroup.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
titleView.setPadding(0, 0, 0, Util.convertDpToPixel(context, 3))
titleView.typeface = Typeface.DEFAULT_BOLD
val title = nativeAd.title.text
Util.setOneLineAndEllipsisForTextView(titleView)
titleView.text = title
nativeAdTitle.addView(titleView)
titlesWrapper.addView(nativeAdTitle)
}
// 広告マークの設定
val prTextView = TextView(context)
prTextView.text = "広告"
prTextView.setTextColor(Color.rgb(218, 218, 218))
prTextView.textSize = 9f
prTextView.layoutParams = ViewGroup.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
titlesWrapper.addView(prTextView)
// 本文の設定
if (nativeAd.desc != null) {
val descView = TextView(context)
descView.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
Util.convertDpToPixel(context, 24))
descView.textSize = 8f
descView.setTextColor(Color.rgb(218, 218, 218))
descView.setPadding(0, Util.convertDpToPixel(context, 6), 0, Util.convertDpToPixel(context, 6))
nativeAdContainer.addView(descView)
Util.setOneLineAndEllipsisForTextView(descView)
val desc = nativeAd.desc.value
descView.text = desc
}
val mediaViewFrame = ADGMediaViewFrame(context)
mediaViewFrame.layoutParams = FrameLayout.LayoutParams(
Util.convertDpToPixel(context, 300),
Util.convertDpToPixel(context, 156))
nativeAdContainer.addView(mediaViewFrame)
val adgMediaView = ADGMediaView(context)
mediaViewFrame.mMediaView = adgMediaView
adgMediaView.setAdgNativeAd(nativeAd)
adgMediaView.load()
mediaViewFrame.addView(adgMediaView)
// インフォメーションアイコンの設定
val infoIcon = ADGInformationIconView(context, nativeAd)
mediaViewFrame.addView(infoIcon)
// Bottomの設定
val bottomWrapper = LinearLayout(context)
bottomWrapper.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
bottomWrapper.orientation = LinearLayout.HORIZONTAL
bottomWrapper.gravity = Gravity.LEFT
nativeAdContainer.addView(bottomWrapper)
val bottomLeftWrapper = LinearLayout(context)
bottomLeftWrapper.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT,
1.0f)
bottomLeftWrapper.gravity = Gravity.CENTER_VERTICAL or Gravity.LEFT
bottomWrapper.addView(bottomLeftWrapper)
val bottomRightWrapper = LinearLayout(context)
bottomRightWrapper.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT,
1.0f)
bottomRightWrapper.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT
bottomWrapper.addView(bottomRightWrapper)
// スポンサーの設定
val spTextView = TextView(context)
spTextView.layoutParams = LinearLayout.LayoutParams(
Util.convertDpToPixel(context, 150),
Util.convertDpToPixel(context, 14))
spTextView.textSize = 8f
spTextView.setTextColor(Color.rgb(218, 218, 218))
spTextView.text = if (nativeAd.sponsored != null) "sponsored by " + nativeAd.sponsored.value else "sponsored"
Util.setOneLineAndEllipsisForTextView(spTextView)
bottomLeftWrapper.addView(spTextView)
// ボタンの設定
val nativeAdButtonArea = LinearLayout(context)
nativeAdButtonArea.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT)
nativeAdButtonArea.gravity = Gravity.CENTER
val nativeAdButton = TextView(context)
nativeAdButton.layoutParams = LinearLayout.LayoutParams(
Util.convertDpToPixel(context, 130),
Util.convertDpToPixel(context, 25))
nativeAdButton.setTextColor(Color.rgb(11, 144, 255))
nativeAdButton.textSize = 12f
nativeAdButton.setBackgroundColor(Color.WHITE)
nativeAdButton.gravity = Gravity.CENTER
// ボタンにborderや角丸を設定
val borders = GradientDrawable()
borders.setColor(Color.WHITE)
borders.cornerRadius = 10f
borders.setStroke(3, Color.rgb(11, 144, 255))
//setBackgroundDrawableはDeprecatedですが、古いバージョンの端末サポートのため使用しています
nativeAdButton.setBackgroundDrawable(borders)
// CTA
if (nativeAd.ctatext != null) {
nativeAdButton.text = nativeAd.ctatext.value
} else {
nativeAdButton.text = "詳しくはこちら"
}
Util.setOneLineAndEllipsisForTextView(nativeAdButton)
nativeAdButtonArea.addView(nativeAdButton)
bottomRightWrapper.addView(nativeAdButtonArea)
nativeAd.setClickEvent(context, layout, null)
return layout
}
}
public class ADGNativeAdView extends RelativeLayout {
private Activity mActivity;
private RelativeLayout mContainer;
private ImageView mIconImageView;
private TextView mTitleLabel;
private TextView mDescLabel;
private FrameLayout mMediaViewContainer;
private TextView mSponsoredLabel;
private TextView mCTALabel;
public ADGNativeAdView(Context context) {
this(context, null);
}
public ADGNativeAdView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ADGNativeAdView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
@TargetApi(21)
public ADGNativeAdView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, 0);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
if (context instanceof Activity) {
mActivity = (Activity)context;
}
View layout = LayoutInflater.from(context).inflate(R.layout.adg_nativead_view, this);
mContainer = (RelativeLayout) layout.findViewById(R.id.adg_nativead_view_container);
mIconImageView = (ImageView) layout.findViewById(R.id.adg_nativead_view_icon);
mTitleLabel = (TextView) layout.findViewById(R.id.adg_nativead_view_title);
mTitleLabel.setText("");
mDescLabel = (TextView) layout.findViewById(R.id.adg_nativead_view_desc);
mDescLabel.setText("");
mMediaViewContainer = (FrameLayout) layout.findViewById(R.id.adg_nativead_view_mediaview_container);
mSponsoredLabel = (TextView) layout.findViewById(R.id.adg_nativead_view_sponsored);
mCTALabel = (TextView) layout.findViewById(R.id.adg_nativead_view_cta);
mCTALabel.setText("");
GradientDrawable borders = new GradientDrawable();
borders.setColor(Color.WHITE);
borders.setCornerRadius(10);
borders.setStroke(3, mCTALabel.getTextColors().getDefaultColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mCTALabel.setBackground(borders);
} else {
mCTALabel.setBackgroundDrawable(borders);
}
}
public void apply(ADGNativeAd nativeAd) {
// アイコン画像
if (nativeAd.getIconImage() != null) {
String url = nativeAd.getIconImage().getUrl();
new DownloadImageAsync(mIconImageView).execute(url);
}
// タイトル
if (nativeAd.getTitle() != null) {
mTitleLabel.setText(nativeAd.getTitle().getText());
}
// リード文
if (nativeAd.getDesc() != null) {
String desc = nativeAd.getDesc().getValue();
mDescLabel.setText(desc);
}
// メイン画像・動画
if (nativeAd.canLoadMedia()) {
ADGMediaView mediaView = new ADGMediaView(mActivity);
mediaView.setAdgNativeAd(nativeAd);
mMediaViewContainer.addView(mediaView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mediaView.load();
}
// インフォメーションアイコン
ADGInformationIconView infoIcon = new ADGInformationIconView(getContext(), nativeAd);
mMediaViewContainer.addView(infoIcon);
// 広告主
if (nativeAd.getSponsored() != null) {
mSponsoredLabel.setText(nativeAd.getSponsored().getValue());
} else {
mSponsoredLabel.setText("sponsored");
}
// CTA
if (nativeAd.getCtatext() != null) {
mCTALabel.setText(nativeAd.getCtatext().getValue());
} else {
mCTALabel.setText("詳しくはこちら");
}
// クリックイベント
nativeAd.setClickEvent(getContext(), mContainer, null);
}
/**
* 画像をロードします(方法については任意で行ってください)
*/
private class DownloadImageAsync extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public DownloadImageAsync(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... params) {
try {
String imageUrl = params[0];
return BitmapFactory.decodeStream(new URL(imageUrl).openStream());
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
this.imageView.setImageBitmap(bitmap);
}
}
}
# 注意事項
- v2.8.0から
ADG#delegateViewManagement
は非推奨となりました。 - 代わりに
ADG#setClickEvent(Context context, View view, ADGNativeAdOnClickListener listener)
およびADG#setAutomaticallyRemoveOnReload(View view)
を使用してください。 - stopを呼び出したタイミングにて、setAutomaticallyRemoveOnReloadにセットされたViewインスタンスは、親ViewからのremoveViewが呼ばれます。
- テストではタップの確認も行ってください。
- 画像のロードはアプリ側で実装する必要があります。
- ADGクラスは1つの広告枠に対して1つのインスタンスを生成してください。
- 広告枠の設定によっては各ネイティブ広告オブジェクトのパラメーターの値がnullになる場合があります。
たとえば、GunosyAdsではCTA取得できません。接続先アドネットワーク毎に違いがございますので、nullを考慮した実装をお願いいたします。画像の縦横サイズも含め、すべてがoptionalな値です。 - PR表記をつける等して広告であることを示してください。
- 画像は、アスペクト比を変えず、切れることのないようしてください。
(ImageViewのscaleTypeをFIT_CENTERに設定) - レスポンスにSponsoredがある場合はできる限り表示をしてください。(特定のアドネットワークではSponsoredの表示要望がございます)
# インフォメーションアイコン(オプトアウトリンク)について
※ 2016/12/8(v2.4.2)より必須項目となりました
v2.4.2より、ターゲティングを行っている広告の場合にはデフォルトでインフォメーションアイコン(オプトアウトリンク)が表示されるようになります(ターゲティングを行っていない広告の場合は表示されません)。
インフォメーションアイコンはタップすることで、DSP事業者が指定したオプトアウトWebサイトページへ遷移します。
オプトアウトリンクはユーザーが広告のターゲティングをオプトアウト(拒否)することにより、ユーザーに関する情報の関連付けを防ぐことを可能とし、設置することで配信できるDSP事業者が増加します。
表示される場所は、setClickEvent(Context context, View view, ADGNativeAdOnClickListener listener)
で指定したViewの右上に設置されます。
デフォルトの表示位置から変更する場合は、ADG.setInformationIconViewDefault(boolean b)
を設定し、ADGInformationIconView
を生成してください。
ターゲティングを行っていない広告の場合は、ADGInformationIconView
を生成してもアイコンは表示されません。
インフォメーションアイコンの表示確認は、テストID48635
を使用してください。
# 動画広告の実装について(Android: v2.9.0~(adg-2.9.0.aar))
ADGMediaViewを利用することで、動画広告を配置できます。
// メイン画像または動画が利用できるかどうかをチェックします。
if (nativeAd.canLoadMedia()) {
// ADGMediaViewを生成します。
ADGMediaView mediaView = new ADGMediaView(mActivity);
// 必ずADGNativeAdの参照を追加してください。
mediaView.setAdgNativeAd(nativeAd);
// Viewを配置します。
mMediaViewContainer.addView(mediaView,
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// メイン画像または動画のロードを開始します。
mediaView.load();
// 不要になったタイミングでdestroyを呼び、破棄処理をおこなってください。
// mediaView.destroy();
}
# ADGMediaView注意事項
- 動画を再生するにはコンストラクタの引数にActivityを渡してください。
- 動画と静止画が利用できる場合は、動画が優先されます。
- 動画や静止画は配信案件によるため、必ずしも配信されるわけではありません。
android:hardwareAccelerated
がfalseの場合はtrue
にしてください。- とくに、複数の動画を配置する場合や、アプリ側でMediaPlayerを扱い、動画や音声を再生している場合、不要になったものから
destroy
を呼びだし、適宜破棄を行う必要があります。
破棄されないままMediaPlayerの生成を繰り返すとクラッシュを引き起こす場合があります。
# テスト用ID
審査完了前に広告の掲載イメージをご確認頂く際は、以下のIDに置き換えご確認ください。
このIDをセットしたままアプリをリリースしないようご注意ください。
テストID | 配信広告 |
---|---|
48635 | テスト広告 |
# 6. proguardの設定をする
Android SDK Getting Started / バナー広告からご確認ください。