React Native Expo中Admob Rewarded Ad仅能展示一次的问题修复
React Native Expo Admob激励广告重复展示问题修复方案
问题现象
集成Admob激励广告后,广告仅能展示一次,再次调用时触发错误:
Error: RewardedAd.show() The requested RewardedAd has not loaded and could not be shown.
关闭并重新打开模态框后可再次观看,但用户体验不佳,需实现无需关闭模态框即可重复观看广告的效果。
问题根源
- 全局创建的广告实例仅加载一次,展示后未重新加载新广告
loaded状态在广告展示后未重置,导致后续调用watchAd时误判广告已加载- 未监听广告关闭、展示失败等事件,无法及时触发新广告的加载
修复方案
1. 重构useRewardedAd钩子
修改钩子逻辑,支持广告展示后自动重新加载,并管理广告实例生命周期:
import { useEffect, useState } from "react"; import { RewardedAd, RewardedAdEventType, TestIds, } from "react-native-google-mobile-ads"; const adUnitId = __DEV__ ? TestIds.REWARDED : "ca-app-pub-xxxxxxxxxxxxx/yyyyyyyyyyyyyy"; export const useRewardedAd = () => { const [loaded, setLoaded] = useState(false); const [rewardedAd, setRewardedAd] = useState<RewardedAd | null>(null); // 创建新的广告实例 const createAdInstance = () => { const ad = RewardedAd.createForAdRequest(adUnitId); setRewardedAd(ad); return ad; }; // 加载广告 const loadAd = () => { const ad = rewardedAd || createAdInstance(); ad.load(); }; // 展示广告 const watchAd = () => { if (loaded && rewardedAd) { rewardedAd.show(); } }; useEffect(() => { let currentAd = rewardedAd || createAdInstance(); // 监听广告加载完成 const unsubscribeLoaded = currentAd.addAdEventListener( RewardedAdEventType.LOADED, () => setLoaded(true) ); // 监听用户获得奖励 const unsubscribeEarned = currentAd.addAdEventListener( RewardedAdEventType.EARNED_REWARD, (reward) => console.log("用户获得奖励: ", reward) ); // 监听广告关闭,重置状态并加载新广告 const unsubscribeClosed = currentAd.addAdEventListener( RewardedAdEventType.CLOSED, () => { setLoaded(false); loadAd(); } ); // 监听广告展示失败,重置状态并加载新广告 const unsubscribeFailed = currentAd.addAdEventListener( RewardedAdEventType.FAILED_TO_SHOW, () => { setLoaded(false); loadAd(); } ); // 初始化加载广告 loadAd(); // 组件卸载时取消所有监听 return () => { unsubscribeLoaded(); unsubscribeEarned(); unsubscribeClosed(); unsubscribeFailed(); }; }, [rewardedAd]); return { watchAd, isAdLoaded: loaded }; };
2. 更新组件调用逻辑
在Earn组件中使用新的钩子返回值,优化用户提示:
import { View, Text, Alert } from "react-native"; import React, { useContext } from "react"; import { Entypo, FontAwesome, FontAwesome5 } from "@expo/vector-icons"; import EarnRow from "./EarnRow/EarnRow"; import { PlayerInformationContext } from "@/context/PlayerInformation"; import { useRewardedAd } from "@/hooks/useRewardedAd/useRewardedAd"; const Earn = () => { const { watchAd, isAdLoaded } = useRewardedAd(); const { money, seenAdsCount, health, setHealth, setMoney, handleSeenAdsCount, } = useContext(PlayerInformationContext); const handleEarn = async (type: string) => { switch (type) { case "getMoney": if (seenAdsCount < 10) { handleSeenAdsCount(seenAdsCount + 1); } if (isAdLoaded) { watchAd(); } else { Alert.alert("广告加载中", "请稍等片刻再尝试"); } break; case "getHealth": if (money >= 100) { setHealth(health + 12); setMoney(money - 100); Alert.alert("获得能量!", "你获得了12点能量值!"); } else { Alert.alert("金币不足", "进行此操作需要100金币"); } break; default: break; } }; return ( <View className="w-full h-full items-start justify-start"> <Text className="w-full mt-10 text-center text-white font-bold text-4xl"> 赚取 </Text> <EarnRow icon={ <FontAwesome5 name="bitcoin" size={40} className="-rotate-12 bg-white rounded-full" color="orange" /> } coinAmount="+30" rewardText="免费" rewardAction={() => handleEarn("getMoney")} buttonIcon={<Entypo name="video" size={24} color="white" />} /> <EarnRow icon={<FontAwesome name="heart" size={40} color="red" />} coinAmount="+12" rewardText="100" rewardAction={() => handleEarn("getHealth")} buttonIcon={ <FontAwesome5 name="bitcoin" size={24} className="-rotate-12 bg-white rounded-full" color="orange" /> } /> </View> ); }; export default Earn;
核心优化点
- 广告实例内部管理,避免全局实例复用导致的状态混乱
- 监听广告关闭、展示失败事件,自动重置加载状态并触发新广告加载
- 返回
isAdLoaded状态,组件可实时判断广告是否可用,给出友好提示 - 每次广告展示完成后立即加载新广告,确保用户下次点击时已有可用广告
内容的提问来源于stack exchange,提问作者Ozan
相关产品推荐
相关产品推荐

