> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 배너 광고

> Android 앱에서 배너 광고를 구현하는 방법을 안내합니다.

## 개요

배너 광고는 화면의 일부 영역에 표시되는 직사각형 광고입니다. XML 레이아웃과 코드 모두에서 사용할 수 있습니다.

### 주요 특징

* 화면 상단, 하단 또는 중간에 고정 배치 가능
* 이미지 및 동영상 광고 지원
* XML 레이아웃 및 프로그래밍 방식 모두 지원
* 리스너를 통한 광고 이벤트 처리

<Note>
  개발 환경에서는 테스트 유닛 ID를 사용하세요: `PUBLIC_TEST_UNIT_ID_320_100`
</Note>

***

## XML 레이아웃 구현

XML 레이아웃에서 `AdropBanner`를 정의하고 Activity나 Fragment에서 로드하는 방식입니다.

### 1. XML 레이아웃 정의

`FrameLayout` 또는 다른 컨테이너에 `AdropBanner`를 배치합니다.

```xml activity_main.xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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="match_parent">

    <!-- 메인 콘텐츠 -->
    <TextView
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="메인 콘텐츠"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <!-- 배너 광고 컨테이너 -->
    <FrameLayout
        android:id="@+id/banner_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <io.adrop.ads.banner.AdropBanner
            android:id="@+id/adrop_banner"
            android:layout_width="320dp"
            android:layout_height="100dp"
            android:layout_gravity="center"
            app:adrop_unit_id="YOUR_UNIT_ID" />

    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
```

### 2. Activity에서 로드

<CodeGroup>
  ```kotlin Kotlin theme={null}
  import android.os.Bundle
  import androidx.appcompat.app.AppCompatActivity
  import io.adrop.ads.banner.AdropBanner
  import io.adrop.ads.banner.AdropBannerListener
  import io.adrop.ads.model.AdropErrorCode

  class MainActivity : AppCompatActivity() {
      private var banner: AdropBanner? = null

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)

          // 1. XML에서 정의한 배너 뷰 찾기
          banner = findViewById(R.id.adrop_banner)

          // 2. 리스너 설정
          banner?.listener = object : AdropBannerListener {
              override fun onAdReceived(banner: AdropBanner) {
                  println("배너 광고 수신 성공")
              }

              override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
                  println("배너 광고 수신 실패: $errorCode")
              }

              override fun onAdImpression(banner: AdropBanner) {
                  println("배너 광고 노출")
              }

              override fun onAdClicked(banner: AdropBanner) {
                  println("배너 광고 클릭")
              }
          }

          // 3. 광고 로드
          banner?.load()
      }

      override fun onDestroy() {
          super.onDestroy()
          // 4. 메모리 해제
          banner?.destroy()
          banner = null
      }
  }
  ```

  ```java Java theme={null}
  import android.os.Bundle;
  import androidx.appcompat.app.AppCompatActivity;
  import io.adrop.ads.banner.AdropBanner;
  import io.adrop.ads.banner.AdropBannerListener;
  import io.adrop.ads.model.AdropErrorCode;
  import org.jetbrains.annotations.NotNull;

  public class MainActivity extends AppCompatActivity {
      private AdropBanner banner;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          // 1. XML에서 정의한 배너 뷰 찾기
          banner = findViewById(R.id.adrop_banner);

          // 2. 리스너 설정
          banner.setListener(new AdropBannerListener() {
              @Override
              public void onAdReceived(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 수신 성공");
              }

              @Override
              public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
                  System.out.println("배너 광고 수신 실패: " + errorCode);
              }

              @Override
              public void onAdImpression(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 노출");
              }

              @Override
              public void onAdClicked(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 클릭");
              }
          });

          // 3. 광고 로드
          banner.load();
      }

      @Override
      protected void onDestroy() {
          super.onDestroy();
          // 4. 메모리 해제
          if (banner != null) {
              banner.destroy();
              banner = null;
          }
      }
  }
  ```
</CodeGroup>

### XML 속성

