React Native应用后台时Unity Ads的AD_COMPLETED事件触发问题
问题描述
在React Native中集成Unity Ads原生模块时遇到了一个问题:当广告播放导致应用进入后台后,AD_COMPLETED事件只有等应用回到前台时才会触发。我需要实现广告在后台完成时立即触发该事件,这样才能及时执行后续任务(比如启动挖矿逻辑),就像react-native-google-mobile-ads插件里Admob广告的正常表现那样。
原因分析
默认用DeviceEventManagerModule.RCTDeviceEventEmitter发送事件时,依赖React Native的JS线程调度。当应用处于后台时,JS线程可能被系统挂起,导致事件没法即时传递到JS层。而成熟的广告插件会用更可靠的事件传递机制,确保后台事件能即时触发。
解决方案
核心思路
通过Android的HeadlessJsTaskService创建后台任务,确保广告完成的事件在后台也能触发JS逻辑;同时区分前台/后台场景,分别用直接发送事件和启动Headless任务的方式处理。
具体实现步骤
1. Android端代码修改
第一步:创建Headless任务服务
新建UnityAdsHeadlessService.java文件:
import com.facebook.react.HeadlessJsTaskService; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; import com.facebook.react.jstasks.HeadlessJsTaskConfig; import android.content.Intent; import android.os.Bundle; public class UnityAdsHeadlessService extends HeadlessJsTaskService { @Override protected HeadlessJsTaskConfig getTaskConfig(Intent intent) { Bundle extras = intent.getExtras(); WritableMap data = Arguments.createMap(); if (extras != null) { data.putString("adStatus", extras.getString("adStatus")); } return new HeadlessJsTaskConfig( "UnityAdsBackgroundTask", data, 5000, // 任务超时时间,单位毫秒 false // 仅允许后台执行 ); } }
第二步:在AndroidManifest.xml注册服务
在<application>标签内添加:
<service android:name=".UnityAdsHeadlessService" />
第三步:修改事件发送逻辑
替换原有的sendEvent方法,新增判断应用前后台的逻辑:
import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import java.util.List; private void sendEventOrStartHeadlessTask(String eventName, WritableMap params) { // 判断应用是否处于后台 if (isAppInBackground(reactContext)) { // 启动Headless任务处理后台事件 Intent intent = new Intent(reactContext, UnityAdsHeadlessService.class); intent.putExtra("adStatus", params.getString("adStatus")); reactContext.startService(intent); } else { // 前台时直接发送事件到JS reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); } } // 判断应用是否在后台的工具方法 private boolean isAppInBackground(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); if (appProcesses == null) return false; final String packageName = context.getPackageName(); for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { return true; } } return false; }
第四步:更新Unity Ads监听器
把原来的sendEvent调用替换为新的方法,同时注意每次创建新的WritableMap避免数据覆盖:
@Override public void onUnityAdsShowComplete(String placementId, UnityAds.UnityAdsShowCompletionState state) { Log.v("UnityAdsExample", "onUnityAdsShowComplete: " + placementId); WritableMap params = Arguments.createMap(); if (state.equals(UnityAds.UnityAdsShowCompletionState.COMPLETED)) { params.putString("adStatus", "fullComplete"); sendEventOrStartHeadlessTask("AD_COMPLETED", params); } else { params.putString("adStatus", "partialComplete"); sendEventOrStartHeadlessTask("AD_COMPLETED", params); } } @Override public void onUnityAdsShowFailure(String placementId, UnityAds.UnityAdsShowError error, String message) { Log.e("UnityAdsExample", "Unity Ads failed to show ad for " + placementId + " with error: [" + error + "] " + message); WritableMap params = Arguments.createMap(); params.putString("adStatus", "adShowFail"); sendEventOrStartHeadlessTask("AD_COMPLETED", params); }
2. JS端代码修改
第一步:注册Headless任务
在项目入口文件(比如index.js)中添加:
import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; // 注册Unity Ads后台任务 AppRegistry.registerHeadlessTask('UnityAdsBackgroundTask', () => require('./UnityAdsBackgroundTask') ); AppRegistry.registerComponent(appName, () => App);
第二步:创建后台任务处理文件
新建UnityAdsBackgroundTask.js:
import AsyncStorage from '@react-native-async-storage/async-storage'; module.exports = async (taskData) => { const { adStatus } = taskData; console.log("Unity Ad后台事件触发:", adStatus); // 后台执行后续任务,比如标记需要启动挖矿 if (adStatus === "fullComplete") { console.log("后台标记启动挖矿..."); await AsyncStorage.setItem('shouldStartMining', 'true'); } return Promise.resolve(); };
第三步:更新组件内的事件监听
修改原有的useEffect,增加后台任务状态检查:
import AsyncStorage from '@react-native-async-storage/async-storage'; React.useEffect(() => { // 组件挂载时检查后台是否已经触发了挖矿标记 const checkBackgroundFlag = async () => { const shouldStart = await AsyncStorage.getItem('shouldStartMining'); if (shouldStart === 'true') { setMiningAfterAd(true); await AsyncStorage.removeItem('shouldStartMining'); } }; checkBackgroundFlag(); const loadEventEmitter = new NativeEventEmitter(); const showEventEmitter = new NativeEventEmitter(); const loadEventListener = loadEventEmitter.addListener( 'AD_LOADED', (event) => { console.log("Unity Ad加载完成"); console.log(event.adStatus); if(showAdTimeOutRef.current){ setShowAdTimeOut(false); return; } else { setAdIsLoaded(true); UnityAdsModule.showAd(); } }); const showEventListener = showEventEmitter.addListener( 'AD_COMPLETED', (event) => { console.log("Unity Ad播放完成"); console.log(event.adStatus); if(event.adStatus === "fullComplete"){ console.log("启动挖矿..."); setMiningAfterAd(true); } }); return () => { loadEventListener.remove(); showEventListener.remove(); }; }, []);
关键注意事项
- Headless任务执行时间不能过长,否则会被系统回收;如果需要长时间运行,要配合前台服务使用。
- 后台任务无法直接更新React组件状态,需要用本地存储(如AsyncStorage)或全局状态管理工具传递状态,前台组件挂载时再读取更新。
- Android 8.0及以上启动后台服务需要
FOREGROUND_SERVICE权限,若任务执行时间较长,需将服务设为前台服务。
内容的提问来源于stack exchange,提问作者prt
相关产品推荐
相关产品推荐

