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

# 原生广告

> 在 Android 应用中集成 CloudX 原生广告和 Reels 风格原生视频体验

原生广告允许您使用自定义 UI 组件（标题、正文、图标、媒体、行动号召）按自己的布局渲染广告素材。配合视频素材和正确的播放设置，原生广告即可成为全屏垂直 "Reels"（与 Instagram Reels 或 TikTok 相同的用户体验）。

一个 Reel 由三部分构成：

1. **包含视频内容的原生广告。** 广告的 `mediaView` 包含视频播放器，其他素材（标题、正文、图标、CTA）覆盖在视频上方。
2. **视频播放设置。** 加载器上的三个属性可以禁用全屏、开启有声播放以及隐藏媒体控件。
3. **全屏分页容器。** 垂直方向的 `ViewPager2`，每一页即为一个 Reel。

#### Reels API 接口

| 功能     | CloudX API                                 | 描述                         |
| ------ | ------------------------------------------ | -------------------------- |
| 检测视频素材 | `ad.nativeAd?.isVideoContent`              | 当加载的素材是视频时返回 `true`        |
| 获取视频时长 | `ad.nativeAd?.videoDuration`               | 视频时长（秒），未知时为 0.0           |
| 用户关闭广告 | `onNativeAdClosed(ad)` 监听回调                | 当用户通过 AdChoices 举报或隐藏广告时触发 |
| 禁用全屏   | `loader.isVideoFullScreenDisabled = true`  | 阻止视频在点击时进入全屏               |
| 开启有声播放 | `loader.isStartVideoUnmuted = true`        | 以有声状态开始播放                  |
| 隐藏媒体控件 | `loader.isVideoMediaControlsHidden = true` | 隐藏播放/暂停和进度条控件              |

<Note>
  原生广告适配器支持情况和依赖要求记录在各适配器概览页中。当前原生广告支持请参阅 [Meta Audience Network 适配器](/zh/android/adapters/meta/overview)。
</Note>

#### 创建布局和视图绑定器

```xml res/layout/native_ad_layout.xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <FrameLayout
            android:id="@+id/native_ad_options"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center_vertical">

            <ImageView
                android:id="@+id/native_ad_icon"
                android:layout_width="48dp"
                android:layout_height="48dp" />

            <TextView
                android:id="@+id/native_ad_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginStart="8dp"
                android:textSize="16sp"
                android:textStyle="bold" />
        </LinearLayout>

        <TextView
            android:id="@+id/native_ad_body"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp" />

        <FrameLayout
            android:id="@+id/native_ad_media_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center_vertical">

            <TextView
                android:id="@+id/native_ad_advertiser"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textColor="#888" />

            <Button
                android:id="@+id/native_ad_cta"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout>
</FrameLayout>
```

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val binder = CloudXNativeAdViewBinder.Builder(R.layout.native_ad_layout)
      .setTitleTextViewId(R.id.native_ad_title)
      .setBodyTextViewId(R.id.native_ad_body)
      .setIconImageViewId(R.id.native_ad_icon)
      .setMediaContentViewGroupId(R.id.native_ad_media_container)
      .setCallToActionButtonId(R.id.native_ad_cta)
      .setOptionsContentViewGroupId(R.id.native_ad_options)
      .setAdvertiserTextViewId(R.id.native_ad_advertiser)
      .build()
  ```

  ```java Java theme={null}
  CloudXNativeAdViewBinder binder = new CloudXNativeAdViewBinder.Builder(R.layout.native_ad_layout)
      .setTitleTextViewId(R.id.native_ad_title)
      .setBodyTextViewId(R.id.native_ad_body)
      .setIconImageViewId(R.id.native_ad_icon)
      .setMediaContentViewGroupId(R.id.native_ad_media_container)
      .setCallToActionButtonId(R.id.native_ad_cta)
      .setOptionsContentViewGroupId(R.id.native_ad_options)
      .setAdvertiserTextViewId(R.id.native_ad_advertiser)
      .build();
  ```
</CodeGroup>

#### 创建加载器并配置视频

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class YourActivity : AppCompatActivity(), CloudXNativeAdListener, CloudXAdRevenueListener {
      private lateinit var nativeAdLoader: CloudXNativeAdLoader

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)

          nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id")
          nativeAdLoader.nativeAdListener = this
          nativeAdLoader.revenueListener = this

          nativeAdLoader.isVideoFullScreenDisabled = true
          nativeAdLoader.isStartVideoUnmuted = true
          nativeAdLoader.isVideoMediaControlsHidden = true
      }

      override fun onDestroy() {
          super.onDestroy()
          nativeAdLoader.destroy()
      }
  }
  ```

  ```java Java theme={null}
  public class YourActivity extends AppCompatActivity implements CloudXNativeAdListener, CloudXAdRevenueListener {
      private CloudXNativeAdLoader nativeAdLoader;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);

          nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id");
          nativeAdLoader.setNativeAdListener(this);
          nativeAdLoader.setRevenueListener(this);

          nativeAdLoader.setVideoFullScreenDisabled(true);
          nativeAdLoader.setStartVideoUnmuted(true);
          nativeAdLoader.setVideoMediaControlsHidden(true);
      }

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

| 属性                           | 默认值     | Reels 值 | 描述            |
| ---------------------------- | ------- | ------- | ------------- |
| `isVideoFullScreenDisabled`  | `false` | `true`  | 阻止视频在点击时进入全屏  |
| `isStartVideoUnmuted`        | `false` | `true`  | 以有声状态开始视频播放   |
| `isVideoMediaControlsHidden` | `false` | `true`  | 隐藏播放/暂停和进度条控件 |

在调用 `loadAd()` **之前**设置以上属性。对静态图片素材无效。对于 Reels 风格的信息流，将三个属性都设为 `true`。

#### 加载广告

**流程 A — 加载到预构建的视图中：**

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val adView = CloudXNativeAdView(this, binder)
  nativeAdLoader.loadAd(adView)
  ```

  ```java Java theme={null}
  CloudXNativeAdView adView = new CloudXNativeAdView(this, binder);
  nativeAdLoader.loadAd(adView);
  ```