| 속성                      | 설명                    |  필수 |
| ----------------------- | --------------------- | :-: |
| `app:adrop_unit_id`     | 광고 유닛 ID              |  O  |
| `app:adrop_context_id`  | 문맥 타겟팅을 위한 Context ID |     |
| `android:layout_width`  | 배너 너비 (dp)            |  O  |
| `android:layout_height` | 배너 높이 (dp)            |  O  |

***

## 프로그래밍 방식 구현

코드에서 직접 `AdropBanner` 인스턴스를 생성하여 뷰에 추가하는 방식입니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  import android.os.Bundle
  import android.widget.FrameLayout
  import androidx.appcompat.app.AppCompatActivity
  import io.adrop.ads.banner.AdropBanner
  import io.adrop.ads.banner.AdropBannerListener
  import io.adrop.ads.model.AdropErrorCode

  class MainActivity : AppCompatActivity() {
      private var banner: AdropBanner? = null

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)

          // 1. 배너 인스턴스 생성
          banner = AdropBanner(this, "YOUR_UNIT_ID").apply {
              // 2. 리스너 설정
              listener = object : AdropBannerListener {
                  override fun onAdReceived(banner: AdropBanner) {
                      println("배너 광고 수신 성공")
                  }

                  override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
                      println("배너 광고 수신 실패: $errorCode")
                  }

                  override fun onAdImpression(banner: AdropBanner) {
                      println("배너 광고 노출")
                  }

                  override fun onAdClicked(banner: AdropBanner) {
                      println("배너 광고 클릭")
                  }
              }
          }

          // 3. 뷰 계층에 추가
          val container = findViewById<FrameLayout>(R.id.banner_container)
          val params = FrameLayout.LayoutParams(
              FrameLayout.LayoutParams.MATCH_PARENT,
              FrameLayout.LayoutParams.WRAP_CONTENT
          )
          container.addView(banner, params)

          // 4. 광고 로드
          banner?.load()
      }

      override fun onDestroy() {
          super.onDestroy()
          // 5. 메모리 해제
          banner?.destroy()
          banner = null
      }
  }
  ```

  ```java Java theme={null}
  import android.os.Bundle;
  import android.widget.FrameLayout;
  import androidx.appcompat.app.AppCompatActivity;
  import io.adrop.ads.banner.AdropBanner;
  import io.adrop.ads.banner.AdropBannerListener;
  import io.adrop.ads.model.AdropErrorCode;
  import org.jetbrains.annotations.NotNull;

  public class MainActivity extends AppCompatActivity {
      private AdropBanner banner;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);

          // 1. 배너 인스턴스 생성 (unitId는 생성자에서 필수)
          banner = new AdropBanner(this, "YOUR_UNIT_ID");

          // 2. 리스너 설정
          banner.setListener(new AdropBannerListener() {
              @Override
              public void onAdReceived(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 수신 성공");
              }

              @Override
              public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
                  System.out.println("배너 광고 수신 실패: " + errorCode);
              }

              @Override
              public void onAdImpression(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 노출");
              }

              @Override
              public void onAdClicked(@NotNull AdropBanner banner) {
                  System.out.println("배너 광고 클릭");
              }
          });

          // 3. 뷰 계층에 추가
          FrameLayout container = findViewById(R.id.banner_container);
          FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
              FrameLayout.LayoutParams.MATCH_PARENT,
              FrameLayout.LayoutParams.WRAP_CONTENT
          );
          container.addView(banner, params);

          // 4. 광고 로드
          banner.load();
      }

      @Override
      protected void onDestroy() {
          super.onDestroy();
          // 5. 메모리 해제
          if (banner != null) {
              banner.destroy();
              banner = null;
          }
      }
  }
  ```
</CodeGroup>

***

## AdropBanner 초기화

### 생성자

<ParamField body="context" type="Context" required>
  Activity 또는 Application Context
</ParamField>

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val banner = AdropBanner(context, "YOUR_UNIT_ID")
  ```

  ```java Java theme={null}
  AdropBanner banner = new AdropBanner(context, "YOUR_UNIT_ID");
  ```
</CodeGroup>

### 유닛 ID 설정

<ParamField body="unitId" type="String" required>
  애드컨트롤 콘솔에서 생성한 유닛 ID
</ParamField>

