optimize ads initialize

Signed-off-by: Haoyi <haoyi.zhang@castbox.fm>
sdk/v3.0.0 v3.0.0
Haoyi 2024-04-11 13:18:00 +08:00
parent ea55fd4551
commit c6549bf584
2 changed files with 201 additions and 106 deletions

View File

@ -22,6 +22,7 @@ import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_applovin_flutter/guru_applovin_flutter.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/network/network_utils.dart';
import 'package:guru_utils/tuple/tuple.dart';
import 'package:guru_utils/ads/ads.dart';
@ -45,7 +46,8 @@ class AdsManager extends AdsManagerDelegate {
final Map<AdUnitId, ApplovinRewardedAds> rewardsAds = {};
final AdImpressionController adImpressionController = AdImpressionController();
final AdImpressionController adImpressionController =
AdImpressionController();
final BehaviorSubject<AdsConfig> _adsConfigSubject =
BehaviorSubject.seeded(AdsConfig.defaultAdsConfig);
@ -53,12 +55,11 @@ class AdsManager extends AdsManagerDelegate {
final BehaviorSubject<AdsProfile> _adsProfileSubject =
BehaviorSubject.seeded(GuruApp.instance.adsProfile);
final BehaviorSubject<bool> _initializedSubject = BehaviorSubject.seeded(false);
final BehaviorSubject<bool> _initializedSubject =
BehaviorSubject.seeded(false);
final BehaviorSubject<ConnectivityResult> connectivityStatusSubject =
BehaviorSubject.seeded(ConnectivityResult.none);
final BehaviorSubject<bool> noBannerAndInterstitialAdsSubject = BehaviorSubject.seeded(false);
final BehaviorSubject<bool> noBannerAndInterstitialAdsSubject =
BehaviorSubject.seeded(false);
final Map<String, dynamic> adsGlobalProperties = <String, dynamic>{};
@ -71,12 +72,29 @@ class AdsManager extends AdsManagerDelegate {
"connection"
};
static const List<int> ltSamples = [0, 1, 2, 3, 4, 5, 6, 14, 30, 60, 90, 120, 180];
static const List<int> ltSamples = [
0,
1,
2,
3,
4,
5,
6,
14,
30,
60,
90,
120,
180
];
@override
Stream<bool> get observableInitialized => _initializedSubject.stream;
Stream<ConnectivityResult> get observableConnectivityStatus => connectivityStatusSubject.stream;
ConnectivityResult get connectivityStatus => NetworkUtils.currentConnectivityStatus;
Stream<ConnectivityResult> get observableConnectivityStatus =>
NetworkUtils.observableConnectivityStatus;
@override
Stream<bool> get observableNoAds => noBannerAndInterstitialAdsSubject.stream;
@ -89,13 +107,13 @@ class AdsManager extends AdsManagerDelegate {
bool get hasAmazonBannerAds => adsConfig.bannerConfig.amazonEnable;
bool get hasAmazonInterstitialAds => adsConfig.interstitialConfig.amazonEnable;
bool get hasAmazonInterstitialAds =>
adsConfig.interstitialConfig.amazonEnable;
bool get hasAmazonAds => hasAmazonBannerAds || hasAmazonInterstitialAds;
ConnectivityResult get connectivityStatus => connectivityStatusSubject.value;
final BehaviorSubject<Map<String, String>> keywordsSubject = BehaviorSubject.seeded({});
final BehaviorSubject<Map<String, String>> keywordsSubject =
BehaviorSubject.seeded({});
Stream<Map<String, String>> get observableKeywords => keywordsSubject.stream;
@ -118,14 +136,16 @@ class AdsManager extends AdsManagerDelegate {
void setNoAds(bool noAds) {
noBannerAndInterstitialAdsSubject.addIfChanged(noAds);
GuruSettings.instance.isNoAds.set(noAds);
GuruAnalytics.instance.setUserProperty("user_type", noAds ? "noads" : "default");
GuruAnalytics.instance
.setUserProperty("user_type", noAds ? "noads" : "default");
setProperty("user_type", noAds ? "noads" : "default");
}
void ensureInitialize() {}
void listenIap() {
final obs = Rx.combineLatest2<bool, AssetsStore<Asset>, Tuple2<bool, AssetsStore<Asset>>>(
final obs = Rx.combineLatest2<bool, AssetsStore<Asset>,
Tuple2<bool, AssetsStore<Asset>>>(
IapManager.instance.observableAvailable,
IapManager.instance.observableAssetStore,
(a, b) => Tuple2(a, b));
@ -133,10 +153,11 @@ class AdsManager extends AdsManagerDelegate {
final available = tuple.item1;
final purchasedStore = tuple.item2;
if (available && purchasedStore.isActive) {
final tempIsNoAds =
purchasedStore.existsAssets(GuruApp.instance.productProfile.noAdsCapIds);
final tempIsNoAds = purchasedStore
.existsAssets(GuruApp.instance.productProfile.noAdsCapIds);
final isNoAds = isPurchasedNoAd;
Log.i("purchased store changed active! tempIsNoAds:$tempIsNoAds isNoAds:$isNoAds",
Log.i(
"purchased store changed active! tempIsNoAds:$tempIsNoAds isNoAds:$isNoAds",
syncFirebase: true);
if (isNoAds != tempIsNoAds) {
if (!tempIsNoAds) {
@ -149,16 +170,18 @@ class AdsManager extends AdsManagerDelegate {
}
}));
}
static bool initializedSdk = false;
Future initialize({SaasUser? saasUser}) async {
_adsProfileSubject.addEx(GuruApp.instance.adsProfile);
await initEnv();
final connected = await NetworkUtils.isNetworkConnected();
Log.d("adsManager initialize connected:$connected", tag: "Ads");
if (connected) {
await initSdk(
saasUser: saasUser,
onInitialized: () {
// loadAds();
adImpressionController.init();
initLifecycleConnectivity();
checkAndPreload();
// GuruSettings.instance.totalLevelUp
// .observe()
@ -169,37 +192,66 @@ class AdsManager extends AdsManagerDelegate {
Log.i("ADS Initialized", tag: "Ads", syncFirebase: true);
});
} else {
NetworkUtils.observableConnectivityTrack.listen((track) async {
if (track.newResult != ConnectivityResult.none) {
if (!initializedSdk) {
initializedSdk = true;
await initSdk(onInitialized: () {
adImpressionController.init();
checkAndPreload();
Log.i("ADS Initialized", tag: "Ads", syncFirebase: true);
});
}
}
if (track.newResult != track.oldResult) {
Log.i("connectivity result changed! retry ads!", tag: "Ads");
setKeyword("connection", track.newResult.toString());
if (LifecycleManager.instance.isAppForeground()) {
if (track.newResult == ConnectivityResult.none &&
track.oldResult != ConnectivityResult.none) {
Log.i("connectivity changed! retry ads!", tag: "Ads");
retry();
}
}
}
});
}
subscriptions.add(RemoteConfigManager.instance.observeConfig().listen((_) {
refreshAdsConfig();
}, onError: (error, stacktrace) {
Log.i("init config error!", tag: "Ads", error: error, stackTrace: stacktrace);
Log.i("init config error!",
tag: "Ads", error: error, stackTrace: stacktrace);
}));
listenIap();
}
void initLifecycleConnectivity() {
StreamSubscription? streamSubscription;
LifecycleManager.instance.observableAppLifecycle.listen((foreground) {
if (foreground) {
streamSubscription =
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
Log.i("Connectivity: $result", tag: "Connectivity");
if (connectivityStatus == ConnectivityResult.none && result != ConnectivityResult.none) {
Log.i("connectivity changed! retry ads!", tag: "Ads");
retry();
}
final changed = connectivityStatusSubject.addIfChanged(result);
if (changed) {
setKeyword("connection", result.toString());
}
});
} else {
streamSubscription?.cancel();
streamSubscription = null;
}
});
}
// void initLifecycleConnectivity() {
// StreamSubscription? streamSubscription;
// LifecycleManager.instance.observableAppLifecycle.listen((foreground) {
// if (foreground) {
// streamSubscription = Connectivity()
// .onConnectivityChanged
// .listen((ConnectivityResult result) {
// Log.i("Connectivity: $result", tag: "Connectivity");
// if (connectivityStatus == ConnectivityResult.none &&
// result != ConnectivityResult.none) {
// Log.i("connectivity changed! retry ads!", tag: "Ads");
// retry();
// }
// final changed = connectivityStatusSubject.addIfChanged(result);
// if (changed) {
// setKeyword("connection", result.toString());
// }
// });
// } else {
// streamSubscription?.cancel();
// streamSubscription = null;
// }
// });
// }
void initAdsProfile() {
final _hasAmazonBannerAds = hasAmazonBannerAds;
@ -209,27 +261,33 @@ class AdsManager extends AdsManagerDelegate {
final strategyInterstitialIds = adsConfig.strategyAdsConfig.interstitialIds;
final newAdsProfile = adsProfile.copyWith(
amazonAppId: _hasAmazonAds ? defaultAdsProfile.amazonAppId : null,
amazonBannerSlotId: _hasAmazonBannerAds ? defaultAdsProfile.amazonBannerSlotId : null,
amazonInterstitialSlotId:
_hasAmazonInterstitialAds ? defaultAdsProfile.amazonInterstitialSlotId : null,
amazonBannerSlotId:
_hasAmazonBannerAds ? defaultAdsProfile.amazonBannerSlotId : null,
amazonInterstitialSlotId: _hasAmazonInterstitialAds
? defaultAdsProfile.amazonInterstitialSlotId
: null,
strategyInterstitialIds: strategyInterstitialIds);
_adsProfileSubject.addEx(newAdsProfile);
}
Future initEnv() async {
final adsPropertyBundle = await AppProperty.getInstance().loadValuesByTag(PropertyTags.ads);
final adsPropertyBundle =
await AppProperty.getInstance().loadValuesByTag(PropertyTags.ads);
final isNoAds = adsPropertyBundle.getBool(PropertyKeys.isNoAds) ?? false;
consentTestDeviceId = adsPropertyBundle.getString(PropertyKeys.admobConsentTestDeviceId);
consentDebugGeography = adsPropertyBundle.getInt(PropertyKeys.admobConsentDebugGeography);
consentTestDeviceId =
adsPropertyBundle.getString(PropertyKeys.admobConsentTestDeviceId);
consentDebugGeography =
adsPropertyBundle.getInt(PropertyKeys.admobConsentDebugGeography);
noBannerAndInterstitialAdsSubject.addIfChanged(isNoAds);
GuruAnalytics.instance.setUserProperty("user_type", isNoAds ? "noads" : "default");
GuruAnalytics.instance
.setUserProperty("user_type", isNoAds ? "noads" : "default");
setProperty("user_type", isNoAds ? "noads" : "default");
final result = await Connectivity().checkConnectivity().catchError((error) {
Log.w("checkConnectivity error! $error");
});
connectivityStatusSubject.addEx(result);
// connectivityStatusSubject.addEx(result);
setProperty("connectivityStatus", result.toString());
refreshAdsConfig();
@ -283,7 +341,8 @@ class AdsManager extends AdsManagerDelegate {
}
void checkAndPreload(
{AdsValidator? rewardedValidator, AdsValidator? interstitialValidator}) async {
{AdsValidator? rewardedValidator,
AdsValidator? interstitialValidator}) async {
final canPreloadReward =
await adsConfig.rewardedConfig.canPreload(validator: rewardedValidator);
if (canPreloadReward) {
@ -294,8 +353,8 @@ class AdsManager extends AdsManagerDelegate {
}
}
final canPreloadInterstitial =
await adsConfig.interstitialConfig.canPreload(validator: interstitialValidator);
final canPreloadInterstitial = await adsConfig.interstitialConfig
.canPreload(validator: interstitialValidator);
if (!isPurchasedNoAd && canPreloadInterstitial) {
Log.d("preload interstitial canPreload!");
final interstitial = await getInterstitialAds();
@ -349,7 +408,9 @@ class AdsManager extends AdsManagerDelegate {
}
await AppProperty.getInstance().setLatestLtDate(dateNum);
}
final idx = _nearestLt(0, ltSamples.lastIndex, lt).abs().clamp(0, ltSamples.lastIndex);
final idx = _nearestLt(0, ltSamples.lastIndex, lt)
.abs()
.clamp(0, ltSamples.lastIndex);
Log.d(
"getKeywordLt: installTime:$latestLtDate now:$dateNum lt:$lt keywordLt:${ltSamples[idx]}");
@ -415,7 +476,8 @@ class AdsManager extends AdsManagerDelegate {
void setKeyword(String key, String value, {bool debugForce = false}) {
if (!GuruSettings.instance.debugMode.get() || !debugForce) {
if (_reservedKeywords.contains(key)) {
Log.w("setKeyword error! the key($key) is reserved and cannot be used!", tag: "Ads");
Log.w("setKeyword error! the key($key) is reserved and cannot be used!",
tag: "Ads");
return;
}
@ -423,7 +485,8 @@ class AdsManager extends AdsManagerDelegate {
key.length > 36 ||
key.indexOf(_alpha) != 0 ||
key.contains(_nonAlphaNumeric)) {
Log.w("setKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
Log.w(
"setKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
tag: "Ads");
return;
}
@ -436,7 +499,9 @@ class AdsManager extends AdsManagerDelegate {
void removeKeyword(String key, {bool debugForce = false}) {
if (!GuruSettings.instance.debugMode.get() || !debugForce) {
if (_reservedKeywords.contains(key)) {
Log.w("removeKeyword error! the key($key) is reserved and cannot be used!", tag: "Ads");
Log.w(
"removeKeyword error! the key($key) is reserved and cannot be used!",
tag: "Ads");
return;
}
@ -444,7 +509,8 @@ class AdsManager extends AdsManagerDelegate {
key.length > 36 ||
key.indexOf(_alpha) != 0 ||
key.contains(_nonAlphaNumeric)) {
Log.w("removeKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
Log.w(
"removeKeyword error! the key($key) must contain 1 to 36 alphanumeric characters.",
tag: "Ads");
return;
}
@ -463,22 +529,28 @@ class AdsManager extends AdsManagerDelegate {
}
bool testParseAdsDefaultConfig() {
final iadsConfigString = RemoteConfigReservedConstants.getDefaultConfigString(
final iadsConfigString =
RemoteConfigReservedConstants.getDefaultConfigString(
RemoteConfigReservedConstants.iadsConfig) ??
"";
final radsConfigString = RemoteConfigReservedConstants.getDefaultConfigString(
final radsConfigString =
RemoteConfigReservedConstants.getDefaultConfigString(
RemoteConfigReservedConstants.radsConfig) ??
"";
final badsConfigString = RemoteConfigReservedConstants.getDefaultConfigString(
final badsConfigString =
RemoteConfigReservedConstants.getDefaultConfigString(
RemoteConfigReservedConstants.badsConfig) ??
"";
final iosAttConfigString = RemoteConfigReservedConstants.getDefaultConfigString(
final iosAttConfigString =
RemoteConfigReservedConstants.getDefaultConfigString(
RemoteConfigReservedConstants.iosAttConfig) ??
"";
try {
final adInterstitial = AdInterstitialConfig.fromJson(json.decode(iadsConfigString));
final adInterstitial =
AdInterstitialConfig.fromJson(json.decode(iadsConfigString));
final adBanner = AdBannerConfig.fromJson(json.decode(badsConfigString));
final iosAttConfig = IOSAttConfig.fromJson(json.decode(iosAttConfigString));
final iosAttConfig =
IOSAttConfig.fromJson(json.decode(iosAttConfigString));
Log.d("==== ADS AdsConfig ====");
Log.d(" ---> [INTERSTITIAL]: $iadsConfigString");
Log.d(" ---> [BANNER]: $badsConfigString");
@ -486,7 +558,9 @@ class AdsManager extends AdsManagerDelegate {
Log.d("=======================");
_adsConfigSubject.addEx(AdsConfig.build(
interstitialConfig: adInterstitial, bannerConfig: adBanner, iosAttConfig: iosAttConfig));
interstitialConfig: adInterstitial,
bannerConfig: adBanner,
iosAttConfig: iosAttConfig));
return true;
} catch (error, stacktrace) {
Log.e("refreshAdsConfig error $error $stacktrace");
@ -500,14 +574,18 @@ class AdsManager extends AdsManagerDelegate {
final adInterstitial = RemoteConfigManager.instance.getIadsConfig();
final adReward = RemoteConfigManager.instance.getRadsConfig();
final adBanner = RemoteConfigManager.instance.getBadsConfig();
final strategyAdsConfig = RemoteConfigManager.instance.getStrategyAdsConfig();
final strategyAdsConfig =
RemoteConfigManager.instance.getStrategyAdsConfig();
final iosAttConfig = RemoteConfigManager.instance.getIOSAttConfig();
Log.d("==== ADS AdsConfig ====", tag: PropertyTags.ads);
Log.d(" ---> [COMMON]: ${commonAdsConfig.toJson()}", tag: PropertyTags.ads);
Log.d(" ---> [INTERSTITIAL]: ${adInterstitial.toJson()}", tag: PropertyTags.ads);
Log.d(" ---> [COMMON]: ${commonAdsConfig.toJson()}",
tag: PropertyTags.ads);
Log.d(" ---> [INTERSTITIAL]: ${adInterstitial.toJson()}",
tag: PropertyTags.ads);
Log.d(" ---> [REWARD]: ${adReward.toJson()}", tag: PropertyTags.ads);
Log.d(" ---> [BANNER]: ${adBanner.toJson()}", tag: PropertyTags.ads);
Log.d(" ---> [STRATEGY]: ${strategyAdsConfig.toJson()}", tag: PropertyTags.ads);
Log.d(" ---> [STRATEGY]: ${strategyAdsConfig.toJson()}",
tag: PropertyTags.ads);
Log.d(" ---> [IOSATT]: ${iosAttConfig.toJson()}", tag: PropertyTags.ads);
Log.d("=======================", tag: PropertyTags.ads);
_adsConfigSubject.addEx(AdsConfig.build(
@ -535,12 +613,14 @@ class AdsManager extends AdsManagerDelegate {
MaxStrategyInterstitialAds.create(strategyInterstitialIds)..init();
} else {
ad = interstitialAds[strategyInterstitialIds.first.adUnitId] ??=
ApplovinInterstitialAds.create(strategyInterstitialIds.first.adUnitId,
ApplovinInterstitialAds.create(
strategyInterstitialIds.first.adUnitId,
strategyInterstitialIds.first.amazonAdSlotId)
..init();
}
} else {
ad = interstitialAds[_adsProfile.interstitialId] ??= ApplovinInterstitialAds.create(
ad = interstitialAds[_adsProfile.interstitialId] ??=
ApplovinInterstitialAds.create(
_adsProfile.interstitialId, _adsProfile.amazonInterstitialSlotId)
..init();
}
@ -561,11 +641,13 @@ class AdsManager extends AdsManagerDelegate {
}
Future<int> requestGdpr({int? debugGeography, String? testDeviceId}) async {
Log.d("requestGdpr! debugGeography:$debugGeography testDeviceId:$testDeviceId", tag: "Ads");
Log.d(
"requestGdpr! debugGeography:$debugGeography testDeviceId:$testDeviceId",
tag: "Ads");
// adb logcat -s UserMessagingPlatform
// Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("xxxx") to set this as a debug device.
final result = await GuruApplovinFlutter.instance
.requestGdpr(debugGeography: debugGeography, testDeviceId: testDeviceId);
final result = await GuruApplovinFlutter.instance.requestGdpr(
debugGeography: debugGeography, testDeviceId: testDeviceId);
final consentResult = await GuruAnalytics.instance.refreshConsents();
Log.d("requestGdpr result:$result consentResult:$consentResult");
return result;
@ -576,14 +658,17 @@ class AdsManager extends AdsManagerDelegate {
}
Future<bool> updateOrientation(int orientation) async {
final result = await GuruApplovinFlutter.instance.updateOrientation(orientation);
final result =
await GuruApplovinFlutter.instance.updateOrientation(orientation);
return result == true;
}
@override
Future<ApplovinBannerAds> createBannerAds({String? scene, AdsLifecycleObserver? observer}) async {
Future<ApplovinBannerAds> createBannerAds(
{String? scene, AdsLifecycleObserver? observer}) async {
final _adsProfile = adsProfile;
return ApplovinBannerAds.create(_adsProfile.bannerId, _adsProfile.amazonBannerSlotId,
return ApplovinBannerAds.create(
_adsProfile.bannerId, _adsProfile.amazonBannerSlotId,
scene: scene, observer: observer);
}
@ -593,8 +678,9 @@ class AdsManager extends AdsManagerDelegate {
}
final hiddenAt = AdsManager.instance.latestFullscreenAdsHiddenTimestamps;
final now = DateTimeUtils.currentTimeInMillis();
final impGapInMillis =
AdsManager.instance.adsConfig.interstitialConfig.getSceneImpGapInSeconds(scene) * 1000;
final impGapInMillis = AdsManager.instance.adsConfig.interstitialConfig
.getSceneImpGapInSeconds(scene) *
1000;
Log.d(
"canShowInterstitial($scene): now:$now latestFullscreenAdsHiddenTimestamps:$latestFullscreenAdsHiddenTimestamps hiddenAt:$hiddenAt impGapInMillis:$impGapInMillis",
tag: "Ads");
@ -606,7 +692,8 @@ class AdsManager extends AdsManagerDelegate {
}
@override
Future<AdCause> validateInterstitial(String? scene, {AdsValidator? validator}) {
Future<AdCause> validateInterstitial(String? scene,
{AdsValidator? validator}) {
final interstitialConfig = adsConfig.interstitialConfig;
return interstitialConfig.check(scene ?? "", validator: validator);
}
@ -629,9 +716,11 @@ class AdsManager extends AdsManagerDelegate {
case "bannerAutoDisposeInterval":
return adsConfig.bannerConfig.autoDisposeIntervalInMinutes;
case "allowInterstitialAsAlternativeReward":
return GuruApp.instance.appSpec.deployment.allowInterstitialAsAlternativeReward;
return GuruApp
.instance.appSpec.deployment.allowInterstitialAsAlternativeReward;
case "showInternalAdsWhenBannerUnavailable":
return GuruApp.instance.appSpec.deployment.showInternalAdsWhenBannerUnavailable;
return GuruApp
.instance.appSpec.deployment.showInternalAdsWhenBannerUnavailable;
}
}
}

View File

@ -44,6 +44,12 @@ class NetworkUtils {
static Stream<ConnectivityTrack> get observableConnectivityTrack =>
_connectivityTrackSubject.stream;
static ConnectivityResult get currentConnectivityStatus =>
_connectivityTrackSubject.value.newResult;
static Stream<ConnectivityResult> get observableConnectivityStatus =>
observableConnectivityTrack.map((event) => event.newResult);
static void setMockConnectivityStatus(ConnectivityResult? status) {
_mockConnectivityStatus = status;
}