</CodeGroup>

**流程 B — 先加载，后渲染（延迟渲染）：**

<CodeGroup>
  ```kotlin Kotlin theme={null}
  nativeAdLoader.loadAd()

  override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
      val newAdView = CloudXNativeAdView(this@YourActivity, binder)
      nativeAdLoader.render(newAdView, ad)
      container.addView(newAdView)
  }
  ```

  ```java Java theme={null}
  nativeAdLoader.loadAd();

  @Override
  public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
      CloudXNativeAdView newAdView = new CloudXNativeAdView(YourActivity.this, binder);
      nativeAdLoader.render(newAdView, ad);
      container.addView(newAdView);
  }
  ```
</CodeGroup>

#### 处理回调

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // 必选回调

  override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
      Log.d("CloudX", "Native ad loaded from ${ad.networkName}")

      ad.nativeAd?.let { nativeAd ->
          if (nativeAd.isVideoContent) {
              Log.d("CloudX", "Video duration: ${nativeAd.videoDuration}s")
          }
      }

      adView?.let { container.addView(it) }
  }

  override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
      Log.e("CloudX", "Native ad failed to load: ${error.message}")
  }

  override fun onNativeAdClicked(ad: CloudXAd) {
      Log.d("CloudX", "Native ad clicked")
  }

  // 可选回调（默认空实现）

  override fun onNativeAdExpired(ad: CloudXAd) {
      Log.d("CloudX", "Native ad expired — destroy and reload")
      nativeAdLoader.destroy(ad)
      nativeAdLoader.loadAd()
  }

  override fun onNativeAdClosed(ad: CloudXAd) {
      Log.d("CloudX", "User dismissed the ad via AdChoices")
      nativeAdLoader.destroy(ad)
  }

  // 收入回调

  override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
      Log.d("CloudX", "Native ad revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
  }
  ```

  ```java Java theme={null}
  // 必选回调

  @Override
  public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
      Log.d("CloudX", "Native ad loaded from " + ad.getNetworkName());

      CloudXNativeAd nativeAd = ad.getNativeAd();
      if (nativeAd != null && nativeAd.isVideoContent()) {
          Log.d("CloudX", "Video duration: " + nativeAd.getVideoDuration() + "s");
      }

      if (adView != null) {
          container.addView(adView);
      }
  }

  @Override
  public void onNativeAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError error) {
      Log.e("CloudX", "Native ad failed to load: " + error.getMessage());
  }

  @Override
  public void onNativeAdClicked(@NonNull CloudXAd ad) {
      Log.d("CloudX", "Native ad clicked");
  }

  // 可选回调（默认空实现）

  @Override
  public void onNativeAdExpired(@NonNull CloudXAd ad) {
      Log.d("CloudX", "Native ad expired — destroy and reload");
      nativeAdLoader.destroy(ad);
      nativeAdLoader.loadAd();
  }

  @Override
  public void onNativeAdClosed(@NonNull CloudXAd ad) {
      Log.d("CloudX", "User dismissed the ad via AdChoices");
      nativeAdLoader.destroy(ad);
  }

  // 收入回调

  @Override
  public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
      Log.d("CloudX", "Native ad revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
  }
  ```
</CodeGroup>

#### 清理资源

使用完毕后务必销毁广告：

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // 销毁特定已加载的广告
  nativeAdLoader.destroy(ad)

  // 销毁加载器及所有关联资源
  nativeAdLoader.destroy()
  ```

  ```java Java theme={null}
  // 销毁特定已加载的广告
  nativeAdLoader.destroy(ad);

  // 销毁加载器及所有关联资源
  nativeAdLoader.destroy();
  ```
</CodeGroup>

#### 原生广告素材（CloudXNativeAd）

`CloudXNativeAd` 接口可通过监听回调中的 `ad.nativeAd` 获取：

| 属性                        | 类型                     | 描述                         |
| ------------------------- | ---------------------- | -------------------------- |
| `title`                   | `String?`              | 标题文本                       |
| `body`                    | `String?`              | 正文/描述文本                    |
| `callToAction`            | `String?`              | CTA 按钮文本（例如 "Install Now"） |
| `advertiser`              | `String?`              | 广告主名称                      |
| `icon`                    | `CloudXNativeAdImage?` | 应用图标（`Drawable` 或 `Uri`）   |
| `mainImage`               | `CloudXNativeAdImage?` | 主图片（静态素材）                  |
| `mediaView`               | `View?`                | 视频/媒体播放器视图（适配器提供）          |
| `optionsView`             | `View?`                | AdChoices 或选项视图（适配器提供）     |
| `mediaContentAspectRatio` | `Float`                | 媒体内容的宽高比                   |
| `starRating`              | `Double?`              | 应用商店评分（0–5）                |
| `isVideoContent`          | `Boolean`              | 素材是否为视频                    |
| `videoDuration`           | `Double`               | 视频时长（秒），未知时为 0.0           |
| `isExpired`               | `Boolean`              | 广告是否已过期                    |

#### Reels 信息流提示

* 使用 `ViewPager2` 并设置 `android:orientation="vertical"`，每个条目宽高都为 `MATCH_PARENT`。
* 回收条目时对 `CloudXNativeAdView` 调用 `prepareForReuse()`。
* 每个广告位创建一个 `CloudXNativeAdLoader`，按顺序加载广告。
* 当广告不再需要时，通过 `destroy(ad)` 销毁。