<CodeGroup>
  ```kotlin Kotlin theme={null}
  banner.setUnitId("YOUR_UNIT_ID")
  ```

  ```java Java theme={null}
  banner.setUnitId("YOUR_UNIT_ID");
  ```
</CodeGroup>

### 광고 로드

배너를 화면에 추가한 후 `load()` 메서드를 호출하여 광고를 요청합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  banner.load()
  ```

  ```java Java theme={null}
  banner.load();
  ```
</CodeGroup>

<Warning>
  배너가 화면에 보이는 시점에 `load()`를 호출하세요. 화면에 보이지 않는 상태에서 로드하면 노출이 정확하게 측정되지 않을 수 있습니다.
</Warning>

***

## 리스너 구현

`AdropBannerListener` 인터페이스는 광고의 생명주기 이벤트를 처리합니다.

### onAdReceived (필수)

광고 수신이 성공했을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdReceived(banner: AdropBanner) {
      println("배너 광고 수신 성공")
      // 광고 로딩 인디케이터 숨김 등의 처리
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdReceived(@NotNull AdropBanner banner) {
      System.out.println("배너 광고 수신 성공");
      // 광고 로딩 인디케이터 숨김 등의 처리
  }
  ```
</CodeGroup>

### onAdFailedToReceive (필수)

광고 수신이 실패했을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
      println("배너 광고 수신 실패: $errorCode")
      // 에러 처리 및 대체 콘텐츠 표시
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
      System.out.println("배너 광고 수신 실패: " + errorCode);
      // 에러 처리 및 대체 콘텐츠 표시
  }
  ```
</CodeGroup>

<ParamField body="errorCode" type="AdropErrorCode">
  에러 유형을 나타내는 코드. 자세한 내용은 [레퍼런스](/ko/sdk/android/reference#에러-코드)를 참고하세요.
</ParamField>

### onAdImpression (선택)

광고가 화면에 노출되었을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdImpression(banner: AdropBanner) {
      println("배너 광고 노출")
      // 노출 분석 로깅 등의 처리
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdImpression(@NotNull AdropBanner banner) {
      System.out.println("배너 광고 노출");
      // 노출 분석 로깅 등의 처리
  }
  ```
</CodeGroup>

### onAdClicked (필수)

