네이티브 광고는 앱의 디자인에 맞게 광고 UI를 자유롭게 커스터마이징할 수 있는 광고 포맷입니다. 광고 소재(이미지, 동영상), 제목, 설명, CTA 버튼 등의 요소를 개별적으로 배치하여 자연스러운 사용자 경험을 제공할 수 있습니다.
주요 기능
- 완전한 UI 커스터마이징: 앱의 디자인 시스템에 맞게 광고 레이아웃을 자유롭게 구성
- 다양한 미디어 지원: 이미지 및 동영상 광고 소재 지원
- 유연한 클릭 영역 설정: 전체 클릭 또는 개별 요소별 클릭 처리 가능
- 프로필 정보 표시: 광고주 프로필 로고, 이름, 링크 지원
클래스 및 인터페이스
AdropNativeAd
네이티브 광고를 로드하고 관리하는 메인 클래스입니다.
주요 프로퍼티
| 프로퍼티 | 타입 | 설명 |
|---|
headline | String | 광고 제목 |
body | String | 광고 본문 |
icon | String | 아이콘 이미지 URL |
cover | String | 커버 이미지 URL |
advertiser | String | Deprecated. profile.displayName을 대신 사용하세요. |
advertiserURL | String | Deprecated. profile.link를 대신 사용하세요. |
accountTag | JSONObject | Deprecated. 더 이상 지원되지 않습니다. |
creativeTag | JSONObject | Deprecated. 더 이상 지원되지 않습니다. |
callToAction | String | 행동 유도 문구 (예: “지금 확인하기”) |
profile | AdropNativeAdProfile | 광고주 프로필 정보 |
isLoaded | Boolean | 광고 로드 완료 여부 |
isDestroyed | Boolean | 광고 제거 여부 |
AdropNativeAdView
네이티브 광고를 표시할 컨테이너 뷰입니다.
전체 영역 클릭 활성화 여부. true로 설정하면 광고 뷰 전체 영역에서 클릭 이벤트가 발생합니다.
주요 메서드
| 메서드 | 설명 |
|---|
setIconView(view, listener) | 아이콘 이미지 뷰 설정 (ImageView 타입) |
setHeadLineView(view, listener) | 제목 텍스트 뷰 설정 (TextView 타입) |
setBodyView(view) | 본문 텍스트 뷰 설정 (TextView 타입) |
setMediaView(view) | 미디어 컨테이너 뷰 설정 (AdropMediaView 타입) |
setAdvertiserView(view, listener) | 광고주명 텍스트 뷰 설정 (TextView 타입) |
setCallToActionView(view) | CTA 버튼 또는 텍스트 뷰 설정 |
setProfileLogoView(view, listener) | 프로필 로고 이미지 뷰 설정 (ImageView 타입) |
setProfileNameView(view, listener) | 프로필 이름 텍스트 뷰 설정 (TextView 타입) |
setNativeAd(ad) | 광고 데이터를 뷰에 바인딩 |
destroy() | 리소스 해제 |
광고 이미지 또는 동영상을 표시할 미디어 컨테이너 뷰입니다.
<io.adrop.ads.nativeAd.AdropMediaView
android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="200dp" />
영상 크리에이티브의 경우 반드시 AdropNativeAdView.setMediaView(...)를 통해 AdropMediaView를 바인딩해야 합니다. ad.asset URL을 읽어 자체 플레이어(ExoPlayer, MediaPlayer 또는 서드파티 라이브러리)로 직접 재생하면 SDK의 영상 트래킹 파이프라인을 거치지 않게 되어 VTR이 측정되지 않고 onAdVideoStart / onAdVideoEnd 콜백도 호출되지 않습니다. 영상 트래킹과 VTR 섹션을 참고하세요.
AdropNativeAdListener
광고 이벤트를 처리하는 인터페이스입니다.
interface AdropNativeAdListener {
fun onAdReceived(ad: AdropNativeAd)
fun onAdClicked(ad: AdropNativeAd)
fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode)
fun onAdImpression(ad: AdropNativeAd) // 선택적 구현
fun onAdVideoStart(ad: AdropNativeAd) // 선택적 구현
fun onAdVideoEnd(ad: AdropNativeAd) // 선택적 구현
}
AdropNativeAdProfile
광고주 프로필 정보를 담는 데이터 클래스입니다.
| 프로퍼티 | 타입 | 설명 |
|---|
displayLogo | String | 프로필 로고 이미지 URL |
displayName | String | 프로필 표시 이름 |
link | String | 프로필 링크 URL |
구현 가이드
1. XML 레이아웃 설정
네이티브 광고를 표시할 레이아웃을 정의합니다. AdropNativeAdView를 루트로 하고, 내부에 광고 요소들을 배치합니다.
res/layout/native_ad_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<io.adrop.ads.nativeAd.AdropNativeAdView
android:id="@+id/native_ad_view"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<!-- 프로필 영역 -->
<ImageView
android:id="@+id/ad_profile_logo"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/ad_profile_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/ad_profile_logo"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/ad_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:text="광고"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/ad_profile_logo"
app:layout_constraintTop_toBottomOf="@id/ad_profile_name" />
<!-- 미디어 영역 -->
<io.adrop.ads.nativeAd.AdropMediaView
android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@id/ad_profile_logo" />
<!-- 광고 내용 -->
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/ad_media" />
<TextView
android:id="@+id/ad_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/ad_headline" />
<!-- 하단 영역 -->
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ad_body" />
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ad_body" />
</androidx.constraintlayout.widget.ConstraintLayout>
</io.adrop.ads.nativeAd.AdropNativeAdView>
2. 광고 로드 및 표시 (Kotlin)
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import io.adrop.ads.model.AdropErrorCode
import io.adrop.ads.nativeAd.AdropMediaView
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdListener
import io.adrop.ads.nativeAd.AdropNativeAdView
class NativeAdActivity : AppCompatActivity() {
private lateinit var nativeAd: AdropNativeAd
private lateinit var nativeAdView: AdropNativeAdView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native_ad)
nativeAdView = findViewById(R.id.native_ad_view)
loadNativeAd()
}
private fun loadNativeAd() {
// 1. 네이티브 광고 인스턴스 생성
nativeAd = AdropNativeAd(
context = this,
unitId = "YOUR_UNIT_ID" // 실제 유닛 ID로 교체
)
// 2. 리스너 설정
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
Log.d("Adrop", "광고 수신 성공")
populateNativeAdView(ad)
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "광고 수신 실패: $errorCode")
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "광고 클릭됨")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "광고 노출됨")
}
override fun onAdVideoStart(ad: AdropNativeAd) {
Log.d("Adrop", "광고 동영상 재생 시작")
}
override fun onAdVideoEnd(ad: AdropNativeAd) {
Log.d("Adrop", "광고 동영상 재생 종료")
}
}
// 3. 광고 로드
nativeAd.load()
}
private fun populateNativeAdView(ad: AdropNativeAd) {
// 프로필 설정
val profileLogoView = findViewById<ImageView>(R.id.ad_profile_logo)
Glide.with(this).load(ad.profile.displayLogo).into(profileLogoView)
nativeAdView.setProfileLogoView(profileLogoView)
val profileNameView = findViewById<TextView>(R.id.ad_profile_name)
profileNameView.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileNameView)
// 제목 설정
val headlineView = findViewById<TextView>(R.id.ad_headline)
headlineView.text = ad.headline
nativeAdView.setHeadLineView(headlineView)
// 미디어 설정
val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
nativeAdView.setMediaView(mediaView)
// 본문 설정
val bodyView = findViewById<TextView>(R.id.ad_body)
bodyView.text = ad.body
nativeAdView.setBodyView(bodyView)
// 광고주 설정
val advertiserView = findViewById<TextView>(R.id.ad_advertiser)
advertiserView.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiserView)
// CTA 버튼 설정
val ctaView = findViewById<Button>(R.id.ad_call_to_action)
ctaView.text = ad.callToAction
nativeAdView.setCallToActionView(ctaView)
// 광고 데이터 바인딩
nativeAdView.setNativeAd(ad)
}
override fun onDestroy() {
// 리소스 해제
nativeAdView.destroy()
nativeAd.destroy()
super.onDestroy()
}
}
3. 광고 로드 및 표시 (Java)
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import io.adrop.ads.model.AdropErrorCode;
import io.adrop.ads.nativeAd.AdropMediaView;
import io.adrop.ads.nativeAd.AdropNativeAd;
import io.adrop.ads.nativeAd.AdropNativeAdListener;
import io.adrop.ads.nativeAd.AdropNativeAdView;
public class NativeAdActivity extends AppCompatActivity {
private AdropNativeAd nativeAd;
private AdropNativeAdView nativeAdView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad);
nativeAdView = findViewById(R.id.native_ad_view);
loadNativeAd();
}
private void loadNativeAd() {
// 1. 네이티브 광고 인스턴스 생성
nativeAd = new AdropNativeAd(
this,
"YOUR_UNIT_ID", // 실제 유닛 ID로 교체
null // contextId (선택사항)
);
// 2. 리스너 설정
nativeAd.setListener(new AdropNativeAdListener() {
@Override
public void onAdReceived(AdropNativeAd ad) {
Log.d("Adrop", "광고 수신 성공");
populateNativeAdView(ad);
}
@Override
public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
Log.e("Adrop", "광고 수신 실패: " + errorCode);
}
@Override
public void onAdClicked(AdropNativeAd ad) {
Log.d("Adrop", "광고 클릭됨");
}
@Override
public void onAdImpression(AdropNativeAd ad) {
Log.d("Adrop", "광고 노출됨");
}
@Override
public void onAdVideoStart(AdropNativeAd ad) {
Log.d("Adrop", "광고 동영상 재생 시작");
}
@Override
public void onAdVideoEnd(AdropNativeAd ad) {
Log.d("Adrop", "광고 동영상 재생 종료");
}
});
// 3. 광고 로드
nativeAd.load();
}
private void populateNativeAdView(AdropNativeAd ad) {
// 프로필 로고 설정
ImageView profileLogoView = findViewById(R.id.ad_profile_logo);
Glide.with(this).load(ad.getProfile().getDisplayLogo()).into(profileLogoView);
nativeAdView.setProfileLogoView(profileLogoView, null);
// 프로필 이름 설정
TextView profileNameView = findViewById(R.id.ad_profile_name);
profileNameView.setText(ad.getProfile().getDisplayName());
nativeAdView.setProfileNameView(profileNameView, null);
// 제목 설정
TextView headlineView = findViewById(R.id.ad_headline);
headlineView.setText(ad.getHeadline());
nativeAdView.setHeadLineView(headlineView, null);
// 미디어 설정
AdropMediaView mediaView = findViewById(R.id.ad_media);
nativeAdView.setMediaView(mediaView);
// 본문 설정
TextView bodyView = findViewById(R.id.ad_body);
bodyView.setText(ad.getBody());
nativeAdView.setBodyView(bodyView);
// 광고주 설정
TextView advertiserView = findViewById(R.id.ad_advertiser);
advertiserView.setText(ad.getProfile().getDisplayName());
nativeAdView.setAdvertiserView(advertiserView, null);
// CTA 버튼 설정
Button ctaView = findViewById(R.id.ad_call_to_action);
ctaView.setText(ad.getCallToAction());
nativeAdView.setCallToActionView(ctaView);
// 광고 데이터 바인딩
nativeAdView.setNativeAd(ad);
}
@Override
protected void onDestroy() {
// 리소스 해제
if (nativeAdView != null) {
nativeAdView.destroy();
}
if (nativeAd != null) {
nativeAd.destroy();
}
super.onDestroy();
}
}
고급 기능
전체 클릭 영역 설정
광고 뷰 전체 영역에서 클릭 이벤트가 발생하도록 설정할 수 있습니다.
nativeAdView.isEntireClick = true
isEntireClick을 true로 설정하면 광고 뷰 전체가 클릭 가능한 영역이 됩니다. 개별 요소의 클릭 리스너는 동작하지 않습니다.
개별 요소 클릭 리스너
특정 광고 요소에 커스텀 클릭 리스너를 설정할 수 있습니다.
// 광고주 클릭 시 커스텀 동작
nativeAdView.setAdvertiserView(advertiserView) {
Log.d("Adrop", "광고주 클릭")
// 커스텀 동작 구현
}
// 프로필 로고 클릭 시 커스텀 동작
nativeAdView.setProfileLogoView(profileLogoView) {
Log.d("Adrop", "프로필 로고 클릭")
// 커스텀 동작 구현
}
문맥 타겟팅
특정 컨텍스트에 맞는 광고를 요청할 수 있습니다.
val nativeAd = AdropNativeAd(
context = this,
unitId = "YOUR_UNIT_ID",
contextId = "SPORTS_NEWS" // 컨텍스트 ID
)
RecyclerView에서 사용하기
RecyclerView의 아이템으로 네이티브 광고를 표시할 수 있습니다.
class ContentAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
const val VIEW_TYPE_CONTENT = 0
const val VIEW_TYPE_AD = 1
const val AD_INTERVAL = 5 // 5개 아이템마다 광고 표시
}
private val items = mutableListOf<Any>()
private val nativeAds = mutableMapOf<Int, AdropNativeAd>()
override fun getItemViewType(position: Int): Int {
return if (position % (AD_INTERVAL + 1) == AD_INTERVAL) {
VIEW_TYPE_AD
} else {
VIEW_TYPE_CONTENT
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_AD -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_native_ad, parent, false)
NativeAdViewHolder(view)
}
else -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_content, parent, false)
ContentViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is NativeAdViewHolder -> loadNativeAd(holder, position)
is ContentViewHolder -> holder.bind(items[position])
}
}
private fun loadNativeAd(holder: NativeAdViewHolder, position: Int) {
// 이미 로드된 광고가 있으면 재사용
nativeAds[position]?.let { ad ->
if (ad.isLoaded) {
holder.bind(ad)
return
}
}
// 새 광고 로드
val nativeAd = AdropNativeAd(holder.itemView.context, "YOUR_UNIT_ID")
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
nativeAds[position] = ad
holder.bind(ad)
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "광고 로드 실패: $errorCode")
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "광고 클릭")
}
}
nativeAd.load()
}
class NativeAdViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nativeAdView: AdropNativeAdView = itemView.findViewById(R.id.native_ad_view)
fun bind(ad: AdropNativeAd) {
val profileLogo = itemView.findViewById<ImageView>(R.id.ad_profile_logo)
Glide.with(itemView.context).load(ad.profile.displayLogo).into(profileLogo)
nativeAdView.setProfileLogoView(profileLogo)
val profileName = itemView.findViewById<TextView>(R.id.ad_profile_name)
profileName.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileName)
val headline = itemView.findViewById<TextView>(R.id.ad_headline)
headline.text = ad.headline
nativeAdView.setHeadLineView(headline)
val media = itemView.findViewById<AdropMediaView>(R.id.ad_media)
nativeAdView.setMediaView(media)
val body = itemView.findViewById<TextView>(R.id.ad_body)
body.text = ad.body
nativeAdView.setBodyView(body)
val advertiser = itemView.findViewById<TextView>(R.id.ad_advertiser)
advertiser.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiser)
val cta = itemView.findViewById<Button>(R.id.ad_call_to_action)
cta.text = ad.callToAction
nativeAdView.setCallToActionView(cta)
nativeAdView.setNativeAd(ad)
}
}
fun destroy() {
nativeAds.values.forEach { it.destroy() }
nativeAds.clear()
}
}
테스트 유닛 ID
개발 및 테스트 시 다음 테스트 유닛 ID를 사용하세요.
| 포맷 | 테스트 유닛 ID |
|---|
| 네이티브 (이미지) | PUBLIC_TEST_UNIT_ID_NATIVE |
| 네이티브 비디오 (16:9) | PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9 |
| 네이티브 비디오 (9:16) | PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16 |
프로덕션 배포 전에 반드시 실제 유닛 ID로 교체하세요.
생명주기 관리
Activity/Fragment에서 리소스 해제
네이티브 광고는 메모리 누수를 방지하기 위해 사용이 끝나면 반드시 해제해야 합니다.
override fun onDestroy() {
nativeAdView.destroy()
nativeAd.destroy()
super.onDestroy()
}
RecyclerView에서 리소스 해제
RecyclerView Adapter에서 광고를 사용하는 경우, 액티비티나 프래그먼트가 종료될 때 모든 광고를 해제해야 합니다.
override fun onDestroy() {
adapter.destroy()
super.onDestroy()
}
영상 트래킹과 VTR
Adrop은 네이티브 광고의 영상 지표 — VTR(영상 완수율), onAdVideoStart, onAdVideoEnd — 를 SDK 미디어 컨테이너(AdropNativeAdView.setMediaView(...)로 바인딩된 AdropMediaView) 안에서 영상이 렌더링될 때에만 수집합니다.
AdropNativeAd.asset 프로퍼티는 원본 이미지 또는 영상 파일의 URL을 반환합니다. 이 값은 썸네일 표시나 매체사 자체 분석 등 부가 메타데이터 용도로 노출된 것이며, 재생을 직접 처리하기 위한 것이 아닙니다.
영상 네이티브 광고의 asset URL을 자체 영상 플레이어(ExoPlayer, MediaPlayer 또는 서드파티 플레이어)에 직접 전달하지 마세요. SDK 미디어 컨테이너를 거치지 않으면 다음 현상이 발생합니다.
- 해당 지면에서 VTR(영상 완수율)이 수집되지 않습니다.
onAdVideoStart / onAdVideoEnd 콜백이 호출되지 않습니다.
- 해당 유닛의 영상 성과 집계가 누락되거나 0으로 보고됩니다.
클릭(onAdClicked)과 노출(onAdImpression) 트래킹은 컨테이너 뷰에 연결되어 있으므로 계속 동작하지만, 영상 관련 신호는 손실됩니다.
권장 패턴
영상 네이티브 광고에는 항상 SDK 미디어 컨테이너를 바인딩하세요.
val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
nativeAdView.setMediaView(mediaView) // 영상 트래킹을 위해 필수
nativeAdView.setNativeAd(ad)
특정 지면이 반드시 자체 플레이어를 사용해야 하고 VTR이 측정되지 않는 것을 수용한다면, 광고를 AdropNativeAdView에 바인딩한 채로 유지해 클릭과 노출 어트리뷰션은 계속 확보할 수 있습니다. 다만 해당 지면을 내부 리포트에서 비-VTR 인벤토리로 분류하고 SDK가 측정한 영상 성과와 섞지 마세요.
모범 사례
1. 필수 요소만 표시
모든 광고 요소를 표시할 필요는 없습니다. 앱의 디자인에 맞게 필요한 요소만 선택적으로 사용하세요.
// 최소 구성 예시 (미디어 + CTA만 사용)
nativeAdView.setMediaView(mediaView)
nativeAdView.setCallToActionView(ctaView)
nativeAdView.setNativeAd(ad)
2. 이미지 로딩 라이브러리 사용
아이콘, 커버, 프로필 이미지를 표시할 때는 Glide, Coil 등의 이미지 로딩 라이브러리를 사용하세요.
// Glide 사용 예시
Glide.with(context)
.load(ad.icon)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(iconView)
3. 광고 뷰 재사용
RecyclerView에서 광고를 표시할 때는 광고 인스턴스를 캐싱하여 재사용하세요.
4. 적절한 광고 위치
사용자 경험을 해치지 않는 선에서 자연스럽게 광고를 배치하세요.
- 콘텐츠 피드: 5~10개 아이템마다 1개의 광고
- 상세 화면: 콘텐츠 하단에 배치
- 스크롤 뷰: 자연스러운 콘텐츠 흐름에 맞춰 배치
5. 에러 처리
광고 로드 실패에 대비하여 적절한 폴백 처리를 구현하세요.
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
// 광고 없음 - 광고 영역 숨기기
nativeAdView.visibility = View.GONE
}
AdropErrorCode.ERROR_CODE_NETWORK -> {
// 네트워크 오류 - 재시도 로직
Handler(Looper.getMainLooper()).postDelayed({
nativeAd.load()
}, 3000)
}
else -> {
Log.e("Adrop", "광고 로드 실패: $errorCode")
}
}
}
완전한 예제
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import io.adrop.ads.model.AdropErrorCode
import io.adrop.ads.nativeAd.AdropMediaView
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdListener
import io.adrop.ads.nativeAd.AdropNativeAdView
class NativeAdExampleActivity : AppCompatActivity() {
private lateinit var nativeAd: AdropNativeAd
private lateinit var nativeAdView: AdropNativeAdView
private lateinit var loadingView: View
private lateinit var errorView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native_ad_example)
nativeAdView = findViewById(R.id.native_ad_view)
loadingView = findViewById(R.id.loading_view)
errorView = findViewById(R.id.error_view)
findViewById<Button>(R.id.retry_button).setOnClickListener {
loadNativeAd()
}
loadNativeAd()
}
private fun loadNativeAd() {
showLoading()
nativeAd = AdropNativeAd(
context = this,
unitId = "PUBLIC_TEST_UNIT_ID_NATIVE"
)
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
Log.d("Adrop", "광고 수신: unitId=${ad.unitId}, txId=${ad.txId}")
populateNativeAdView(ad)
showAd()
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "광고 수신 실패: $errorCode")
showError()
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "광고 클릭: txId=${ad.txId}")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "광고 노출: txId=${ad.txId}")
}
}
nativeAd.load()
}
private fun populateNativeAdView(ad: AdropNativeAd) {
// 전체 클릭 영역 활성화 (선택사항)
// nativeAdView.isEntireClick = true
// 프로필 로고
val profileLogoView = findViewById<ImageView>(R.id.ad_profile_logo)
Glide.with(this)
.load(ad.profile.displayLogo)
.circleCrop()
.into(profileLogoView)
nativeAdView.setProfileLogoView(profileLogoView)
// 프로필 이름
val profileNameView = findViewById<TextView>(R.id.ad_profile_name)
profileNameView.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileNameView)
// 제목
val headlineView = findViewById<TextView>(R.id.ad_headline)
headlineView.text = ad.headline
nativeAdView.setHeadLineView(headlineView)
// 미디어 (이미지 또는 동영상)
val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
nativeAdView.setMediaView(mediaView)
// 본문
val bodyView = findViewById<TextView>(R.id.ad_body)
bodyView.text = ad.body
nativeAdView.setBodyView(bodyView)
// 광고주
val advertiserView = findViewById<TextView>(R.id.ad_advertiser)
advertiserView.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiserView) {
Log.d("Adrop", "광고주 클릭: ${ad.profile.displayName}")
}
// CTA 버튼
val ctaView = findViewById<Button>(R.id.ad_call_to_action)
ctaView.text = ad.callToAction
nativeAdView.setCallToActionView(ctaView)
// 광고 데이터 바인딩
nativeAdView.setNativeAd(ad)
}
private fun showLoading() {
loadingView.visibility = View.VISIBLE
nativeAdView.visibility = View.GONE
errorView.visibility = View.GONE
}
private fun showAd() {
loadingView.visibility = View.GONE
nativeAdView.visibility = View.VISIBLE
errorView.visibility = View.GONE
}
private fun showError() {
loadingView.visibility = View.GONE
nativeAdView.visibility = View.GONE
errorView.visibility = View.VISIBLE
}
override fun onDestroy() {
nativeAdView.destroy()
nativeAd.destroy()
super.onDestroy()
}
}
일괄 로드 (loads)
AdropNativeAd.loads()를 사용하면 한 번의 호출로 여러 네이티브 광고를 요청할 수 있습니다. 피드·캐러셀·페이지네이션 리스트에 삽입할 광고 풀을 프리페칭하는 데 유용합니다.
시그니처
AdropNativeAd.loads(
context: Context,
unitId: String,
contextId: String? = null,
listener: AdropNativeAdListener,
)
사용 방법
AdropNativeAd.loads(
context = this,
unitId = "YOUR_UNIT_ID",
contextId = null,
listener = object : AdropNativeAdListener {
// 배치 성공 — 각 광고는 렌더링이 완료된 상태로 전달됩니다.
override fun onAdsReceived(ads: List<AdropNativeAd>) {
ads.forEach { ad ->
// AdropNativeAdView에 바인딩하거나 라이프사이클용으로 보관
bindToView(ad)
}
}
// 배치 실패
override fun onAdsFailedToReceive(errorCode: AdropErrorCode) {
Log.e("Adrop", "배치 로드 실패: $errorCode")
}
// 단건 콜백은 loads 경로에서 호출되지 않습니다.
override fun onAdReceived(ad: AdropNativeAd) { /* batch path */ }
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {}
// 클릭/노출/비디오 콜백은 각 인스턴스에 대해 정상 호출됩니다.
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "네이티브 클릭: ${ad.creativeId}")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "네이티브 노출: ${ad.creativeId}")
}
},
)
리스너 콜백
| 콜백 | 호출 시점 |
|---|
onAdsReceived(ads) | 배치 전달 완료. 모든 광고가 완전히 렌더링된 상태로 전달됩니다. |
onAdsFailedToReceive(errorCode) | 요청 거부, 네트워크 실패 또는 표시 가능한 광고가 없는 경우(ERROR_CODE_AD_NO_FILL). |
onAdClicked / onAdImpression / onAdVideoStart / onAdVideoEnd | 단건 load()와 동일하게 각 광고별로 호출됩니다. |
단건 onAdReceived / onAdFailedToReceive 콜백은 loads() 경로에서 호출되지 않습니다. 배치 결과는 반드시 onAdsReceived / onAdsFailedToReceive로만 수신하세요.
제약 사항
- 호출당 최대 5개. 서버가 그 이상을 반환하더라도 앞의 5개만 전달됩니다.
- 백필은 적용되지 않습니다. 직광고 채움이 없으면 백필 설정과 무관하게
onAdsFailedToReceive가 ERROR_CODE_AD_NO_FILL로 호출됩니다.
onAdsReceived 안에서 destroy()를 호출하지 마세요. 각 광고를 AdropNativeAdView.setNativeAd(...)로 바인딩하거나 라이프사이클용으로 보관한 후, 부모 Activity.onDestroy()(또는 동등한 시점)에서 destroy() 하세요.
AdropBanner.loads()와 달리, 네이티브 배치는 모든 광고의 렌더링이 끝날 때까지 기다린 후 onAdsReceived를 호출합니다. 전달 시점에 각 인스턴스는 완전히 렌더링된 상태입니다.
백필 광고
백필 광고가 활성화된 경우, 직광고가 없을 때 자동으로 백필 광고가 로드됩니다. isBackfilled 프로퍼티로 백필 광고 여부를 확인할 수 있습니다.
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
if (ad.isBackfilled) {
println("백필 광고가 로드되었습니다")
} else {
println("직광고가 로드되었습니다")
}
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("직광고 없음, 백필 광고 요청 중...")
}
AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
println("백필 광고도 없음")
}
else -> {
println("광고 로드 실패: $errorCode")
}
}
}
}
AdChoices 위치 조정
백필 네이티브 광고에서 AdChoices 마크가 표시될 코너를 지정할 수 있습니다. load() 호출 전에 AdropNativeAd 인스턴스의 preferredAdChoicesPosition을 설정하세요.
import io.adrop.ads.nativeAd.AdropAdChoicesPosition
import io.adrop.ads.nativeAd.AdropNativeAd
val nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
nativeAd.preferredAdChoicesPosition = AdropAdChoicesPosition.BOTTOM_RIGHT
nativeAd.load()
| 값 | 설명 |
|---|
AdropAdChoicesPosition.TOP_LEFT | 좌측 상단 |
AdropAdChoicesPosition.TOP_RIGHT | 우측 상단 (기본값) |
AdropAdChoicesPosition.BOTTOM_LEFT | 좌측 하단 |
AdropAdChoicesPosition.BOTTOM_RIGHT | 우측 하단 |
이 설정은 백필 네트워크에 전달되는 선호 위치 힌트로, 백필 네이티브 광고에만 적용됩니다 — 직광고에는 영향이 없습니다. 반드시 load() 호출 전에 설정해야 하며, 백필 네트워크가 정책상 다른 위치로 표시할 수 있습니다.
백필 광고를 사용하려면 io.adrop:adrop-ads-backfill 의존성을 추가해야 합니다. 시작하기를 참고하세요.
다음 단계