사용자가 광고를 클릭했을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdClicked(banner: AdropBanner) {
      println("배너 광고 클릭")
      // 클릭 분석 로깅 등의 처리
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdClicked(@NotNull AdropBanner banner) {
      System.out.println("배너 광고 클릭");
      // 클릭 분석 로깅 등의 처리
  }
  ```
</CodeGroup>

### onAdVideoStart (선택)

동영상 배너 광고에서 동영상 재생이 시작되었을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdVideoStart(banner: AdropBanner) {
      println("배너 동영상 재생 시작")
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdVideoStart(@NotNull AdropBanner banner) {
      System.out.println("배너 동영상 재생 시작");
  }
  ```
</CodeGroup>

### onAdVideoEnd (선택)

동영상 배너 광고에서 동영상 재생이 종료되었을 때 호출됩니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdVideoEnd(banner: AdropBanner) {
      println("배너 동영상 재생 종료")
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdVideoEnd(@NotNull AdropBanner banner) {
      System.out.println("배너 동영상 재생 종료");
  }
  ```
</CodeGroup>

***

## Context ID 설정

[문맥 타겟팅](/ko/sdk/android/targeting#문맥-타겟팅)을 위해 Context ID를 설정할 수 있습니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // contextId는 읽기 전용 — 생성자에서 설정
  val banner = AdropBanner(this, "YOUR_UNIT_ID", "article_123")
  ```

  ```java Java theme={null}
  // contextId는 읽기 전용 — 생성자에서 설정
  AdropBanner banner = new AdropBanner(this, "YOUR_UNIT_ID", "article_123");
  ```
</CodeGroup>

<Note>
  Context ID는 광고를 로드하기 전에 설정해야 합니다.
</Note>

***

## 생명주기 관리

### destroy()

배너를 더 이상 사용하지 않을 때는 반드시 `destroy()`를 호출하여 리소스를 해제해야 합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onDestroy() {
      super.onDestroy()
      banner?.destroy()
      banner = null
  }
  ```

  ```java Java theme={null}
  @Override
  protected void onDestroy() {
      super.onDestroy();
      if (banner != null) {
          banner.destroy();
          banner = null;
      }
  }
  ```
</CodeGroup>

<Warning>
  `destroy()`를 호출하지 않으면 메모리 누수가 발생할 수 있습니다.
</Warning>

***

## 광고 크기

배너 광고는 유닛에 설정한 크기에 맞춰 뷰의 크기를 지정해야 합니다.

### 일반적인 배너 크기

| 크기        | 용도     |
| --------- | ------ |
| 320 x 50  | 소형 배너  |
| 320 x 100 | 중형 배너  |
| 16:9 비율   | 동영상 배너 |

### XML에서 크기 지정

```xml theme={null}
<io.adrop.ads.banner.AdropBanner
    android:id="@+id/adrop_banner"
    android:layout_width="320dp"
    android:layout_height="100dp"
    app:adrop_unit_id="YOUR_UNIT_ID" />
```

### 코드에서 크기 지정

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val params = FrameLayout.LayoutParams(
      dpToPx(320), // width
      dpToPx(100)   // height
  )
  container.addView(banner, params)

  // dp를 px로 변환하는 유틸리티 함수
  fun dpToPx(dp: Int): Int {
      val density = resources.displayMetrics.density
      return (dp * density).toInt()
  }
  ```

  ```java Java theme={null}
  FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
      dpToPx(320), // width
      dpToPx(100)   // height
  );
  container.addView(banner, params);

  // dp를 px로 변환하는 유틸리티 함수
  private int dpToPx(int dp) {
      float density = getResources().getDisplayMetrics().density;
      return (int) (dp * density);
  }
  ```
</CodeGroup>

***

## 테스트 유닛 ID

개발 및 테스트 시 아래의 테스트 유닛 ID를 사용하세요.

| 광고 유형         | 테스트 유닛 ID                               | 크기        |
| ------------- | --------------------------------------- | --------- |
| 배너 (320x50)   | `PUBLIC_TEST_UNIT_ID_320_50`            | 320 x 50  |
| 배너 (320x100)  | `PUBLIC_TEST_UNIT_ID_320_100`           | 320 x 100 |
| 캐러셀 배너        | `PUBLIC_TEST_UNIT_ID_CAROUSEL`          | 가변        |
| 배너 비디오 (16:9) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` | 16:9 비율   |
| 배너 비디오 (9:16) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` | 9:16 비율   |

### 사용 예시

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val banner = AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_100")
  ```

  ```java Java theme={null}
  AdropBanner banner = new AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_100");
  ```

  ```xml XML theme={null}
  <io.adrop.ads.banner.AdropBanner
      android:id="@+id/adrop_banner"
      android:layout_width="320dp"
      android:layout_height="100dp"
      app:adrop_unit_id="PUBLIC_TEST_UNIT_ID_320_100" />
  ```
</CodeGroup>

***

## 동영상 재생 제어

동영상 배너 광고의 경우, `play()`와 `pause()`를 사용하여 비디오 재생을 수동으로 제어할 수 있습니다.

### play()

동영상 재생을 재개합니다. 팝업이 닫히거나 앱이 백그라운드에서 복귀해 배너가 다시 노출될 때 호출하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  banner?.play()
  ```

  ```java Java theme={null}
  banner.play();
  ```
</CodeGroup>

### pause()

동영상 재생을 일시 정지합니다. 배너가 팝업에 가려지거나 앱이 백그라운드로 진입할 때 호출하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  banner?.pause()
  ```

  ```java Java theme={null}
  banner.pause();
  ```
</CodeGroup>

### 예시: 백그라운드/포그라운드 처리

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onResume() {
      super.onResume()
      banner?.play()
  }

  override fun onPause() {
      super.onPause()
      banner?.pause()
  }
  ```

  ```java Java theme={null}
  @Override
  protected void onResume() {
      super.onResume();
      if (banner != null) {
          banner.play();
      }
  }

  @Override
  protected void onPause() {
      super.onPause();
      if (banner != null) {
          banner.pause();
      }
  }
  ```
</CodeGroup>

<Note>
  `play()`와 `pause()`는 동영상 배너 광고에만 영향을 미칩니다. 이미지 배너에는 효과가 없습니다.
</Note>

***

## 커스텀 클릭 처리

기본적으로 SDK는 사용자가 배너 광고를 클릭하면 자동으로 도착 URL을 엽니다. 클릭 이벤트를 직접 처리하고 싶다면 (예: 확인 다이얼로그 표시, 분석 로깅 후 이동 등) `useCustomClick`을 사용할 수 있습니다.

<ParamField path="useCustomClick" type="Boolean" default="false">
  `true`로 설정하면 SDK가 클릭 시 자동으로 도착 URL을 열지 않습니다. `onAdClicked` 이벤트만 발생하며, 직접 `open()`을 호출해야 합니다.
</ParamField>

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // 1. 커스텀 클릭 처리 활성화
  banner?.useCustomClick = true

  // 2. 리스너에서 클릭 처리
  banner?.listener = object : AdropBannerListener {
      override fun onAdClicked(banner: AdropBanner) {
          // 커스텀 로직 수행 (다이얼로그 표시, 분석 로깅 등)
          println("광고 클릭됨 — 도착 URL 열기")
          banner.open()
      }

      // ... 기타 리스너 메서드
  }
  ```

  ```java Java theme={null}
  // 1. 커스텀 클릭 처리 활성화
  banner.setUseCustomClick(true);

  // 2. 리스너에서 클릭 처리
  banner.setListener(new AdropBannerListener() {
      @Override
      public void onAdClicked(@NotNull AdropBanner banner) {
          // 커스텀 로직 수행 (다이얼로그 표시, 분석 로깅 등)
          System.out.println("광고 클릭됨 — 도착 URL 열기");
          banner.open();
      }

      // ... 기타 리스너 메서드
  });
  ```
</CodeGroup>

### open()

광고의 도착 URL을 엽니다. 커스텀 URL을 전달하여 다른 URL을 열 수도 있습니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // 기본 도착 URL 열기
  banner?.open()

  // 커스텀 URL 열기
  banner?.open("https://example.com")
  ```

  ```java Java theme={null}
  // 기본 도착 URL 열기
  banner.open();

  // 커스텀 URL 열기
  banner.open("https://example.com");
  ```
</CodeGroup>

<Note>
  `open()`은 `useCustomClick`이 `true`로 설정된 경우에만 동작합니다. `useCustomClick`이 `false`(기본값)이면 SDK가 자동으로 URL을 엽니다.
</Note>

***

## 모범 사례

### 1. 메모리 관리

Activity나 Fragment가 종료될 때 배너를 반드시 해제하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onDestroy() {
      super.onDestroy()
      banner?.destroy()
      banner = null
  }
  ```

  ```java Java theme={null}
  @Override
  protected void onDestroy() {
      super.onDestroy();
      if (banner != null) {
          banner.destroy();
          banner = null;
      }
  }
  ```
</CodeGroup>

### 2. 화면 가시성

배너가 화면에 보일 때 광고를 로드하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onResume() {
      super.onResume()
      banner?.load()
  }
  ```

  ```java Java theme={null}
  @Override
  protected void onResume() {
      super.onResume();
      if (banner != null) {
          banner.load();
      }
  }
  ```
</CodeGroup>

### 3. 에러 처리

광고 로드 실패 시 적절한 에러 처리를 구현하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
      when (errorCode) {
          AdropErrorCode.ERROR_CODE_NETWORK -> {
              println("네트워크 오류: 연결을 확인하세요")
          }
          AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
              println("노출 가능한 광고 없음")
          }
          else -> {
              println("광고 로드 실패: $errorCode")
          }
      }
  }
  ```

  ```java Java theme={null}
  @Override
  public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
      if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
          System.out.println("네트워크 오류: 연결을 확인하세요");
      } else if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
          System.out.println("노출 가능한 광고 없음");
      } else {
          System.out.println("광고 로드 실패: " + errorCode);
      }
  }
  ```
</CodeGroup>

### 4. 리사이클러뷰에서 사용

리사이클러뷰에서 배너를 사용할 때는 뷰홀더에서 적절히 관리하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class BannerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
      private val banner: AdropBanner = view.findViewById(R.id.adrop_banner)

      fun bind() {
          banner.listener = object : AdropBannerListener {
              // 리스너 구현
          }
          banner.load()
      }

      fun recycle() {
          banner.destroy()
      }
  }

  // 어댑터에서
  override fun onViewRecycled(holder: BannerViewHolder) {
      holder.recycle()
  }
  ```

  ```java Java theme={null}
  class BannerViewHolder extends RecyclerView.ViewHolder {
      private AdropBanner banner;

      public BannerViewHolder(View view) {
          super(view);
          banner = view.findViewById(R.id.adrop_banner);
      }

      public void bind() {
          banner.setListener(new AdropBannerListener() {
              // 리스너 구현
          });
          banner.load();
      }

      public void recycle() {
          banner.destroy();
      }
  }

  // 어댑터에서
  @Override
  public void onViewRecycled(BannerViewHolder holder) {
      holder.recycle();
  }
  ```
</CodeGroup>

***

## 전체 예제

### Kotlin 예제

```kotlin theme={null}
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.banner.AdropBanner
import io.adrop.ads.banner.AdropBannerListener
import io.adrop.ads.model.AdropErrorCode

class BannerActivity : AppCompatActivity() {
    private var banner: AdropBanner? = null
    private lateinit var progressBar: ProgressBar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_banner)

        progressBar = findViewById(R.id.progress_bar)
        setupBanner()
    }

    private fun setupBanner() {
        // 1. 배너 인스턴스 생성
        // contextId는 읽기 전용 프로퍼티 — 생성자에서 설정
        banner = AdropBanner(this, "YOUR_UNIT_ID", "article_123").apply {
            // 2. 리스너 설정
            listener = object : AdropBannerListener {
                override fun onAdReceived(banner: AdropBanner) {
                    println("배너 광고 수신 성공")
                    progressBar.visibility = View.GONE
                }

                override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
                    println("배너 광고 수신 실패: $errorCode")
                    progressBar.visibility = View.GONE

                    // 에러에 따른 처리
                    when (errorCode) {
                        AdropErrorCode.ERROR_CODE_NETWORK -> {
                            showToast("네트워크 연결을 확인하세요")
                        }
                        AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
                            println("노출 가능한 광고가 없습니다")
                        }
                        else -> {
                            showToast("광고를 불러올 수 없습니다")
                        }
                    }
                }

                override fun onAdImpression(banner: AdropBanner) {
                    println("배너 광고 노출")
                }

                override fun onAdClicked(banner: AdropBanner) {
                    println("배너 광고 클릭")
                }
            }
        }

        // 3. 뷰 계층에 추가
        val container = findViewById<FrameLayout>(R.id.banner_container)
        val params = FrameLayout.LayoutParams(
            dpToPx(320),
            dpToPx(100)
        )
        container.addView(banner, params)

        // 4. 광고 로드
        progressBar.visibility = View.VISIBLE
        banner?.load()
    }

    private fun dpToPx(dp: Int): Int {
        val density = resources.displayMetrics.density
        return (dp * density).toInt()
    }

    private fun showToast(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }

    override fun onDestroy() {
        super.onDestroy()
        // 5. 메모리 해제
        banner?.destroy()
        banner = null
    }
}
```

### Java 예제

```java theme={null}
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.banner.AdropBanner;
import io.adrop.ads.banner.AdropBannerListener;
import io.adrop.ads.model.AdropErrorCode;
import org.jetbrains.annotations.NotNull;

public class BannerActivity extends AppCompatActivity {
    private AdropBanner banner;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_banner);

        progressBar = findViewById(R.id.progress_bar);
        setupBanner();
    }

    private void setupBanner() {
        // 1. 배너 인스턴스 생성 (Context ID 포함)
        banner = new AdropBanner(this, "YOUR_UNIT_ID", "article_123");

        // 2. 리스너 설정
        banner.setListener(new AdropBannerListener() {
            @Override
            public void onAdReceived(@NotNull AdropBanner banner) {
                System.out.println("배너 광고 수신 성공");
                progressBar.setVisibility(View.GONE);
            }

            @Override
            public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
                System.out.println("배너 광고 수신 실패: " + errorCode);
                progressBar.setVisibility(View.GONE);

                // 에러에 따른 처리
                if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
                    showToast("네트워크 연결을 확인하세요");
                } else if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
                    System.out.println("노출 가능한 광고가 없습니다");
                } else {
                    showToast("광고를 불러올 수 없습니다");
                }
            }

            @Override
            public void onAdImpression(@NotNull AdropBanner banner) {
                System.out.println("배너 광고 노출");
            }

            @Override
            public void onAdClicked(@NotNull AdropBanner banner) {
                System.out.println("배너 광고 클릭");
            }
        });

        // 3. 뷰 계층에 추가
        FrameLayout container = findViewById(R.id.banner_container);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            dpToPx(320),
            dpToPx(100)
        );
        container.addView(banner, params);

        // 4. 광고 로드
        progressBar.setVisibility(View.VISIBLE);
        banner.load();
    }

    private int dpToPx(int dp) {
        float density = getResources().getDisplayMetrics().density;
        return (int) (dp * density);
    }

    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 5. 메모리 해제
        if (banner != null) {
            banner.destroy();
            banner = null;
        }
    }
}
```

### XML 레이아웃

```xml activity_banner.xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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="match_parent">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="배너 광고 예제"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginTop="32dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <FrameLayout
        android:id="@+id/banner_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

***

## 일괄 로드 (loads)

`AdropBanner.loads()`를 사용하면 한 번의 호출로 여러 배너 광고를 요청할 수 있습니다. 캐러셀, 페이지네이션 피드, 광고 풀 프리페칭 등에 유용합니다.

### 시그니처

<CodeGroup>
  ```kotlin Kotlin theme={null}
  AdropBanner.loads(
      context: Context,
      unitId: String,
      contextId: String? = null,
      listener: AdropBannerListener,
  )
  ```

  ```java Java theme={null}
  AdropBanner.loads(context, "YOUR_UNIT_ID", null, listener);
  ```
</CodeGroup>

### 사용 방법

<CodeGroup>
  ```kotlin Kotlin theme={null}
  AdropBanner.loads(
      context = this,
      unitId = "YOUR_UNIT_ID",
      contextId = null,
      listener = object : AdropBannerListener {
          // 배치 성공 — 최대 5개의 준비된 배너가 전달됩니다.
          override fun onAdsReceived(banners: List<AdropBanner>) {
              banners.forEach { banner ->
                  // 뷰 계층에 추가 (ViewPager2, RecyclerView 등)
                  container.addView(banner)
              }
          }

          // 배치 실패
          override fun onAdsFailedToReceive(errorCode: AdropErrorCode) {
              println("배치 로드 실패: $errorCode")
          }

          // 단건 콜백은 loads 경로에서 호출되지 않습니다.
          override fun onAdReceived(banner: AdropBanner) { /* batch path */ }
          override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {}

          // 클릭/노출/비디오 콜백은 각 인스턴스에 대해 정상 호출됩니다.
          override fun onAdClicked(banner: AdropBanner) {
              println("배너 클릭: ${banner.creativeId}")
          }

          override fun onAdImpression(banner: AdropBanner) {
              println("배너 노출: ${banner.creativeId}")
          }
      },
  )
  ```

  ```java Java theme={null}
  AdropBanner.loads(this, "YOUR_UNIT_ID", null, new AdropBannerListener() {
      @Override
      public void onAdsReceived(@NotNull List<AdropBanner> banners) {
          for (AdropBanner banner : banners) {
              container.addView(banner);
          }
      }

      @Override
      public void onAdsFailedToReceive(@NotNull AdropErrorCode errorCode) {
          System.out.println("배치 로드 실패: " + errorCode);
      }

      // 단건 콜백 — 배치 경로에서는 사용되지 않습니다.
      @Override
      public void onAdReceived(@NotNull AdropBanner banner) { }

      @Override
      public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) { }

      @Override
      public void onAdClicked(@NotNull AdropBanner banner) {
          System.out.println("배너 클릭: " + banner.getCreativeId());
      }

      @Override
      public void onAdImpression(@NotNull AdropBanner banner) {
          System.out.println("배너 노출: " + banner.getCreativeId());
      }
  });
  ```
</CodeGroup>

### 리스너 콜백

| 콜백                                                                   | 호출 시점                                                          |
| -------------------------------------------------------------------- | -------------------------------------------------------------- |
| `onAdsReceived(banners)`                                             | 배치 전달 완료. 각 `AdropBanner`에는 리스너가 이미 부착되어 있고 광고 데이터가 적용된 상태입니다. |
| `onAdsFailedToReceive(errorCode)`                                    | 요청 거부, 네트워크 실패 또는 표시 가능한 광고가 없는 경우(`ERROR_CODE_AD_NO_FILL`).   |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | 단건 `load()`와 동일하게 각 배너별로 호출됩니다.                                |

<Warning>
  단건 `onAdReceived` / `onAdFailedToReceive` 콜백은 `loads()` 경로에서 **호출되지 않습니다**. 배치 결과는 반드시 `onAdsReceived` / `onAdsFailedToReceive`로만 수신하세요.
</Warning>

### 제약 사항

* **호출당 최대 5개.** 서버가 그 이상을 반환하더라도 앞의 5개만 전달됩니다.
* **백필은 적용되지 않습니다.** 직광고 채움이 없으면 백필 설정과 무관하게 `onAdsFailedToReceive`가 `ERROR_CODE_AD_NO_FILL`로 호출됩니다.
* **`onAdsReceived` 안에서 `destroy()`를 호출하지 마세요.** 먼저 각 배너를 뷰 계층에 부착한 뒤, 부모 `Activity.onDestroy()`(또는 동등한 시점)에서 `destroy()` 하세요.
* `onAdsReceived`는 광고 데이터가 적용되는 즉시 발화되며, 이는 단건 `load()` 동작과 동일합니다 — 뷰에 부착된 뒤 크리에이티브가 그려집니다.

***

## 백필 광고

백필 광고가 활성화된 경우, 직광고가 없을 때 자동으로 백필 광고가 로드됩니다. `isBackfilled` 프로퍼티로 백필 광고 여부를 확인할 수 있습니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  banner.listener = object : AdropBannerListener {
      override fun onAdReceived(banner: AdropBanner) {
          if (banner.isBackfilled) {
              println("백필 광고가 로드되었습니다")
          } else {
              println("직광고가 로드되었습니다")
          }
      }

      override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
          when (errorCode) {
              AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
                  println("직광고 없음, 백필 광고 요청 중...")
              }
              AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
                  println("백필 광고도 없음")
              }
              else -> {
                  println("광고 로드 실패: $errorCode")
              }
          }
      }
  }
  ```

  ```java Java theme={null}
  banner.setListener(new AdropBannerListener() {
      @Override
      public void onAdReceived(@NotNull AdropBanner banner) {
          if (banner.isBackfilled()) {
              System.out.println("백필 광고가 로드되었습니다");
          } else {
              System.out.println("직광고가 로드되었습니다");
          }
      }

      @Override
      public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
          if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
              System.out.println("직광고 없음, 백필 광고 요청 중...");
          } else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
              System.out.println("백필 광고도 없음");
          } else {
              System.out.println("광고 로드 실패: " + errorCode);
          }
      }
  });
  ```
</CodeGroup>

<Note>
  백필 광고를 사용하려면 `io.adrop:adrop-ads-backfill` 의존성을 추가해야 합니다.
</Note>

***

## 다음 단계

<CardGroup cols={2}>
  <Card title="네이티브 광고" href="/ko/sdk/android/native">
    UI에 맞게 커스터마이징 가능한 네이티브 광고 구현하기
  </Card>

  <Card title="전면 광고" href="/ko/sdk/android/interstitial">
    화면 전체를 덮는 전면 광고 구현하기
  </Card>

  <Card title="타겟팅 설정" href="/ko/sdk/android/targeting">
    사용자 속성 및 문맥 타겟팅 설정하기
  </Card>

  <Card title="레퍼런스" href="/ko/sdk/android/reference">
    클래스, 리스너, 에러 코드 참고하기
  </Card>
</CardGroup>
